Ampel / ampel (push) Successful in 1m34s
Update-Strecke: Quellenkette (Hersteller 525 → Forum → Archiv, höchste gewinnt, 31.08. nachgemessen), MZ- + Versions-Ressourcen-Prüfung (GuinpinSoft inc, gemessen), Download in .neu, Start übers Haupt (shell/UAC — Installer hängt bewusst NICHT an der Leine), Warte-Poll bis die EXE-Version wechselt, dann Schlüssel neu prüfen. Ehrlicher Zweig: installierte == neueste Fassung → kein sinnloser Download. Fenster: Laufwerks-Kachel als Bühne (TMDb-Backdrop, Poster, Beschreibung, Phasen-Label, Restzeit, aufklappbares Protokoll), Bibliothek als Poster-Regal (DB-Schema 3: poster_pfad, Migration), Schlüssel-Karte mit Update-Knopf und Beschaffungs-Balken, MakeMKV-Version als Badge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
// Startet und überwacht den Kern (utilityProcess) — KONZEPT § 4.1.
|
|
// utilityProcess statt child_process.fork ist Electrons eigene Empfehlung;
|
|
// nur er kann einen MessagePort DIREKT zwischen Kern und Fenster aufspannen
|
|
// (Electron-Doku »utilityProcess« / »MessagePorts in Electron«).
|
|
import { app, MessageChannelMain, utilityProcess, type UtilityProcess, type WebContents } from 'electron'
|
|
import { join } from 'node:path'
|
|
import { istKernNachricht, type HauptNachricht, type KernNachricht } from '../gemeinsam/nachrichten'
|
|
|
|
export interface KernZustand {
|
|
laeuft: boolean
|
|
pid?: number
|
|
neustarts: number
|
|
}
|
|
|
|
// Stirbt der Kern öfter als so, stimmt etwas Grundsätzliches — dann wird
|
|
// nicht weiter neu gestartet, sondern der Zustand im Fenster gezeigt.
|
|
const MAX_NEUSTARTS_JE_MINUTE = 3
|
|
|
|
export class KernVerwaltung {
|
|
private kern: UtilityProcess | null = null
|
|
private beendenGewollt = false
|
|
private neustarts = 0
|
|
private neustartZeiten: number[] = []
|
|
|
|
constructor(
|
|
private readonly datenbankPfad: string,
|
|
private readonly beiNachricht: (nachricht: KernNachricht) => void,
|
|
private readonly beiZustand: (zustand: KernZustand) => void,
|
|
private readonly smokeModus = false,
|
|
) {}
|
|
|
|
starten(): void {
|
|
const kernDatei = join(__dirname, 'kern.js')
|
|
const args = [`--datenbank=${this.datenbankPfad}`]
|
|
// Der Smoke-Beweis läuft ohne Netz und ohne MakeMKV — der Kern lässt
|
|
// die Schlüsselkette dann aus.
|
|
if (this.smokeModus) args.push('--smoke-kern')
|
|
// Im installierten Rippy liegen die mitgelieferten Werkzeuge unter
|
|
// resources\vendor — der Kern findet sie über RIPPY_VENDOR (§ 9).
|
|
const umgebung: NodeJS.ProcessEnv = { ...process.env }
|
|
if (app.isPackaged) {
|
|
umgebung['RIPPY_VENDOR'] = join(process.resourcesPath, 'vendor')
|
|
}
|
|
const kern = utilityProcess.fork(kernDatei, args, {
|
|
serviceName: 'Rippy-Kern',
|
|
stdio: 'inherit',
|
|
env: umgebung,
|
|
})
|
|
this.kern = kern
|
|
|
|
kern.on('message', (nachricht: unknown) => {
|
|
if (istKernNachricht(nachricht)) this.beiNachricht(nachricht)
|
|
})
|
|
|
|
kern.on('spawn', () => {
|
|
this.beiZustand(this.zustand())
|
|
})
|
|
|
|
kern.on('exit', (code) => {
|
|
this.kern = null
|
|
if (this.beendenGewollt) return
|
|
// R4: Ein gestorbener Kern ist ein lautes Ereignis, kein stilles.
|
|
console.error(`[haupt] Kern beendet mit Code ${code} — Neustart wird geprüft`)
|
|
const jetzt = Date.now()
|
|
this.neustartZeiten = this.neustartZeiten.filter((t) => jetzt - t < 60_000)
|
|
if (this.neustartZeiten.length >= MAX_NEUSTARTS_JE_MINUTE) {
|
|
console.error('[haupt] Kern stirbt wiederholt — kein weiterer Neustart')
|
|
this.beiZustand(this.zustand())
|
|
return
|
|
}
|
|
this.neustartZeiten.push(jetzt)
|
|
this.neustarts += 1
|
|
this.starten()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Spannt einen frischen MessagePort direkt zwischen Kern und Fenster auf.
|
|
* Der Fortschritt eines Rips geht damit später ohne Umweg über das Haupt
|
|
* ins Fenster (§ 4.1).
|
|
*/
|
|
fensterVerbinden(webContents: WebContents): void {
|
|
if (this.kern === null) return
|
|
const { port1, port2 } = new MessageChannelMain()
|
|
const nachricht: HauptNachricht = { art: 'fenster-port' }
|
|
this.kern.postMessage(nachricht, [port1])
|
|
webContents.postMessage('kern-port', null, [port2])
|
|
}
|
|
|
|
/** Eine Nachricht an den Kern (z. B. die Antwort auf 'installer-starten'). */
|
|
senden(nachricht: HauptNachricht): void {
|
|
this.kern?.postMessage(nachricht)
|
|
}
|
|
|
|
zustand(): KernZustand {
|
|
return {
|
|
laeuft: this.kern !== null,
|
|
pid: this.kern?.pid,
|
|
neustarts: this.neustarts,
|
|
}
|
|
}
|
|
|
|
beenden(): void {
|
|
this.beendenGewollt = true
|
|
this.kern?.kill()
|
|
this.kern = null
|
|
}
|
|
}
|