diff --git a/rippy-windows/src/kern/ablauf/ablegen.ts b/rippy-windows/src/kern/ablauf/ablegen.ts index 5640cba..fea2021 100644 --- a/rippy-windows/src/kern/ablauf/ablegen.ts +++ b/rippy-windows/src/kern/ablauf/ablegen.ts @@ -252,6 +252,20 @@ export async function komprimierenUndAblegen( if (datei.rolle === 'folge') folgenSekunden.push(datei.dauerS) } + // ── Wiederholen: die alte Fassung weicht der neuen — JETZT erst, da alle + // neuen Dateien da sind. Vorher hätte ein gescheiterter Lauf die + // erste Fassung gekostet; danach stünde sie bei Serien der + // Umbenennung im Weg („existiert schon"). ──────────────────────── + for (const alt of auftrag.ersetzt ?? []) { + if (fertigeDateien.includes(alt) || !existsSync(alt)) continue + try { + unlinkSync(alt) + hinweise.push(`Alte Fassung ersetzt: ${basename(alt)}`) + } catch (fehler) { + hinweise.push(`Alte Fassung nicht gelöscht (${basename(alt)}): ${String(fehler)}`) + } + } + // ── Serien-Staffelablage (§ 4.2) ──────────────────────────────────── // Von Hand gesetzte Folgen („ab Folge 3") schlagen die Laufzeit- // Zuordnung; TMDb darf dann nur noch warnen. Ohne Vorgabe wird diff --git a/rippy-windows/src/kern/ablauf/auftrag.ts b/rippy-windows/src/kern/ablauf/auftrag.ts index 874a77f..6020dab 100644 --- a/rippy-windows/src/kern/ablauf/auftrag.ts +++ b/rippy-windows/src/kern/ablauf/auftrag.ts @@ -45,6 +45,11 @@ export interface KompressionsAuftrag { * Fassung ersetzt die alte, statt einen zweiten Ordner anzulegen. * '' = Zielordner wird frisch bestimmt. */ zielOrdner: string + /** Beim Wiederholen: die fertigen Dateien des ERSTEN Durchlaufs. Sie + * werden gelöscht, sobald die neuen fertig sind — vorher nicht, damit + * ein gescheiterter zweiter Lauf die erste Fassung nicht kostet. Bei + * Serien stünden sie sonst der Umbenennung im Weg („existiert schon"). */ + ersetzt?: string[] } export function auftragZuJson(auftrag: KompressionsAuftrag): string { @@ -94,6 +99,7 @@ export function auftragAusJson(text: string): KompressionsAuftrag | null { begonnen: typeof a.begonnen === 'string' ? a.begonnen : '', preset: typeof a.preset === 'string' ? a.preset : '', zielOrdner: typeof a.zielOrdner === 'string' ? a.zielOrdner : '', + ersetzt: Array.isArray(a.ersetzt) ? a.ersetzt.map(String) : [], } } diff --git a/rippy-windows/src/kern/index.ts b/rippy-windows/src/kern/index.ts index 3f864ee..9f91a27 100644 --- a/rippy-windows/src/kern/index.ts +++ b/rippy-windows/src/kern/index.ts @@ -734,12 +734,15 @@ function vorgangNeuKomprimieren(vorgangId: string, preset: string): void { if (vorhandene.length < auftrag.dateien.length) { hinweis(`„${v.titel}": ${auftrag.dateien.length - vorhandene.length} Roh-Datei(en) fehlen — der Rest wird komprimiert.`) } + const alteFassung = v.phase === 'fertig' && v.zielOrdner.length > 0 && existsSync(v.zielOrdner) const neu = { ...auftrag, dateien: vorhandene, preset, - // Die neue Fassung ersetzt die alte im selben Ordner. - zielOrdner: v.phase === 'fertig' && v.zielOrdner.length > 0 && existsSync(v.zielOrdner) ? v.zielOrdner : '', + // Die neue Fassung ersetzt die alte im selben Ordner — die alten + // Dateien fallen erst, wenn die neuen fertig sind (ablegen.ts). + zielOrdner: alteFassung ? v.zielOrdner : '', + ersetzt: alteFassung ? dateienAusJson(v.fertigeDateien) : [], } db?.vorgangAktualisieren(vorgangId, { phase: 'wartet', fehler: '', auftrag: auftragZuJson(neu) }) const platz = warteschlange.einreihen(neu, v.titel) diff --git a/rippy-windows/test/ablegen.test.ts b/rippy-windows/test/ablegen.test.ts index 890d131..c104445 100644 --- a/rippy-windows/test/ablegen.test.ts +++ b/rippy-windows/test/ablegen.test.ts @@ -4,7 +4,7 @@ // gewollt UND HandBrake da, sonst VERSCHIEBEN; ein Kompressions-Fehler // lässt den verlustfreien Rip STEHEN und sagt wo er liegt; Sprachen je // Datei erreichen HandBrake; Folgen von Hand schlagen die Laufzeit-Raterei. -import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs' +import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' @@ -299,3 +299,51 @@ describe('komprimierenUndAblegen', () => { expect(prozente).toContain(87) }) }) + +describe('Wiederholen ersetzt die alte Fassung', () => { + it('Serie: die alten S01E03/E04 fallen erst nach dem Encode, dann greift die Umbenennung', async () => { + const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge']) + const ziel = join(ablage, 'Serien', 'Spartacus', 'Season 01') + mkdirSync(ziel, { recursive: true }) + const alt3 = join(ziel, 'Spartacus S01E03.mkv') + const alt4 = join(ziel, 'Spartacus S01E04.mkv') + writeFileSync(alt3, 'alt') + writeFileSync(alt4, 'alt') + const ergebnis = await komprimierenUndAblegen( + auftrag({ + rohOrdner: roh, + dateien, + zuordnung: zuordnung('tv', 'Spartacus', 2010), + staffel: 1, + ersteFolge: 3, + zielOrdner: ziel, + ersetzt: [alt3, alt4], + }), + umgebung(ablage), + () => {}, + KEIN_ABBRUCH, + ) + expect(ergebnis.status).toBe('fertig') + expect(readdirSync(ziel).filter((n) => n.endsWith('.mkv')).sort()).toEqual(['Spartacus S01E03.mkv', 'Spartacus S01E04.mkv']) + expect(readFileSync(alt3, 'utf8')).toBe('encoded') + expect(ergebnis.hinweise.join(' ')).toContain('Alte Fassung ersetzt') + expect(ergebnis.hinweise.join(' ')).not.toContain('existiert schon') + }) + + it('scheitert der zweite Lauf, bleibt die alte Fassung stehen', async () => { + hb.ergebnis = 'error' + const { ablage, roh, dateien } = aufbau(['title_t00.mkv']) + const ziel = join(ablage, 'Filme', 'Akira (1988)') + mkdirSync(ziel, { recursive: true }) + const alt = join(ziel, 'title_t00.mkv') + writeFileSync(alt, 'alt') + const ergebnis = await komprimierenUndAblegen( + auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('movie', 'Akira', 1988), zielOrdner: ziel, ersetzt: [alt] }), + umgebung(ablage), + () => {}, + KEIN_ABBRUCH, + ) + expect(ergebnis.status).toBe('fehler') + expect(readFileSync(alt, 'utf8')).toBe('alt') + }) +}) diff --git a/rippy-windows/test/auftrag.test.ts b/rippy-windows/test/auftrag.test.ts index 4992edc..62f907a 100644 --- a/rippy-windows/test/auftrag.test.ts +++ b/rippy-windows/test/auftrag.test.ts @@ -99,6 +99,7 @@ describe('Auftrag als JSON — und wieder zurück', () => { begonnen: '2026-08-30T12:00:00.000Z', preset: '', zielOrdner: '', + ersetzt: [], } it('kommt unverändert zurück', () => { expect(auftragAusJson(auftragZuJson(a))).toEqual(a)