Native Windows-Auslieferung statt Container
Docker faellt weg. Das Projekt hat keine npm-Abhaengigkeiten und Node fuehrt TypeScript direkt aus - der Container schrieb nur noch drei Programme fest, und dafuer braucht es keine Virtualisierung, sondern einen Ordner. Der Anlass war, dass ADR-0004 zwar `docker compose pull` versprach, es aber nie ein veroeffentlichtes Image gab. Wer installieren wollte, musste whisper.cpp aus dem Quelltext uebersetzen. Plattformschicht (packages/core/src/platform.ts): - Programme werden neben der Anwendung gesucht, nicht im PATH - Daten unter %LOCALAPPDATA%, damit Installation und Update ohne Administratorrechte auskommen - Schriften mitgeliefert: ohne fontsdir setzt libass wortlos eine Ersatzschrift ein - gemessen 720 statt 826 Pixel Zeilenbreite Windows-Eigenheiten, an echtem ffmpeg 8.1 durchprobiert: - Pfade im Filtergraphen brauchen alle drei Schritte (Vorwaertsschraegstriche, escapter Doppelpunkt, Anfuehrungszeichen); je zwei davon reichen nicht - `nice` gibt es nicht, ersetzt durch os.setPriority Auslieferung: - scripts/build-package.ts baut das Paket, laeuft auch unter Linux - install.ps1 ohne Administratorrechte, Autostart ueber den Aufgabenplaner - update legt neue Fassungen bereit; eingespielt wird beim naechsten Start, nie mitten im Stream - ZIP lesen und schreiben ohne Abhaengigkeiten (packages/core/src/zip.ts) Kein `irm ... | iex`-Einzeiler: git.zeasy.dev antwortete instanzweit mit einer Umleitung auf /user/login, und `iex` fuehrte die Anmeldeseite als PowerShell aus. Der Updater verfolgt Umleitungen jetzt nicht und hat keine erfundene Vorgabeadresse mehr. Ausserdem: backup.ps1 war an zwei Stellen dokumentiert, aber nie geschrieben. ADR-0007 ersetzt ADR-0004. 84 Tests, Typpruefung sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
This commit is contained in:
1 parent
71dd660d01
commit
5f6301268a
41 files changed
+2254
-312
No files matched your search
@@ -0,0 +1,164 @@
|
||||
// ZIP lesen. Node bringt zlib mit, aber kein Archivformat, und das Projekt
|
||||
// kommt ohne Abhaengigkeiten aus (ADR-0001) - wie beim YAML-Parser und beim
|
||||
// obs-websocket-Client also selbst gebaut.
|
||||
//
|
||||
// Gebraucht wird das an zwei Stellen: beim Paketbau kommen ffmpeg,
|
||||
// whisper.cpp und Node als ZIP von ihren Herausgebern, und der Updater auf
|
||||
// Abdus Rechner packt damit das neue Paket aus - ohne PowerShell, ohne
|
||||
// externes Programm.
|
||||
|
||||
import zlib from 'node:zlib';
|
||||
|
||||
const EOCD = 0x06054b50; // End of Central Directory
|
||||
const CENTRAL = 0x02014b50; // Eintrag im Inhaltsverzeichnis
|
||||
const LOCAL = 0x04034b50; // Kopf vor den eigentlichen Daten
|
||||
|
||||
export type ZipEntry = {
|
||||
name: string;
|
||||
/** Unkomprimierte Groesse in Bytes. */
|
||||
size: number;
|
||||
/** Verzeichniseintraege haben keine Daten. */
|
||||
isDirectory: boolean;
|
||||
data: () => Buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inhaltsverzeichnis eines ZIP lesen.
|
||||
*
|
||||
* Die Daten werden erst beim Aufruf von `data()` entpackt - beim Paketbau
|
||||
* werden von 800 Eintraegen keine 30 gebraucht.
|
||||
*/
|
||||
export function readZip(buf: Buffer): ZipEntry[] {
|
||||
const eocd = findEocd(buf);
|
||||
const count = buf.readUInt16LE(eocd + 10);
|
||||
const dirSize = buf.readUInt32LE(eocd + 12);
|
||||
const dirStart = buf.readUInt32LE(eocd + 16);
|
||||
|
||||
if (dirStart === 0xffffffff || dirSize === 0xffffffff || count === 0xffff) {
|
||||
throw new Error('ZIP64 wird nicht unterstuetzt - das Archiv ist zu gross.');
|
||||
}
|
||||
|
||||
const entries: ZipEntry[] = [];
|
||||
let p = dirStart;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (buf.readUInt32LE(p) !== CENTRAL) {
|
||||
throw new Error(`Beschaedigtes Inhaltsverzeichnis bei Eintrag ${i}`);
|
||||
}
|
||||
const method = buf.readUInt16LE(p + 10);
|
||||
const compressed = buf.readUInt32LE(p + 20);
|
||||
const size = buf.readUInt32LE(p + 24);
|
||||
const nameLen = buf.readUInt16LE(p + 28);
|
||||
const extraLen = buf.readUInt16LE(p + 30);
|
||||
const commentLen = buf.readUInt16LE(p + 32);
|
||||
const localOffset = buf.readUInt32LE(p + 42);
|
||||
const name = buf.toString('utf8', p + 46, p + 46 + nameLen);
|
||||
|
||||
entries.push({
|
||||
name,
|
||||
size,
|
||||
isDirectory: name.endsWith('/'),
|
||||
data: () => extract(buf, localOffset, method, compressed, size, name),
|
||||
});
|
||||
|
||||
p += 46 + nameLen + extraLen + commentLen;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function extract(
|
||||
buf: Buffer, localOffset: number, method: number,
|
||||
compressed: number, size: number, name: string
|
||||
): Buffer {
|
||||
if (buf.readUInt32LE(localOffset) !== LOCAL) {
|
||||
throw new Error(`Kein lokaler Dateikopf fuer ${name}`);
|
||||
}
|
||||
// Der lokale Kopf hat eigene Laengen fuer Name und Extra-Feld; sie koennen
|
||||
// von denen im Inhaltsverzeichnis abweichen und muessen von hier kommen.
|
||||
const nameLen = buf.readUInt16LE(localOffset + 26);
|
||||
const extraLen = buf.readUInt16LE(localOffset + 28);
|
||||
const start = localOffset + 30 + nameLen + extraLen;
|
||||
const raw = buf.subarray(start, start + compressed);
|
||||
|
||||
if (method === 0) return Buffer.from(raw);
|
||||
if (method === 8) {
|
||||
const out = zlib.inflateRawSync(raw);
|
||||
if (out.length !== size) {
|
||||
throw new Error(`${name}: ${out.length} Bytes entpackt, ${size} erwartet`);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
throw new Error(`${name}: unbekanntes Kompressionsverfahren ${method}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Das Inhaltsverzeichnis steht am Dateiende, hinter einem Kommentar
|
||||
* beliebiger Laenge. Also von hinten suchen - hoechstens 64 KB weit, mehr
|
||||
* darf der Kommentar nicht sein.
|
||||
*/
|
||||
function findEocd(buf: Buffer): number {
|
||||
const min = Math.max(0, buf.length - 0x10000 - 22);
|
||||
for (let i = buf.length - 22; i >= min; i--) {
|
||||
if (buf.readUInt32LE(i) === EOCD) return i;
|
||||
}
|
||||
throw new Error('Kein ZIP-Inhaltsverzeichnis gefunden.');
|
||||
}
|
||||
|
||||
/**
|
||||
* ZIP schreiben. Nur so viel Format, wie der Windows-Explorer und
|
||||
* `Expand-Archive` verlangen - insbesondere die CRC32-Pruefsumme, sonst
|
||||
* halten beide das Archiv fuer beschaedigt.
|
||||
*/
|
||||
export function writeZip(files: Array<{ name: string; data: Buffer }>): Buffer {
|
||||
const locals: Buffer[] = [];
|
||||
const centrals: Buffer[] = [];
|
||||
let offset = 0;
|
||||
|
||||
for (const f of files) {
|
||||
const name = Buffer.from(f.name.replace(/\\/g, '/'), 'utf8');
|
||||
const packed = zlib.deflateRawSync(f.data, { level: 9 });
|
||||
// Groesser als das Original kommt bei schon komprimierten Dateien vor -
|
||||
// dann unveraendert ablegen.
|
||||
const useDeflate = packed.length < f.data.length;
|
||||
const body = useDeflate ? packed : f.data;
|
||||
const crc = zlib.crc32(f.data);
|
||||
|
||||
const local = Buffer.alloc(30 + name.length);
|
||||
local.writeUInt32LE(LOCAL, 0);
|
||||
local.writeUInt16LE(20, 4); // benoetigte Version: 2.0
|
||||
local.writeUInt16LE(useDeflate ? 8 : 0, 8);
|
||||
local.writeUInt32LE(crc, 14);
|
||||
local.writeUInt32LE(body.length, 18);
|
||||
local.writeUInt32LE(f.data.length, 22);
|
||||
local.writeUInt16LE(name.length, 26);
|
||||
name.copy(local, 30);
|
||||
locals.push(local, body);
|
||||
|
||||
const central = Buffer.alloc(46 + name.length);
|
||||
central.writeUInt32LE(CENTRAL, 0);
|
||||
central.writeUInt16LE(20, 4);
|
||||
central.writeUInt16LE(20, 6);
|
||||
central.writeUInt16LE(useDeflate ? 8 : 0, 10);
|
||||
central.writeUInt32LE(crc, 16);
|
||||
central.writeUInt32LE(body.length, 20);
|
||||
central.writeUInt32LE(f.data.length, 24);
|
||||
central.writeUInt16LE(name.length, 28);
|
||||
central.writeUInt32LE(offset, 42);
|
||||
name.copy(central, 46);
|
||||
centrals.push(central);
|
||||
|
||||
offset += local.length + body.length;
|
||||
}
|
||||
|
||||
const dir = Buffer.concat(centrals);
|
||||
const eocd = Buffer.alloc(22);
|
||||
eocd.writeUInt32LE(EOCD, 0);
|
||||
eocd.writeUInt16LE(files.length, 8);
|
||||
eocd.writeUInt16LE(files.length, 10);
|
||||
eocd.writeUInt32LE(dir.length, 12);
|
||||
eocd.writeUInt32LE(offset, 16);
|
||||
|
||||
const out = Buffer.concat([...locals, dir, eocd]);
|
||||
if (out.length > 0xffffffff) throw new Error('Ueber 4 GB braeuchte ZIP64.');
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user