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.
|
Die Titelliste bleibt jetzt stehen.
|
||||||
In 5.2.0 und 5.3.0 blieb der Dialog ewig bei "Rippy liest die Titel der
|
In 5.3.1 kam die Auswahl korrekt — und Minuten spaeter legte sich ein
|
||||||
Disc …" stehen. Der Grund war ein Fehler in Rippy selbst: Die Anfrage
|
"keine Antwort nach 300 s" darueber. Der Grund: Ein aelterer, laengst
|
||||||
"lies die Titel" war dem Kern unbekannt und wurde weggeworfen — still.
|
ueberholter Lese-Vorgang meldete verspaetet seinen Abbruch. Jetzt gilt je
|
||||||
Behoben, und zwar auf drei Ebenen: Die Anfrage ist jetzt bekannt, eine
|
Laufwerk nur der NEUESTE Lauf; ein abgeloester gibt das Laufwerk sofort
|
||||||
verworfene Nachricht sagt künftig laut warum, und ein neuer Prüf-Test
|
frei und schweigt. Nebenbei bremsen sich damit nicht mehr zwei Lesevorgaenge
|
||||||
vergleicht bei jedem Bau die Nachrichten-Liste mit dem Prüfer. Derselbe
|
auf derselben Disc gegenseitig aus.
|
||||||
Fehler kann so nicht wiederkommen.
|
|
||||||
|
|
||||||
Rippy sagt jetzt, WARUM keine Titel kamen.
|
Und wenn es wirklich zu lange dauert, steht da jetzt ein Satz statt einer
|
||||||
Findet MakeMKV das Laufwerk nicht (das passiert nach abgebrochenen
|
Fehlermeldung: Bei einer beschaedigten Disc kann das Lesen ueber fuenf
|
||||||
Rips), stand dort bisher nur "keine Titel gefunden" — als läge es an der
|
Minuten brauchen — Rippy beendet den Versuch dann und gibt das Laufwerk frei.
|
||||||
Disc. Jetzt steht die echte Ursache da, samt Abhilfe: Disc auswerfen und
|
|
||||||
neu einlegen, Laufwerk ab- und anstecken, notfalls neu starten.
|
|
||||||
|
|
||||||
Aus 5.3.0: Serien landen unter Serien/<Titel>/Season NN, Filme bekommen
|
Aus 5.3.1: Die Titel-Auswahl funktioniert (der Kern verwarf die Anfrage
|
||||||
ihre Extras im Unterordner "extras".
|
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.
|
Aus 5.2.0: Auswahl, was gerippt wird — plus der Defekt-Waechter.
|
||||||
|
|
||||||
Unveraendert: Updates warten immer, bis kein Rip mehr laeuft.
|
Unveraendert: Updates warten immer, bis kein Rip mehr laeuft.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "rippy",
|
"name": "rippy",
|
||||||
"version": "5.3.1",
|
"version": "5.3.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "rippy",
|
"name": "rippy",
|
||||||
"version": "5.3.1",
|
"version": "5.3.2",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-updater": "^6.8.9",
|
"electron-updater": "^6.8.9",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "rippy",
|
"name": "rippy",
|
||||||
"productName": "Rippy",
|
"productName": "Rippy",
|
||||||
"version": "5.3.1",
|
"version": "5.3.2",
|
||||||
"description": "Rippy v5 — Disc-Ripping als eigenständiges Windows-Programm (KONZEPT-WINDOWS.md)",
|
"description": "Rippy v5 — Disc-Ripping als eigenständiges Windows-Programm (KONZEPT-WINDOWS.md)",
|
||||||
"main": "out/haupt/index.js",
|
"main": "out/haupt/index.js",
|
||||||
"author": "KrBrZ",
|
"author": "KrBrZ",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { TmdbClient } from './metadaten/tmdb'
|
|||||||
import { zuordnen, type Zuordnung } from './metadaten/zuordnung'
|
import { zuordnen, type Zuordnung } from './metadaten/zuordnung'
|
||||||
import { presetEmpfehlung } from '../gemeinsam/preset-empfehlung'
|
import { presetEmpfehlung } from '../gemeinsam/preset-empfehlung'
|
||||||
import { Datenbank } from './speicher/db'
|
import { Datenbank } from './speicher/db'
|
||||||
import { titelInfoLesen } from './rip/makemkv'
|
import { titelInfoLesen, ABGELOEST } from './rip/makemkv'
|
||||||
import { titelVorauswahl } from './rip/titelwahl'
|
import { titelVorauswahl } from './rip/titelwahl'
|
||||||
import { finden } from './werkzeuge/katalog'
|
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
|
// nicht schon beim Einlegen (der 30.08.-Fund: „das Erkennen dauert sehr
|
||||||
// sehr lange"). Das Fenster bekommt sofort `laeuft: true`, damit es in der
|
// sehr lange"). Das Fenster bekommt sofort `laeuft: true`, damit es in der
|
||||||
// Zeit nicht stumm dasteht (R2).
|
// 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> {
|
async function titelLesen(id: string): Promise<void> {
|
||||||
const leer = { id, titel: [], einordnung: '', unsicher: false }
|
const leer = { id, titel: [], einordnung: '', unsicher: false }
|
||||||
if (ripVerwaltung.laeuft(id)) {
|
if (ripVerwaltung.laeuft(id)) {
|
||||||
@@ -305,9 +314,19 @@ async function titelLesen(id: string): Promise<void> {
|
|||||||
})
|
})
|
||||||
return
|
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: '' } })
|
anFenster({ art: 'disc-titel', stand: { ...leer, laeuft: true, fehler: '' } })
|
||||||
try {
|
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 merkmale = discMerkmale.get(id)
|
||||||
const wahl = titelVorauswahl(info.titel, {
|
const wahl = titelVorauswahl(info.titel, {
|
||||||
istSerie: merkmale?.istSerie ?? false,
|
istSerie: merkmale?.istSerie ?? false,
|
||||||
@@ -331,12 +350,22 @@ async function titelLesen(id: string): Promise<void> {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (fehler) {
|
} catch (fehler) {
|
||||||
// R4: laut scheitern. „Konnte nicht nachsehen" ist etwas anderes als
|
// Ein ABGELÖSTER Lauf schweigt: Ihn hat ein neuerer ersetzt, das ist
|
||||||
// „die Disc hat keine Titel" — beides zu vermischen erzeugt Unsinn.
|
// Absicht und kein Fehlschlag. Genau hier lag der Commander-Fund —
|
||||||
anFenster({
|
// der alte Lauf überschrieb sonst die längst gelieferte Titelliste.
|
||||||
art: 'disc-titel',
|
if (String(fehler).includes(ABGELOEST) || !nochAktuell()) return
|
||||||
stand: { ...leer, laeuft: false, fehler: `Titel lesen: ${String(fehler).slice(0, 200)}` },
|
// 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. */
|
* von einem echten Defekt zu unterscheiden. */
|
||||||
export const HAENGT_AB = 12
|
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. */
|
/** Auswertung der Rip-Ausgabe — pur, eine Zeile nach der anderen. */
|
||||||
export class RipAuswertung {
|
export class RipAuswertung {
|
||||||
prozent = -1
|
prozent = -1
|
||||||
@@ -153,6 +157,7 @@ export function titelInfoLesen(
|
|||||||
programm: string,
|
programm: string,
|
||||||
geraetepfad: string,
|
geraetepfad: string,
|
||||||
timeoutMs = 300_000,
|
timeoutMs = 300_000,
|
||||||
|
abbruch?: AbortSignal,
|
||||||
): Promise<DiscInfo> {
|
): Promise<DiscInfo> {
|
||||||
return new Promise((fertig, scheitert) => {
|
return new Promise((fertig, scheitert) => {
|
||||||
const kind = spawn(programm, buildInfoArgs(geraetepfad), {
|
const kind = spawn(programm, buildInfoArgs(geraetepfad), {
|
||||||
@@ -166,12 +171,24 @@ export function titelInfoLesen(
|
|||||||
kind.kill()
|
kind.kill()
|
||||||
scheitert(new Error(`makemkvcon info: keine Antwort nach ${Math.round(timeoutMs / 1000)} s`))
|
scheitert(new Error(`makemkvcon info: keine Antwort nach ${Math.round(timeoutMs / 1000)} s`))
|
||||||
}, timeoutMs)
|
}, 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) => {
|
kind.on('error', (fehler) => {
|
||||||
clearTimeout(zeiger)
|
clearTimeout(zeiger)
|
||||||
|
abbruch?.removeEventListener('abort', aufgeben)
|
||||||
scheitert(fehler)
|
scheitert(fehler)
|
||||||
})
|
})
|
||||||
kind.on('close', () => {
|
kind.on('close', () => {
|
||||||
clearTimeout(zeiger)
|
clearTimeout(zeiger)
|
||||||
|
abbruch?.removeEventListener('abort', aufgeben)
|
||||||
const ausgabe = textVon(Buffer.concat(stuecke))
|
const ausgabe = textVon(Buffer.concat(stuecke))
|
||||||
const titel = parseTitelInfo(ausgabe)
|
const titel = parseTitelInfo(ausgabe)
|
||||||
const streams = parseStreamInfo(ausgabe)
|
const streams = parseStreamInfo(ausgabe)
|
||||||
|
|||||||
Reference in New Issue
Block a user