feat(v5): MakeMKV-Selbst-Update (§ 9) und Kino-Look fürs Fenster
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>
This commit is contained in:
Hitonabi
2026-08-31 20:18:08 +02:00
co-authored by Claude Fable 5
parent 5622d8e47d
commit d5ea48cd1e
15 changed files with 1199 additions and 201 deletions
+123
View File
@@ -113,6 +113,8 @@ async function discErmitteln(id: string, geraetepfad: string, groesseBytes: numb
confidence: zuordnung.confidence,
quelle: zuordnung.meta.quelle,
posterPfad: zuordnung.meta.posterPfad,
backdropPfad: zuordnung.meta.backdropPfad,
beschreibung: zuordnung.meta.beschreibung,
// § 6.6: Rippy erkennt beim Einlegen, wenn eine Disc schon einmal
// durchgelaufen ist — und fragt nach, statt stumm zu doppeln.
schonGerippt: db?.bibliothekSchonGerippt(fingerprint) ?? null,
@@ -182,6 +184,7 @@ async function zuordnungSetzen(id: string, vorschlag: MetaVorschlag): Promise<vo
jahr: jahrAus(vorschlag.typ === 'movie' ? details?.release_date : details?.first_air_date) ?? vorschlag.jahr,
beschreibung: details?.overview ?? '',
posterPfad: details?.poster_path ?? vorschlag.posterPfad,
backdropPfad: details?.backdrop_path ?? '',
laufzeitMinuten: details?.runtime ?? 0,
genres: (details?.genres ?? []).map((g) => g.name),
quelle: 'tmdb',
@@ -197,6 +200,8 @@ async function zuordnungSetzen(id: string, vorschlag: MetaVorschlag): Promise<vo
confidence: 1,
quelle: 'deine Wahl',
posterPfad: zuordnung.meta.posterPfad,
backdropPfad: zuordnung.meta.backdropPfad,
beschreibung: zuordnung.meta.beschreibung,
schonGerippt: db?.bibliothekSchonGerippt(discFingerprints.get(id) ?? '') ?? null,
},
})
@@ -231,6 +236,8 @@ const laufwerkeBereit: Promise<void> = (async () => {
confidence: 0,
quelle: '',
posterPfad: '',
backdropPfad: '',
beschreibung: '',
schonGerippt: null,
},
})
@@ -385,6 +392,113 @@ async function schluesselLauf(): Promise<void> {
// Täglich neu prüfen (§ 9.1) — ohne den Prozess-Exit aufzuhalten.
setInterval(() => void schluesselLauf(), 24 * 60 * 60 * 1000).unref()
// ── MakeMKV-Update (§ 9): holen, prüfen, installieren lassen ────────────
// Der Kern lädt und PRÜFT die Datei; STARTEN kann sie nur das Haupt
// (shell → UAC-Abfrage; der erhöhte Installer hängt dadurch nicht an der
// Prozess-Leine und überlebt ein geschlossenes Rippy).
let beschaffungLaeuft = false
let installerAntwort: ((fehler: string | null) => void) | null = null
function beschaffungMelden(text: string, anteil: number | null, laeuft: boolean, fehler = false): void {
anFenster({ art: 'beschaffung-status', stand: { werkzeug: 'makemkv', laeuft, text, anteil, fehler } })
}
async function makemkvUpdateLauf(): Promise<void> {
if (beschaffungLaeuft) {
beschaffungMelden('Ein MakeMKV-Update läuft bereits.', null, true)
return
}
beschaffungLaeuft = true
try {
const { istNeuer, makemkvNeuesteVersion, makemkvInstallerHolen, versionsangaben } = await import(
'./werkzeuge/beschaffen'
)
beschaffungMelden('Nach der neuesten MakeMKV-Fassung wird gesucht …', null, true)
const neueste = await makemkvNeuesteVersion()
if (neueste.length === 0) {
beschaffungMelden(
'Keine der Quellen konnte sagen, welche Fassung aktuell ist (Hersteller-Seite, Forum, Archiv). ' +
'Ausweg: MakeMKV von Hand von makemkv.com laden und installieren; Rippy findet es danach von allein.',
null,
false,
true,
)
beschaffungLaeuft = false
return
}
const installiert = schluesselStand?.version ?? ''
if (installiert.length > 0 && !istNeuer(neueste, installiert)) {
// Ehrlich bleiben (31.08.2026 gemessen: 1.18.4 ist installiert UND
// die neueste angekündigte — die Beta ist nur abgelaufen): Ein
// Download derselben Fassung würde nichts lösen.
beschaffungMelden(
`MakeMKV ${installiert} ist bereits die neueste angekündigte Fassung. ` +
'Sobald der Hersteller eine neuere veröffentlicht, holt dieser Knopf sie — ' +
'bis dahin rippt der Beta-Modus weiter.',
null,
false,
)
beschaffungLaeuft = false
return
}
// Merken, woran eine gelungene Installation zu erkennen ist: Die
// Versions-Ressource der gefundenen EXE ändert sich (oder es gibt
// erstmals eine).
const alteExe = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
const alteVersion = alteExe.length > 0 ? versionsangaben(alteExe).version : ''
const ergebnis = await makemkvInstallerHolen(neueste, (text, anteil) =>
beschaffungMelden(text, anteil, true),
)
beschaffungMelden(`Geprüft (Quelle: ${ergebnis.quelle}) — der Installer wird gestartet …`, null, true)
const startFehler = await new Promise<string | null>((fertig) => {
installerAntwort = fertig
process.parentPort.postMessage({ art: 'installer-starten', pfad: ergebnis.pfad })
// Falls das Haupt nie antwortet, bleibt der Lauf nicht ewig hängen.
setTimeout(() => fertig('Keine Antwort vom Haupt-Prozess'), 30_000).unref()
})
installerAntwort = null
if (startFehler !== null && startFehler.length > 0) {
beschaffungMelden(`Der Installer wurde nicht gestartet: ${startFehler}`, null, false, true)
beschaffungLaeuft = false
return
}
beschaffungMelden(
`Der MakeMKV-Installer ${ergebnis.version} läuft — bitte die Windows-Nachfrage bestätigen und durchklicken. ` +
'Rippy merkt die fertige Installation von allein und prüft den Schlüssel neu.',
null,
true,
)
// Warten, bis die Installation durch ist: Die Versions-Ressource der
// EXE ändert sich. Alle 15 s nachsehen, höchstens 20 Minuten.
let runden = 0
const zeiger = setInterval(() => {
runden += 1
const exe = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
const jetztVersion = exe.length > 0 ? versionsangaben(exe).version : ''
if (jetztVersion.length > 0 && jetztVersion !== alteVersion) {
clearInterval(zeiger)
beschaffungLaeuft = false
beschaffungMelden(`MakeMKV ${jetztVersion} ist installiert — der Schlüssel wird neu geprüft.`, null, false)
void schluesselLauf().then(() => werkzeugeSenden())
} else if (runden >= 80) {
clearInterval(zeiger)
beschaffungLaeuft = false
beschaffungMelden(
'Die fertige Installation wurde nicht bemerkt — nach dem Durchklicken hilft „Schlüssel neu prüfen".',
null,
false,
)
}
}, 15_000)
zeiger.unref()
} catch (fehler) {
beschaffungMelden(fehler instanceof Error ? fehler.message : String(fehler), null, false, true)
beschaffungLaeuft = false
}
}
// Die gemessene Auskunft bleibt liegen — die Pipeline braucht sie für die
// Preset-Empfehlung, auch wenn gerade kein Fenster offen ist.
let werkzeugAuskunft: { presets: string[]; backends: string[] } = { presets: [], backends: [] }
@@ -432,6 +546,11 @@ async function auswerfen(id: string): Promise<void> {
process.parentPort.on('message', (ereignis) => {
const nachricht: unknown = ereignis.data
if (istHauptNachricht(nachricht) && nachricht.art === 'installer-ergebnis') {
installerAntwort?.(nachricht.fehler ?? '')
installerAntwort = null
return
}
if (istHauptNachricht(nachricht) && nachricht.art === 'fenster-port') {
// Das Haupt reicht einen frischen Port zum Fenster durch. Ein neues
// Fenster bringt einen neuen Port — der alte wird geschlossen.
@@ -468,6 +587,10 @@ process.parentPort.on('message', (ereignis) => {
void werkzeugeSenden()
} else if (frage.art === 'bibliothek-laden') {
anFenster({ art: 'bibliothek', eintraege: db?.bibliothekListe() ?? [] })
} else if (frage.art === 'makemkv-update') {
void makemkvUpdateLauf()
} else if (frage.art === 'schluessel-pruefen') {
void schluesselLauf().then(() => werkzeugeSenden())
}
})
fensterPort.start()