feat(v5): Etappe W-2 — Rippen: Parser, makemkvcon-Lauf, Werkzeug-Katalog
Ampel / ampel (push) Failing after 1m22s
Ampel / ampel (push) Failing after 1m22s
rip/parser.ts: die reine Textverarbeitung mit allen bezahlten Fallen aus dem Docker-Zweig — quelle() (dev:G: statt dev:\.\G:, der 404-Verwandte), textVon() (UTF-8 streng, sonst cp1252 — nie wieder 'Öffnen'), parseMsg (Regex statt split — Meldungen mit Komma), PRGV (-1 vs. echtes 0 %), TINFO/SINFO (Sprache aus Attr 3/4, NICHT 28/29), laengsterTitel mit Play-All-Schutz, episodenTitel, KRITISCHE_CODES (sprachfeste NUMMERN). rip/makemkv.ts: Kommandobau + Lauf — ZeilenLeser (binaer, halbe Zeilen, je Zeile dekodiert), RipAuswertung (pur), rippen() mit AbortSignal, Lesefehler-Flag AUCH bei Rueckgabewert 0. werkzeuge/katalog.ts: die Suchreihenfolge samt %VAR%-Gross/Klein-Falle und Registry-Rueckfall. ablauf/rippen.ts: ein Rip je Laufwerk, Status-Push, 'erst nachsehen, dann rippen' (unbekannt heisst trotzdem rippen). Fenster: Rippen-Knopf, Fortschrittsbalken, Abbrechen; Auswurf waehrend des Rips abgelehnt. Gemessen am echten System (30.08.2026): Katalog findet makemkvcon64 (Program Files x86), HandBrakeCLI und flac (beide im rc11-Werkzeugordner LOCALAPPDATA\Rippy\tools). NEUER BEFUND: Nach dem abgebrochenen rc11-Rip vom Mittag sieht MakeMKV GAR KEIN Laufwerk mehr (MSG 5042, leere DRV-Liste, mit und ohne --noscan), waehrend die Storage-IOCTLs sauber antworten — MakeMKV spricht SCSI direkt. 5042 ist jetzt ein KRITISCHER_CODE mit Klartext-Abhilfe. Der volle Rip-Beweis (messung.rip.test.ts liegt bereit) braucht deshalb erst eine Hand am Geraet: Disc neu einlegen oder USB ab-/anstecken. 96 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
a45069af5e
commit
844bca37ff
@@ -6,13 +6,16 @@
|
||||
// jedem neuen Abonnenten sofort wiedergegeben — er wird nie auf »leer«
|
||||
// zurückgesetzt, nur weil gerade keine Nachricht kam. Insbesondere die
|
||||
// Laufwerksliste: Sie ändert sich NUR durch eine neue Liste vom Kern.
|
||||
import { istKernNachricht, type GeraeteInfo, type KernNachricht } from '../gemeinsam/nachrichten'
|
||||
import { istKernNachricht, type GeraeteInfo, type KernNachricht, type RipStatus } from '../gemeinsam/nachrichten'
|
||||
|
||||
export interface Verbindung {
|
||||
verbunden: boolean
|
||||
kern: Extract<KernNachricht, { art: 'kern-bereit' }> | null
|
||||
pongLatenzMs: number | null
|
||||
laufwerke: GeraeteInfo[]
|
||||
/** Letzter Rip-Stand je Laufwerks-Kennung. Bleibt nach dem Ende stehen,
|
||||
* bis ein neuer Rip startet (R2: kein Verschwinden ohne neue Auskunft). */
|
||||
rips: Record<string, RipStatus>
|
||||
/** Kurze Ereigniszeilen (neueste zuerst): Disc rein/raus, Auswurf, Fehler. */
|
||||
meldungen: string[]
|
||||
}
|
||||
@@ -21,13 +24,22 @@ type Horcher = (stand: Verbindung) => void
|
||||
|
||||
const MAX_MELDUNGEN = 8
|
||||
|
||||
const stand: Verbindung = { verbunden: false, kern: null, pongLatenzMs: null, laufwerke: [], meldungen: [] }
|
||||
const stand: Verbindung = {
|
||||
verbunden: false,
|
||||
kern: null,
|
||||
pongLatenzMs: null,
|
||||
laufwerke: [],
|
||||
rips: {},
|
||||
meldungen: [],
|
||||
}
|
||||
const horcher = new Set<Horcher>()
|
||||
let gestartet = false
|
||||
let port: MessagePort | null = null
|
||||
|
||||
function melden(): void {
|
||||
for (const h of horcher) h({ ...stand, laufwerke: [...stand.laufwerke], meldungen: [...stand.meldungen] })
|
||||
for (const h of horcher) {
|
||||
h({ ...stand, laufwerke: [...stand.laufwerke], rips: { ...stand.rips }, meldungen: [...stand.meldungen] })
|
||||
}
|
||||
}
|
||||
|
||||
function meldung(text: string): void {
|
||||
@@ -67,6 +79,20 @@ function kernNachricht(nachricht: KernNachricht): void {
|
||||
meldung(nachricht.grund === 'ok' ? text : `⚠ ${text}`)
|
||||
break
|
||||
}
|
||||
case 'rip-status': {
|
||||
stand.rips = { ...stand.rips, [nachricht.status.id]: nachricht.status }
|
||||
if (nachricht.status.phase === 'fertig') {
|
||||
meldung(
|
||||
`Laufwerk ${nachricht.status.id}: Rip fertig — ${nachricht.status.dateien.length} Datei(en)` +
|
||||
(nachricht.status.lesefehler ? ' ⚠ mit Lesefehlern' : ''),
|
||||
)
|
||||
} else if (nachricht.status.phase === 'fehler') {
|
||||
meldung(`⚠ Laufwerk ${nachricht.status.id}: ${nachricht.status.text}`)
|
||||
} else if (nachricht.status.phase === 'abgebrochen') {
|
||||
meldung(`Laufwerk ${nachricht.status.id}: Rip abgebrochen`)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
melden()
|
||||
}
|
||||
@@ -104,15 +130,27 @@ export function verbindungStarten(): void {
|
||||
|
||||
export function aufVerbindung(h: Horcher): () => void {
|
||||
horcher.add(h)
|
||||
h({ ...stand, laufwerke: [...stand.laufwerke], meldungen: [...stand.meldungen] })
|
||||
h({ ...stand, laufwerke: [...stand.laufwerke], rips: { ...stand.rips }, meldungen: [...stand.meldungen] })
|
||||
return () => horcher.delete(h)
|
||||
}
|
||||
|
||||
export function auswerfen(id: string): void {
|
||||
function anKern(nachricht: { art: string; id: string }, wasWar: string): void {
|
||||
if (port === null) {
|
||||
meldung(`⚠ Auswerfen ${id}: keine Verbindung zum Kern`)
|
||||
meldung(`⚠ ${wasWar}: keine Verbindung zum Kern`)
|
||||
melden()
|
||||
return
|
||||
}
|
||||
port.postMessage({ art: 'auswerfen', id })
|
||||
port.postMessage(nachricht)
|
||||
}
|
||||
|
||||
export function auswerfen(id: string): void {
|
||||
anKern({ art: 'auswerfen', id }, `Auswerfen ${id}`)
|
||||
}
|
||||
|
||||
export function ripStarten(id: string): void {
|
||||
anKern({ art: 'rip-start', id }, `Rip ${id}`)
|
||||
}
|
||||
|
||||
export function ripAbbrechen(id: string): void {
|
||||
anKern({ art: 'rip-abbruch', id }, `Rip-Abbruch ${id}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user