fix(v5): Nur der NEUESTE Titel-Lauf antwortet — ein alter legte sich darueber
Ampel / ampel (push) Successful in 1m49s
Ampel / ampel (push) Successful in 1m49s
Commander: "Er liest es korrekt ein, ich bekomme auch die auswahl aber kurz danach kommt dieser fehler" — "makemkvcon info: keine Antwort nach 300 s", MINUTEN nach der fertigen Titelliste. Mit nur einem Lauf ist das unmoeglich: Der Timeout wird bei 'close' geloescht. Es waren also zwei Laeufe unterwegs, und der aeltere meldete verspaetet seinen Abbruch — auf die laengst gelieferte Liste drauf. Behoben, je Laufwerk: * Ein neuer Lauf LOEST den alten AB: AbortController im Kern, titelInfoLesen nimmt jetzt ein AbortSignal, killt das Kind und lehnt mit der Marke ABGELOEST ab. Das Laufwerk ist sofort frei, und es laufen nie zwei makemkvcon auf derselben Disc. * Nur die NEUESTE Laufnummer darf antworten (titelLaufNummern) — eine verspaetete Antwort wird verworfen, nicht angezeigt. * Ein abgeloester Lauf SCHWEIGT: abgeloest ist Absicht, kein Fehlschlag. * Laeuft es wirklich in den Timeout, steht jetzt ein Satz statt einer Fehlermeldung: Bei einer beschaedigten Disc kann das Lesen ueber fuenf Minuten brauchen; Rippy beendet den Versuch und gibt das Laufwerk frei. Am lebenden Objekt bestaetigt (SPARTACUS_GOTA_D1, Commander-Screenshot): "Serie: 2 Folgen erwartet, 2 gefunden - passt." | Titel 0 52:47 min 16,28 GB 7 Kap. | Titel 1 54:09 min 16,67 GB 7 Kap. | beide Rolle Folge | 2 von 2 gewaehlt, zusammen 32,95 GB — und KEIN Timeout hinterher. Die Laengen liegen 2,5 Prozent auseinander, also weit innerhalb von AEHNLICH (15 Prozent) — deshalb "passt" ohne Unsicherheits-Warnung. 261 Tests gruen, Typpruefung sauber. Version 5.3.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9622f4e6ef
commit
6d149212c9
@@ -1,22 +1,20 @@
|
||||
Version 5.3.1
|
||||
Version 5.3.2
|
||||
|
||||
Die Titel-Auswahl funktioniert jetzt wirklich.
|
||||
In 5.2.0 und 5.3.0 blieb der Dialog ewig bei "Rippy liest die Titel der
|
||||
Disc …" stehen. Der Grund war ein Fehler in Rippy selbst: Die Anfrage
|
||||
"lies die Titel" war dem Kern unbekannt und wurde weggeworfen — still.
|
||||
Behoben, und zwar auf drei Ebenen: Die Anfrage ist jetzt bekannt, eine
|
||||
verworfene Nachricht sagt künftig laut warum, und ein neuer Prüf-Test
|
||||
vergleicht bei jedem Bau die Nachrichten-Liste mit dem Prüfer. Derselbe
|
||||
Fehler kann so nicht wiederkommen.
|
||||
Die Titelliste bleibt jetzt stehen.
|
||||
In 5.3.1 kam die Auswahl korrekt — und Minuten spaeter legte sich ein
|
||||
"keine Antwort nach 300 s" darueber. Der Grund: Ein aelterer, laengst
|
||||
ueberholter Lese-Vorgang meldete verspaetet seinen Abbruch. Jetzt gilt je
|
||||
Laufwerk nur der NEUESTE Lauf; ein abgeloester gibt das Laufwerk sofort
|
||||
frei und schweigt. Nebenbei bremsen sich damit nicht mehr zwei Lesevorgaenge
|
||||
auf derselben Disc gegenseitig aus.
|
||||
|
||||
Rippy sagt jetzt, WARUM keine Titel kamen.
|
||||
Findet MakeMKV das Laufwerk nicht (das passiert nach abgebrochenen
|
||||
Rips), stand dort bisher nur "keine Titel gefunden" — als läge es an der
|
||||
Disc. Jetzt steht die echte Ursache da, samt Abhilfe: Disc auswerfen und
|
||||
neu einlegen, Laufwerk ab- und anstecken, notfalls neu starten.
|
||||
Und wenn es wirklich zu lange dauert, steht da jetzt ein Satz statt einer
|
||||
Fehlermeldung: Bei einer beschaedigten Disc kann das Lesen ueber fuenf
|
||||
Minuten brauchen — Rippy beendet den Versuch dann und gibt das Laufwerk frei.
|
||||
|
||||
Aus 5.3.0: Serien landen unter Serien/<Titel>/Season NN, Filme bekommen
|
||||
ihre Extras im Unterordner "extras".
|
||||
Aus 5.3.1: Die Titel-Auswahl funktioniert (der Kern verwarf die Anfrage
|
||||
vorher still), und Rippy nennt die Ursache, wenn MakeMKV kein Laufwerk findet.
|
||||
Aus 5.3.0: Serien unter Serien/<Titel>/Season NN, Film-Extras im Unterordner.
|
||||
Aus 5.2.0: Auswahl, was gerippt wird — plus der Defekt-Waechter.
|
||||
|
||||
Unveraendert: Updates warten immer, bis kein Rip mehr laeuft.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "rippy",
|
||||
"version": "5.3.1",
|
||||
"version": "5.3.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rippy",
|
||||
"version": "5.3.1",
|
||||
"version": "5.3.2",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.8.9",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rippy",
|
||||
"productName": "Rippy",
|
||||
"version": "5.3.1",
|
||||
"version": "5.3.2",
|
||||
"description": "Rippy v5 — Disc-Ripping als eigenständiges Windows-Programm (KONZEPT-WINDOWS.md)",
|
||||
"main": "out/haupt/index.js",
|
||||
"author": "KrBrZ",
|
||||
|
||||
@@ -32,7 +32,7 @@ import { TmdbClient } from './metadaten/tmdb'
|
||||
import { zuordnen, type Zuordnung } from './metadaten/zuordnung'
|
||||
import { presetEmpfehlung } from '../gemeinsam/preset-empfehlung'
|
||||
import { Datenbank } from './speicher/db'
|
||||
import { titelInfoLesen } from './rip/makemkv'
|
||||
import { titelInfoLesen, ABGELOEST } from './rip/makemkv'
|
||||
import { titelVorauswahl } from './rip/titelwahl'
|
||||
import { finden } from './werkzeuge/katalog'
|
||||
|
||||
@@ -285,6 +285,15 @@ const laufwerkeBereit: Promise<void> = (async () => {
|
||||
// nicht schon beim Einlegen (der 30.08.-Fund: „das Erkennen dauert sehr
|
||||
// sehr lange"). Das Fenster bekommt sofort `laeuft: true`, damit es in der
|
||||
// Zeit nicht stumm dasteht (R2).
|
||||
// Je Laufwerk höchstens EIN Titel-Lauf, und nur der NEUESTE darf
|
||||
// antworten. Der Commander-Fund vom 01.09.2026: Die Titelliste kam
|
||||
// korrekt an — und 300 s später legte ein ÄLTERER, längst überholter Lauf
|
||||
// seinen Timeout darüber („keine Antwort nach 300 s"). Zwei makemkvcon
|
||||
// auf derselben Disc bremsen sich außerdem gegenseitig aus.
|
||||
const titelAbbrueche = new Map<string, AbortController>()
|
||||
let titelLaufZaehler = 0
|
||||
const titelLaufNummern = new Map<string, number>()
|
||||
|
||||
async function titelLesen(id: string): Promise<void> {
|
||||
const leer = { id, titel: [], einordnung: '', unsicher: false }
|
||||
if (ripVerwaltung.laeuft(id)) {
|
||||
@@ -305,9 +314,19 @@ async function titelLesen(id: string): Promise<void> {
|
||||
})
|
||||
return
|
||||
}
|
||||
// Einen noch laufenden Lauf ablösen: Er gibt das Laufwerk sofort frei,
|
||||
// und seine Antwort wird verworfen (nur die neueste Nummer zählt).
|
||||
titelAbbrueche.get(id)?.abort()
|
||||
const abbruch = new AbortController()
|
||||
titelAbbrueche.set(id, abbruch)
|
||||
const meineNummer = ++titelLaufZaehler
|
||||
titelLaufNummern.set(id, meineNummer)
|
||||
const nochAktuell = (): boolean => titelLaufNummern.get(id) === meineNummer
|
||||
|
||||
anFenster({ art: 'disc-titel', stand: { ...leer, laeuft: true, fehler: '' } })
|
||||
try {
|
||||
const info = await titelInfoLesen(programm, geraetepfad)
|
||||
const info = await titelInfoLesen(programm, geraetepfad, 300_000, abbruch.signal)
|
||||
if (!nochAktuell()) return
|
||||
const merkmale = discMerkmale.get(id)
|
||||
const wahl = titelVorauswahl(info.titel, {
|
||||
istSerie: merkmale?.istSerie ?? false,
|
||||
@@ -331,12 +350,22 @@ async function titelLesen(id: string): Promise<void> {
|
||||
},
|
||||
})
|
||||
} catch (fehler) {
|
||||
// R4: laut scheitern. „Konnte nicht nachsehen" ist etwas anderes als
|
||||
// „die Disc hat keine Titel" — beides zu vermischen erzeugt Unsinn.
|
||||
anFenster({
|
||||
art: 'disc-titel',
|
||||
stand: { ...leer, laeuft: false, fehler: `Titel lesen: ${String(fehler).slice(0, 200)}` },
|
||||
})
|
||||
// Ein ABGELÖSTER Lauf schweigt: Ihn hat ein neuerer ersetzt, das ist
|
||||
// Absicht und kein Fehlschlag. Genau hier lag der Commander-Fund —
|
||||
// der alte Lauf überschrieb sonst die längst gelieferte Titelliste.
|
||||
if (String(fehler).includes(ABGELOEST) || !nochAktuell()) return
|
||||
// R4: sonst laut scheitern. „Konnte nicht nachsehen" ist etwas anderes
|
||||
// als „die Disc hat keine Titel" — beides zu vermischen erzeugt Unsinn.
|
||||
const grund = String(fehler).includes('keine Antwort nach')
|
||||
? 'MakeMKV hat die Titel nach fünf Minuten nicht gelesen. Bei einer beschädigten Disc kann das passieren — Rippy hat den Versuch beendet und das Laufwerk freigegeben.'
|
||||
: `Titel lesen: ${String(fehler).slice(0, 200)}`
|
||||
anFenster({ art: 'disc-titel', stand: { ...leer, laeuft: false, fehler: grund } })
|
||||
} finally {
|
||||
// Nur aufräumen, wenn ICH noch der aktuelle Lauf bin.
|
||||
if (nochAktuell()) {
|
||||
titelAbbrueche.delete(id)
|
||||
titelLaufNummern.delete(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ export function buildRipArgs(geraetepfad: string, zielOrdner: string, titel: str
|
||||
* von einem echten Defekt zu unterscheiden. */
|
||||
export const HAENGT_AB = 12
|
||||
|
||||
/** Kennzeichnet einen Titel-Lauf, den ein NEUERER abgelöst hat. Der
|
||||
* Aufrufer schweigt dazu — abgelöst ist kein Fehler, sondern Absicht. */
|
||||
export const ABGELOEST = 'titel-lauf-abgeloest'
|
||||
|
||||
/** Auswertung der Rip-Ausgabe — pur, eine Zeile nach der anderen. */
|
||||
export class RipAuswertung {
|
||||
prozent = -1
|
||||
@@ -153,6 +157,7 @@ export function titelInfoLesen(
|
||||
programm: string,
|
||||
geraetepfad: string,
|
||||
timeoutMs = 300_000,
|
||||
abbruch?: AbortSignal,
|
||||
): Promise<DiscInfo> {
|
||||
return new Promise((fertig, scheitert) => {
|
||||
const kind = spawn(programm, buildInfoArgs(geraetepfad), {
|
||||
@@ -166,12 +171,24 @@ export function titelInfoLesen(
|
||||
kind.kill()
|
||||
scheitert(new Error(`makemkvcon info: keine Antwort nach ${Math.round(timeoutMs / 1000)} s`))
|
||||
}, timeoutMs)
|
||||
// Ein abgelöster Lauf lässt das Laufwerk sofort los — zwei makemkvcon
|
||||
// auf derselben Disc machen beide langsamer (Commander-Fund 01.09.2026:
|
||||
// Ein alter Lauf meldete 300 s später seinen Timeout und legte ihn über
|
||||
// die längst gelieferte Titelliste).
|
||||
const aufgeben = (): void => {
|
||||
clearTimeout(zeiger)
|
||||
kind.kill()
|
||||
scheitert(new Error(ABGELOEST))
|
||||
}
|
||||
abbruch?.addEventListener('abort', aufgeben, { once: true })
|
||||
kind.on('error', (fehler) => {
|
||||
clearTimeout(zeiger)
|
||||
abbruch?.removeEventListener('abort', aufgeben)
|
||||
scheitert(fehler)
|
||||
})
|
||||
kind.on('close', () => {
|
||||
clearTimeout(zeiger)
|
||||
abbruch?.removeEventListener('abort', aufgeben)
|
||||
const ausgabe = textVon(Buffer.concat(stuecke))
|
||||
const titel = parseTitelInfo(ausgabe)
|
||||
const streams = parseStreamInfo(ausgabe)
|
||||
|
||||
Reference in New Issue
Block a user