feat(v5): Etappe W-5 — Oberflaeche: Tray, Toasts, Taskleiste, Einstellungen, Bibliothek
Ampel / ampel (push) Successful in 1m23s
Ampel / ampel (push) Successful in 1m23s
haupt/: tray.ts (Symbol mit Zustand, Kontextmenue, Fenster zu = weiter- laufen, Beenden nur noch gewollt), Windows-Toasts bei Disc erkannt / fertig / Fehler (Klick holt das Fenster), Taskleisten-Fortschritt (setProgressBar, Fehlermodus), echter Windows-Ordnerdialog per IPC. Eigenes Icon (bau/icon.png + tray.png, Disc-Optik). Kern: Einstellungen lesen/schreiben NUR ueber die eine Quelle (§ 6.7, Whitelist der Schluessel), Werkzeug-Auskunft (Katalog + gemessene HandBrake-Presets/Backends), Bibliothek in db.ts (Schema v2 — fingerprint, Titel, Groesse, Ort, Dauer), Duplikat-Erkennung beim Einlegen (§ 6.6: 'schon einmal gerippt am …' als disc-info + Toast), Pipeline traegt fertige Vorgaenge ein. Fenster: drei Bereiche (Uebersicht / Einstellungen / Bibliothek). Einstellungen: Ablage mit Durchsuchen-Dialog, Kompression je Disc-Typ (Preset-Liste VOM eigenen HandBrake gemessen, 'NICHT komprimieren' waehlbar), Sprachlisten, TMDb/OMDb-Keys, Medienserver, Werkzeug-Stand mit Klartext (gefunden/fehlt + Folgen). Bibliothek als Tabelle. Der Smoke-Screenshot zeigt nebenbei den Ernstfall in schoen: Das Laufwerk ist nach dem rc11-Vorfall inzwischen auch am Storage-Stack in 'unknown' gekippt — und die Kachel zeigt den ZUGRIFFS_GRUENDE-Klartext samt Abhilfe statt eines stummen Zustands. 165 Tests gruen, Smoke gruen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a9fa8fa2d8
commit
c78ff22ab7
@@ -18,7 +18,7 @@ import { Pipeline } from './ablauf/pipeline'
|
||||
import type { LaufwerkApi } from './laufwerk/codes'
|
||||
import { auswerfenMitGrund, geraeteInfo, kennung, listOptischeLaufwerke } from './laufwerk/disc'
|
||||
import { DiscWache } from './laufwerk/wache'
|
||||
import { discTitel } from './metadaten/discmerkmale'
|
||||
import { discFingerprint, discTitel } from './metadaten/discmerkmale'
|
||||
import { OmdbClient } from './metadaten/omdb'
|
||||
import { TmdbClient } from './metadaten/tmdb'
|
||||
import { zuordnen, type Zuordnung } from './metadaten/zuordnung'
|
||||
@@ -84,10 +84,13 @@ let wache: DiscWache | null = null
|
||||
|
||||
// ── Metadaten (W-4): Was liegt da drin? ─────────────────────────────────
|
||||
const discZuordnungen = new Map<string, Zuordnung>()
|
||||
const discFingerprints = new Map<string, string>()
|
||||
|
||||
async function discErmitteln(id: string, geraetepfad: string): Promise<void> {
|
||||
async function discErmitteln(id: string, geraetepfad: string, groesseBytes: number): Promise<void> {
|
||||
try {
|
||||
const gefunden = discTitel(geraetepfad)
|
||||
const fingerprint = discFingerprint(geraetepfad, groesseBytes)
|
||||
discFingerprints.set(id, fingerprint)
|
||||
const tmdb = new TmdbClient(db?.einstellungLesen('tmdbApiKey') ?? '')
|
||||
const omdb = new OmdbClient(db?.einstellungLesen('omdbApiKey') ?? '')
|
||||
const zuordnung = await zuordnen(gefunden.titel, tmdb, omdb)
|
||||
@@ -101,6 +104,9 @@ async function discErmitteln(id: string, geraetepfad: string): Promise<void> {
|
||||
confidence: zuordnung.confidence,
|
||||
quelle: zuordnung.meta.quelle,
|
||||
posterPfad: zuordnung.meta.posterPfad,
|
||||
// § 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,
|
||||
},
|
||||
})
|
||||
} catch (fehler) {
|
||||
@@ -124,12 +130,21 @@ const laufwerkeBereit: Promise<void> = (async () => {
|
||||
anHauptUndFenster({ art: 'laufwerke', liste: meldung.laufwerke, ereignisse: meldung.ereignisse })
|
||||
for (const ereignis of meldung.ereignisse) {
|
||||
if (ereignis.art === 'disc-eingelegt') {
|
||||
void discErmitteln(ereignis.geraet.id, ereignis.geraet.pfad)
|
||||
void discErmitteln(ereignis.geraet.id, ereignis.geraet.pfad, ereignis.geraet.groesseBytes)
|
||||
} else if (ereignis.art === 'disc-entnommen') {
|
||||
discZuordnungen.delete(ereignis.geraet.id)
|
||||
discFingerprints.delete(ereignis.geraet.id)
|
||||
anHauptUndFenster({
|
||||
art: 'disc-info',
|
||||
info: { id: ereignis.geraet.id, titel: '', jahr: null, confidence: 0, quelle: '', posterPfad: '' },
|
||||
info: {
|
||||
id: ereignis.geraet.id,
|
||||
titel: '',
|
||||
jahr: null,
|
||||
confidence: 0,
|
||||
quelle: '',
|
||||
posterPfad: '',
|
||||
schonGerippt: null,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -193,8 +208,62 @@ const ripVerwaltung = new Pipeline({
|
||||
einstellungen: einstellungenLaden,
|
||||
melden: (status) => anHauptUndFenster({ art: 'rip-status', status }),
|
||||
zuordnung: (id) => discZuordnungen.get(id) ?? null,
|
||||
fingerprint: (id) => discFingerprints.get(id) ?? null,
|
||||
bibliothekEintragen: (eintrag) => {
|
||||
try {
|
||||
db?.bibliothekEintragen(eintrag)
|
||||
anFenster({ art: 'bibliothek', eintraege: db?.bibliothekListe() ?? [] })
|
||||
} catch (fehler) {
|
||||
// R4: Ein Bibliotheks-Fehler kippt keinen gelungenen Rip, wird aber
|
||||
// gesagt.
|
||||
anFenster({ art: 'kern-fehler', text: `Bibliothek: ${String(fehler)}` })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// ── Einstellungen (§ 6.7): das Fenster liest und schreibt NUR über den
|
||||
// Kern; erlaubt sind genau diese Schlüssel ───────────────────────────
|
||||
const ERLAUBTE_EINSTELLUNGEN = new Set([
|
||||
'ablage',
|
||||
'transcodeEnabled',
|
||||
'transcodePreset',
|
||||
'transcodePresetDvd',
|
||||
'transcodePresetBluray',
|
||||
'transcodePresetUhd',
|
||||
'audioSprachen',
|
||||
'untertitelSprachen',
|
||||
'mediaServer',
|
||||
'mediaServerUrl',
|
||||
'mediaServerApiKey',
|
||||
'tmdbApiKey',
|
||||
'omdbApiKey',
|
||||
'autoAuswurf',
|
||||
'werkzeug.makemkv',
|
||||
'werkzeug.handbrake',
|
||||
'werkzeug.flac',
|
||||
])
|
||||
|
||||
function einstellungenSenden(): void {
|
||||
const werte: Record<string, string> = { ablage: ablageOrdner() }
|
||||
for (const schluessel of ERLAUBTE_EINSTELLUNGEN) {
|
||||
const wert = db?.einstellungLesen(schluessel)
|
||||
if (wert !== null && wert !== undefined) werte[schluessel] = wert
|
||||
}
|
||||
anFenster({ art: 'einstellungen', werte })
|
||||
}
|
||||
|
||||
async function werkzeugeSenden(): Promise<void> {
|
||||
const makemkvcon = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
|
||||
const handbrake = finden('handbrake', db?.einstellungLesen('werkzeug.handbrake') ?? '')
|
||||
const flac = finden('flac', db?.einstellungLesen('werkzeug.flac') ?? '')
|
||||
const { encoderMessen } = await import('./komprimieren/encoder')
|
||||
const gemessen = await encoderMessen(handbrake)
|
||||
anFenster({
|
||||
art: 'werkzeuge',
|
||||
auskunft: { makemkvcon, handbrake, flac, presets: gemessen.presets, backends: gemessen.backends },
|
||||
})
|
||||
}
|
||||
|
||||
async function auswerfen(id: string): Promise<void> {
|
||||
await laufwerkeBereit
|
||||
if (ripVerwaltung.laeuft(id)) {
|
||||
@@ -242,6 +311,21 @@ process.parentPort.on('message', (ereignis) => {
|
||||
void laufwerkeBereit.then(() => ripVerwaltung.starten(frage.id))
|
||||
} else if (frage.art === 'rip-abbruch') {
|
||||
ripVerwaltung.abbrechen(frage.id)
|
||||
} else if (frage.art === 'einstellung-setzen') {
|
||||
if (!ERLAUBTE_EINSTELLUNGEN.has(frage.schluessel)) {
|
||||
anFenster({ art: 'kern-fehler', text: `Unbekannter Einstellungs-Schlüssel: ${frage.schluessel}` })
|
||||
} else if (db === null) {
|
||||
anFenster({ art: 'kern-fehler', text: 'Einstellung nicht gespeichert — die Datenbank ist nicht da.' })
|
||||
} else {
|
||||
db.einstellungSchreiben(frage.schluessel, frage.wert)
|
||||
einstellungenSenden()
|
||||
}
|
||||
} else if (frage.art === 'einstellungen-laden') {
|
||||
einstellungenSenden()
|
||||
} else if (frage.art === 'werkzeuge-laden') {
|
||||
void werkzeugeSenden()
|
||||
} else if (frage.art === 'bibliothek-laden') {
|
||||
anFenster({ art: 'bibliothek', eintraege: db?.bibliothekListe() ?? [] })
|
||||
}
|
||||
})
|
||||
fensterPort.start()
|
||||
|
||||
Reference in New Issue
Block a user