WAS: Der Rip endet, sobald die Roh-Dateien liegen; die Kompression laeuft danach in einer Warteschlange (eine zur Zeit), das Laufwerk ist frei fuer die naechste Disc. Jeder Rip ist jetzt ein Datensatz (Tabelle vorgaenge, Schema 4): Roh-Ordner, Zielordner, Phase, Auftrag als JSON. Darauf bauen: Roh-Aufraeumen nach Regel (Vorgabe 7 Tage, nur bei gelungener Kompression und nur wenn jede fertige Datei da und nicht leer ist), Wiederholen ab Kompression, Aufraeumen halber Dateien nach Abbruch/Fehler, Wiederaufnahme nach Neustart. Unkomprimierte Rips (4K, kein HandBrake) werden in den Zielordner VERSCHOBEN statt unter roh\ liegen zu bleiben. Sprachen je Titel reisen vom Info-Lauf (SINFO) bis in HandBrake; fremder Ton schaltet den deutschen Untertitel vor (--subtitle-default=1). Vor der Kompression prueft ein HandBrake-Scan die Roh-Datei. MakeMKVs Auswahlregel wird gesetzt, wenn keine steht, damit alle Sprachen in die Roh-Datei kommen. Staffel und erste Folge von Hand schlagen die Laufzeit-Raterei. Durchgehender Fortschritt, nach Groesse gewichtet. Erkennung v2: leere TMDb-Eintraege (Stubs) zaehlen nicht, Laufzeit wird nach dem Info-Lauf gegen TMDb gehalten, Titel-Struktur als Fingerabdruck, bdmt_deu als zweite Titel-Variante. TMDb liefert Logo, Tagline, Bewertung, FSK und Besetzung in derselben Anfrage. Laufwerks-Gesundheit aus dem Windows-Ereignisprotokoll (wevtutil): Disc oder Hardware. WARUM: Uebergabe-Punkte 1-5, 8-12 des SAVEPOINT vom 01.09.2026 — vom Commander gutgeheissen (7 Tage / Untertitel vorgewaehlt / entkoppelte Kompression waren seine Wahl). GEMESSEN (Regel D): HandBrake 1.11 Sprach-Schalter an einer ffmpeg- Testdatei (jpn/deu/deu/eng + deu/eng), HandBrake --scan --json Format, TMDb movie/149 + tv/46296 mit append_to_response, wevtutil /f:xml an diesem PC (338x cdrom 7, 8x cdrom 11 in 30 Tagen), makemkvcon --minlength, MakeMKV-Auswahlregel laut forum.makemkv.com t=4386. 348 Tests gruen (vorher 261), Typpruefung und Bau gruen. Oberflaeche folgt im naechsten Schritt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
149 lines
6.6 KiB
TypeScript
149 lines
6.6 KiB
TypeScript
// Tests des Roh-Aufräumens (kern/ablage/rohaufraeumen.ts, 5.4.0): die
|
|
// Regel, die Frist, die drei Sicherungen — und dass fremde Ordner nur
|
|
// gezeigt, nie von selbst gelöscht werden.
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import {
|
|
aufraeumen,
|
|
faelligAm,
|
|
fremdeOrdner,
|
|
istUnterRohWurzel,
|
|
loeschHindernis,
|
|
ordnerGroesse,
|
|
regelLesen,
|
|
regelText,
|
|
type AufraeumKandidat,
|
|
type AufraeumZugriff,
|
|
} from '../src/kern/ablage/rohaufraeumen'
|
|
|
|
let wegwerf: string[] = []
|
|
afterEach(() => {
|
|
for (const o of wegwerf.splice(0)) rmSync(o, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('regelLesen', () => {
|
|
it('kennt behalten, sofort und Tage — und die Vorgabe 7', () => {
|
|
expect(regelLesen('behalten')).toEqual({ art: 'behalten' })
|
|
expect(regelLesen('sofort')).toEqual({ art: 'sofort' })
|
|
expect(regelLesen('30')).toEqual({ art: 'tage', tage: 30 })
|
|
expect(regelLesen(null)).toEqual({ art: 'tage', tage: 7 })
|
|
expect(regelLesen('')).toEqual({ art: 'tage', tage: 7 })
|
|
expect(regelLesen('quatsch')).toEqual({ art: 'tage', tage: 7 })
|
|
expect(regelLesen('-3')).toEqual({ art: 'tage', tage: 7 })
|
|
expect(regelText(regelLesen('7'))).toContain('7 Tage')
|
|
})
|
|
})
|
|
|
|
describe('faelligAm', () => {
|
|
it('Tage nach dem Ende; sofort = am Ende; behalten = nie', () => {
|
|
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'tage', tage: 7 })).toBe('2026-09-08T20:00:00.000Z')
|
|
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'sofort' })).toBe('2026-09-01T20:00:00.000Z')
|
|
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'behalten' })).toBe('')
|
|
expect(faelligAm(null, { art: 'sofort' })).toBe('')
|
|
expect(faelligAm('kaputt', { art: 'sofort' })).toBe('')
|
|
})
|
|
})
|
|
|
|
const fertigeDateien = ['E:\\Rippy\\Filme\\Akira (1988)\\title_t00.mkv']
|
|
const kandidat = (teil: Partial<AufraeumKandidat> = {}): AufraeumKandidat => ({
|
|
id: 'bluray-1',
|
|
phase: 'fertig',
|
|
komprimiert: true,
|
|
rohOrdner: 'E:\\Rippy\\roh\\bluray-1',
|
|
rohGeloeschtAm: null,
|
|
beendet: '2026-09-01T20:00:00.000Z',
|
|
fertigeDateien,
|
|
...teil,
|
|
})
|
|
const zugriff = (überschreiben: Partial<AufraeumZugriff> = {}): AufraeumZugriff & { geloescht: string[] } => {
|
|
const geloescht: string[] = []
|
|
return {
|
|
existiert: () => true,
|
|
groesse: () => 1000,
|
|
ordnerLoeschen: (p) => {
|
|
geloescht.push(p)
|
|
},
|
|
geloescht,
|
|
...überschreiben,
|
|
}
|
|
}
|
|
const regel7 = { art: 'tage' as const, tage: 7 }
|
|
const spaeter = new Date('2026-09-10T00:00:00Z')
|
|
const frueher = new Date('2026-09-03T00:00:00Z')
|
|
|
|
describe('loeschHindernis — die drei Sicherungen', () => {
|
|
it('fertig, komprimiert, Ergebnis da, Frist um: löschbar', () => {
|
|
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff())).toBe('')
|
|
})
|
|
it('vor der Frist: nicht löschbar, mit Datum', () => {
|
|
expect(loeschHindernis(kandidat(), regel7, frueher, zugriff())).toContain('Frist läuft bis 2026-09-08')
|
|
})
|
|
it('nicht fertig / nicht komprimiert / schon gelöscht / Regel behalten', () => {
|
|
expect(loeschHindernis(kandidat({ phase: 'fehler' }), regel7, spaeter, zugriff())).toContain('nicht fertig')
|
|
expect(loeschHindernis(kandidat({ komprimiert: false }), regel7, spaeter, zugriff())).toContain('nicht komprimiert')
|
|
expect(loeschHindernis(kandidat({ rohGeloeschtAm: 'x' }), regel7, spaeter, zugriff())).toBe('schon gelöscht')
|
|
expect(loeschHindernis(kandidat(), { art: 'behalten' }, spaeter, zugriff())).toBe('Regel: behalten')
|
|
})
|
|
it('die fertige Datei fehlt oder ist leer: NIE löschen', () => {
|
|
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff({ existiert: (p) => p.endsWith('bluray-1') }))).toContain('fertige Datei fehlt')
|
|
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff({ groesse: () => 0 }))).toContain('ist leer')
|
|
expect(loeschHindernis(kandidat({ fertigeDateien: [] }), regel7, spaeter, zugriff())).toContain('keine fertige Datei')
|
|
})
|
|
it('sofort: gleich nach dem Ende löschbar', () => {
|
|
expect(loeschHindernis(kandidat(), { art: 'sofort' }, new Date('2026-09-01T20:00:01Z'), zugriff())).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('aufraeumen', () => {
|
|
it('löscht nur die Fälligen und sagt, warum der Rest blieb', () => {
|
|
const z = zugriff()
|
|
const ergebnis = aufraeumen(
|
|
[kandidat(), kandidat({ id: 'jung', beendet: '2026-09-09T00:00:00.000Z' }), kandidat({ id: 'roh', komprimiert: false })],
|
|
regel7,
|
|
spaeter,
|
|
z,
|
|
)
|
|
expect(ergebnis.geloescht).toEqual(['bluray-1'])
|
|
expect(z.geloescht).toEqual(['E:\\Rippy\\roh\\bluray-1'])
|
|
expect(ergebnis.uebersprungen.map((u) => u.id)).toEqual(['jung', 'roh'])
|
|
expect(ergebnis.fehler).toEqual([])
|
|
})
|
|
it('ein Ordner, der sich nicht löschen lässt, ist ein lauter Fehler, kein stilles Nichts', () => {
|
|
const ergebnis = aufraeumen([kandidat()], regel7, spaeter, zugriff({ ordnerLoeschen: () => { throw new Error('EBUSY') } }))
|
|
expect(ergebnis.geloescht).toEqual([])
|
|
expect(ergebnis.fehler[0].grund).toContain('EBUSY')
|
|
})
|
|
})
|
|
|
|
describe('Belegung und fremde Ordner — am echten Dateisystem', () => {
|
|
it('ordnerGroesse zählt rekursiv; fremdeOrdner kennt die Vorgänge und lässt sie aus', () => {
|
|
const wurzel = mkdtempSync(join(tmpdir(), 'rippy-roh-'))
|
|
wegwerf.push(wurzel)
|
|
const roh = join(wurzel, 'roh')
|
|
mkdirSync(join(roh, 'bluray-1', 'tief'), { recursive: true })
|
|
mkdirSync(join(roh, 'alt-vor-540'), { recursive: true })
|
|
writeFileSync(join(roh, 'bluray-1', 'a.mkv'), 'x'.repeat(100))
|
|
writeFileSync(join(roh, 'bluray-1', 'tief', 'b.mkv'), 'x'.repeat(50))
|
|
writeFileSync(join(roh, 'alt-vor-540', 'c.mkv'), 'x'.repeat(7))
|
|
writeFileSync(join(roh, 'lose.txt'), 'x')
|
|
expect(ordnerGroesse(join(roh, 'bluray-1'))).toBe(150)
|
|
expect(ordnerGroesse(join(roh, 'gibt-es-nicht'))).toBe(0)
|
|
const fremde = fremdeOrdner(roh, new Set([join(roh, 'bluray-1')]))
|
|
expect(fremde).toEqual([{ ordner: join(roh, 'alt-vor-540'), bytes: 7 }])
|
|
expect(fremdeOrdner(join(wurzel, 'nix'), new Set())).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('istUnterRohWurzel — nur Ordner DIREKT unter roh\\', () => {
|
|
it('erlaubt genau eine Ebene', () => {
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh\\bluray-1')).toBe(true)
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh\\', 'e:\\rippy\\roh\\bluray-1\\')).toBe(true)
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh')).toBe(false)
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh\\a\\b')).toBe(false)
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\Filme\\Akira')).toBe(false)
|
|
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\rohling\\x')).toBe(false)
|
|
})
|
|
})
|