feat(v5): 5.5.0 — Kern und Oberfläche aufgeteilt, Übersicht und Einstellungen neu, Erst-Einrichtung mit Poster-Kulisse
Commander-Wünsche vom 02.09.2026 vor der Freigabe von 5.4.0 (die deshalb nie veröffentlicht wurde): Kern: kern/index.ts ist nur noch Bootstrap und Router; Dienste in kern/dienste/ (Einstellungen, Werkzeuge, Laufwerke, Erkennung, Vorgänge, Speicher, Benachrichtigung, Automatik), Kontext in kern/kontext.ts, Protokoll-Datei in gemeinsam/protokoll.ts, Platz-Rechnung in gemeinsam/platz.ts. Oberfläche: fenster/App.tsx ist nur noch der Rahmen; bausteine.tsx, dialoge/, uebersicht/ (LaufwerkKachel, KompressionsKarte, SpeicherKarte, VerlaufKarte), einstellungen/ (Karten je Funktion, SprachAuswahl), einrichtung/ (ErstEinrichtung, PosterHintergrund). Neu: Bibliothek weg, dafür Verlauf; Speicher-und-Bilanz-Karte (statfs); LED-Fußleiste; MakeMKV-Kopf in die Einstellungen; Sprach-Chips mit Dropdown statt Allgemeinem Preset; Karten Automatik/Benachrichtigung/Allgemein/Update; Durchsuchen bei Werkzeugen; Arbeitsordner lokal (Encode dort, dann in die Ablage); Platz-Prüfung vor dem Rip (Dialog und Kern); Automatik mit Countdown; Serien-Merker über Discs; Rettungs-Abbild mit Nullen nur nach gesehenen Lesefehlern (kern/rip/rettung.ts, Phase rettet, iso:-Quelle); Warteschlange pausieren; Discord/Telegram-Benachrichtigung; Protokoll-Datei je Tag; Fensterlage 80 % mittig, gemerkt; Erst-Einrichtung in acht Schritten vor dem Poster-Grid. Beweise: 413 Tests (neu: speicher, benachrichtigung, automatik, rettung, fensterlage, protokoll, kleinkram-550), drei Typprüfungen, Klick-Beweis mit 29 Schritten inklusive Erst-Einrichtung (zweiter Start mit RIPPY_START_BEREICH=einrichtung), Bilder beweise/klick/01…09. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6678c81592
commit
3dc6798c79
@@ -0,0 +1,420 @@
|
||||
// Vorgänge (5.4.0): jeder Rip als Datensatz, die Kompressions-Warteschlange,
|
||||
// das Roh-Aufräumen, Wiederholen ab Kompression, Wiederaufnahme nach dem
|
||||
// Neustart — und seit 5.5.0: Pause, Statistik, Serien-Merker und die
|
||||
// Benachrichtigung, wenn eine Kompression zu Ende ist.
|
||||
import { existsSync, rmSync } from 'node:fs'
|
||||
import type { KompressionStand, Statistik, VorgaengeStand, VorgangEintrag } from '../../gemeinsam/nachrichten'
|
||||
import { komprimierenUndAblegen, type AblageErgebnis, type AblageUmgebung } from '../ablauf/ablegen'
|
||||
import { auftragAusJson, auftragZuJson, type KompressionsAuftrag } from '../ablauf/auftrag'
|
||||
import type { VorgangBeginn } from '../ablauf/pipeline'
|
||||
import { KompressionsWarteschlange } from '../ablauf/warteschlange'
|
||||
import {
|
||||
AUFBEWAHRUNG_SCHLUESSEL,
|
||||
aufraeumen,
|
||||
faelligAm,
|
||||
fremdeOrdner,
|
||||
istUnterRohWurzel,
|
||||
ordnerGroesse,
|
||||
regelLesen,
|
||||
regelText,
|
||||
} from '../ablage/rohaufraeumen'
|
||||
import { presetEmpfehlung } from '../../gemeinsam/preset-empfehlung'
|
||||
import type { KernKontext } from '../kontext'
|
||||
import { TmdbClient } from '../metadaten/tmdb'
|
||||
import type { Vorgang } from '../speicher/db'
|
||||
import type { BenachrichtigungsDienst } from './benachrichtigung'
|
||||
import type { EinstellungsDienst } from './einstellungen'
|
||||
import type { ErkennungDienst } from './erkennung'
|
||||
import type { WerkzeugDienst } from './werkzeuge'
|
||||
|
||||
/** Statistik aus den Vorgängen (pur). */
|
||||
export function statistikAus(liste: readonly Pick<Vorgang, 'phase' | 'rohBytes' | 'fertigBytes' | 'begonnen' | 'beendet'>[]): Statistik {
|
||||
let fertige = 0
|
||||
let rohBytes = 0
|
||||
let fertigBytes = 0
|
||||
let dauerS = 0
|
||||
for (const v of liste) {
|
||||
rohBytes += v.rohBytes
|
||||
if (v.phase !== 'fertig') continue
|
||||
fertige += 1
|
||||
fertigBytes += v.fertigBytes
|
||||
if (v.beendet !== null) {
|
||||
const d = (new Date(v.beendet).getTime() - new Date(v.begonnen).getTime()) / 1000
|
||||
if (Number.isFinite(d) && d > 0) dauerS += d
|
||||
}
|
||||
}
|
||||
return { discs: liste.length, fertige, rohBytes, fertigBytes, dauerS: Math.round(dauerS) }
|
||||
}
|
||||
|
||||
export function dateienAusJson(text: string, melden?: (grund: string) => void): string[] {
|
||||
const liste: string[] = []
|
||||
try {
|
||||
const roh: unknown = JSON.parse(text)
|
||||
if (Array.isArray(roh)) liste.push(...roh.map(String))
|
||||
} catch (fehler) {
|
||||
// R4: Ein unlesbarer Eintrag wird gesagt — und zählt als „keine
|
||||
// fertigen Dateien bekannt", womit das Aufräumen ihn nie anfasst.
|
||||
melden?.(`fertige Dateien nicht lesbar: ${String(fehler)}`)
|
||||
}
|
||||
return liste
|
||||
}
|
||||
|
||||
export class VorgangDienst {
|
||||
readonly warteschlange: KompressionsWarteschlange
|
||||
/** Nur bei einer PHASEN-Änderung in die Datenbank — nicht bei jedem Prozent-Tick. */
|
||||
private letztePhase = new Map<string, string>()
|
||||
|
||||
constructor(
|
||||
private readonly kontext: KernKontext,
|
||||
private readonly einstellungen: EinstellungsDienst,
|
||||
private readonly werkzeuge: WerkzeugDienst,
|
||||
private readonly erkennung: ErkennungDienst,
|
||||
private readonly benachrichtigung: BenachrichtigungsDienst,
|
||||
/** Nach Änderungen an Vorgängen auch den Speicher neu messen. */
|
||||
private readonly speicherSenden: () => void,
|
||||
) {
|
||||
this.warteschlange = new KompressionsWarteschlange(
|
||||
(auftrag, melden, abbruch) => komprimierenUndAblegen(auftrag, this.ablageUmgebung(), melden, abbruch),
|
||||
(stand) => this.standGeaendert(stand.laeuft),
|
||||
(auftrag, ergebnis) => this.ergebnis(auftrag, ergebnis),
|
||||
)
|
||||
}
|
||||
|
||||
private get db() {
|
||||
return this.kontext.db
|
||||
}
|
||||
|
||||
ablageUmgebung(): AblageUmgebung {
|
||||
return {
|
||||
handbrake: () => this.werkzeuge.handbrake(),
|
||||
ablage: () => this.einstellungen.ablageOrdner(),
|
||||
arbeitsordner: () => this.einstellungen.arbeitsordnerEigen(),
|
||||
einstellungen: () => this.einstellungen.fuerPipeline(),
|
||||
presetEmpfehlung: (typ) => presetEmpfehlung(typ, this.werkzeuge.auskunft.backends, this.werkzeuge.auskunft.presets),
|
||||
// Staffel-Laufzeiten von TMDb — Grundlage der Episoden-Zuordnung (§ 4.2).
|
||||
episodenLaufzeiten: async (tmdbId, staffel) => {
|
||||
const tmdb = new TmdbClient(this.einstellungen.lesen('tmdbApiKey'))
|
||||
if (!tmdb.verfuegbar) return []
|
||||
const antwort = await tmdb.tvStaffel(tmdbId, staffel)
|
||||
const paare: Array<[number, number]> = []
|
||||
for (const ep of antwort?.episodes ?? []) {
|
||||
if (typeof ep.runtime === 'number' && ep.runtime > 0) paare.push([ep.episode_number, ep.runtime * 60])
|
||||
}
|
||||
return paare
|
||||
},
|
||||
bibliothekEintragen: (eintrag) => this.bibliothekEintragen(eintrag),
|
||||
}
|
||||
}
|
||||
|
||||
bibliothekEintragen(eintrag: Parameters<NonNullable<AblageUmgebung['bibliothekEintragen']>>[0]): void {
|
||||
try {
|
||||
this.db?.bibliothekEintragen(eintrag)
|
||||
} catch (fehler) {
|
||||
// R4: Ein Bibliotheks-Fehler kippt keinen gelungenen Rip, wird aber gesagt.
|
||||
this.kontext.fehler(`Bibliothek: ${String(fehler)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private standGeaendert(laeuft: KompressionStand | null): void {
|
||||
if (laeuft !== null && this.letztePhase.get(laeuft.vorgangId) !== laeuft.phase) {
|
||||
this.letztePhase.set(laeuft.vorgangId, laeuft.phase)
|
||||
this.db?.vorgangAktualisieren(laeuft.vorgangId, { phase: laeuft.phase === 'legt-ab' ? 'legt-ab' : 'komprimiert' })
|
||||
}
|
||||
this.senden()
|
||||
}
|
||||
|
||||
private async ergebnis(auftrag: KompressionsAuftrag, ergebnis: AblageErgebnis): Promise<void> {
|
||||
this.letztePhase.delete(auftrag.vorgangId)
|
||||
const jetzt = new Date().toISOString()
|
||||
this.db?.vorgangAktualisieren(auftrag.vorgangId, {
|
||||
phase: ergebnis.status,
|
||||
beendet: jetzt,
|
||||
zielOrdner: ergebnis.zielOrdner,
|
||||
fertigBytes: ergebnis.fertigBytes,
|
||||
komprimiert: ergebnis.komprimiert,
|
||||
preset: ergebnis.preset,
|
||||
fehler: ergebnis.status === 'fertig' ? '' : ergebnis.text,
|
||||
fertigeDateien: JSON.stringify(ergebnis.dateien),
|
||||
})
|
||||
const vorgang = this.db?.vorgangLesen(auftrag.vorgangId)
|
||||
const titel = vorgang?.titel ?? auftrag.vorgangId
|
||||
const text = ergebnis.text + (ergebnis.hinweise.length > 0 ? ` · ${ergebnis.hinweise.join(' · ')}` : '')
|
||||
this.kontext.protokoll.schreiben(`Kompression „${titel}": ${ergebnis.status} — ${text}`)
|
||||
this.kontext.anHauptUndFenster({
|
||||
art: 'kompression-ergebnis',
|
||||
vorgangId: auftrag.vorgangId,
|
||||
titel,
|
||||
ok: ergebnis.status === 'fertig',
|
||||
text,
|
||||
})
|
||||
// Serien-Merker (5.5.0): Mit welcher Folge geht die nächste Disc weiter?
|
||||
const tv = auftrag.zuordnung?.meta.typ === 'tv' ? auftrag.zuordnung.meta : null
|
||||
if (ergebnis.status === 'fertig' && tv !== null && tv.id !== null && ergebnis.folgenVergeben.length > 0) {
|
||||
const letzte = Math.max(...ergebnis.folgenVergeben)
|
||||
this.erkennung.serienMerkerSetzen(tv.id, auftrag.zuordnung?.titel ?? titel, auftrag.staffel ?? 1, letzte + 1)
|
||||
}
|
||||
if (ergebnis.status !== 'abgebrochen') {
|
||||
void this.benachrichtigung.senden(
|
||||
ergebnis.status === 'fertig' ? 'Rippy: fertig' : 'Rippy: Kompression fehlgeschlagen',
|
||||
`${titel}: ${ergebnis.text.slice(0, 300)}`,
|
||||
)
|
||||
}
|
||||
// Regel „sofort": direkt nach der gelungenen Kompression aufräumen.
|
||||
if (ergebnis.status === 'fertig') this.rohAufraeumenLauf()
|
||||
else this.senden()
|
||||
}
|
||||
|
||||
// ── Pipeline-Haken ──────────────────────────────────────────────────────
|
||||
vorgangBeginnt(v: VorgangBeginn): void {
|
||||
const vorgang: Vorgang = {
|
||||
id: v.id,
|
||||
laufwerk: v.laufwerk,
|
||||
typ: v.typ,
|
||||
titel: v.titel,
|
||||
jahr: v.jahr,
|
||||
phase: 'rippt',
|
||||
begonnen: v.begonnen,
|
||||
beendet: null,
|
||||
rohOrdner: v.rohOrdner,
|
||||
rohBytes: 0,
|
||||
rohGeloeschtAm: null,
|
||||
zielOrdner: '',
|
||||
fertigBytes: 0,
|
||||
komprimiert: false,
|
||||
preset: '',
|
||||
fehler: '',
|
||||
auftrag: '{}',
|
||||
lesefehler: false,
|
||||
fertigeDateien: '[]',
|
||||
}
|
||||
this.db?.vorgangAnlegen(vorgang)
|
||||
this.kontext.protokoll.schreiben(`Vorgang ${v.id} beginnt: „${v.titel}" auf ${v.laufwerk} → ${v.rohOrdner}`)
|
||||
this.senden()
|
||||
}
|
||||
|
||||
vorgangEinreihen(auftrag: KompressionsAuftrag, titel: string): number {
|
||||
this.db?.vorgangAktualisieren(auftrag.vorgangId, {
|
||||
phase: 'wartet',
|
||||
titel,
|
||||
jahr: auftrag.zuordnung?.jahr ?? null,
|
||||
rohBytes: auftrag.dateien.reduce((s, d) => s + d.groesseBytes, 0),
|
||||
lesefehler: auftrag.lesefehler,
|
||||
auftrag: auftragZuJson(auftrag),
|
||||
})
|
||||
const platz = this.warteschlange.einreihen(auftrag, titel)
|
||||
this.kontext.protokoll.schreiben(`Vorgang ${auftrag.vorgangId}: ${auftrag.dateien.length} Roh-Dateien, Kompression eingereiht (Platz ${platz})`)
|
||||
this.senden()
|
||||
return platz
|
||||
}
|
||||
|
||||
vorgangEndet(id: string, phase: 'fehler' | 'abgebrochen', text: string): void {
|
||||
this.db?.vorgangAktualisieren(id, { phase, beendet: new Date().toISOString(), fehler: text })
|
||||
this.kontext.protokoll.schreiben(`Vorgang ${id} ${phase}: ${text}`)
|
||||
this.senden()
|
||||
}
|
||||
|
||||
// ── Stand fürs Fenster ──────────────────────────────────────────────────
|
||||
stand(): VorgaengeStand {
|
||||
const regel = regelLesen(this.einstellungen.lesen(AUFBEWAHRUNG_SCHLUESSEL, ''))
|
||||
const liste = this.db?.vorgangListe() ?? []
|
||||
let rohGesamtBytes = 0
|
||||
const vorgaenge: VorgangEintrag[] = liste.map((v) => {
|
||||
const rohDa = v.rohGeloeschtAm === null && existsSync(v.rohOrdner)
|
||||
const rohBytes = rohDa ? ordnerGroesse(v.rohOrdner) : 0
|
||||
rohGesamtBytes += rohBytes
|
||||
const auftrag = auftragAusJson(v.auftrag)
|
||||
const ruhig = v.phase === 'fertig' || v.phase === 'fehler' || v.phase === 'abgebrochen'
|
||||
return {
|
||||
id: v.id,
|
||||
laufwerk: v.laufwerk,
|
||||
typ: v.typ,
|
||||
titel: v.titel,
|
||||
jahr: v.jahr,
|
||||
phase: v.phase,
|
||||
begonnen: v.begonnen,
|
||||
beendet: v.beendet,
|
||||
rohOrdner: v.rohOrdner,
|
||||
rohBytes,
|
||||
rohGeloescht: !rohDa,
|
||||
zielOrdner: v.zielOrdner,
|
||||
fertigBytes: v.fertigBytes,
|
||||
komprimiert: v.komprimiert,
|
||||
preset: v.preset,
|
||||
fehler: v.fehler,
|
||||
titelZahl: auftrag?.dateien.length ?? 0,
|
||||
neuKomprimierbar: rohDa && ruhig && auftrag !== null && auftrag.dateien.length > 0 && !this.warteschlange.kennt(v.id),
|
||||
faelligAm: v.phase === 'fertig' && v.komprimiert && rohDa ? faelligAm(v.beendet, regel) : '',
|
||||
}
|
||||
})
|
||||
const bekannte = new Set(liste.map((v) => v.rohOrdner))
|
||||
const fremde = fremdeOrdner(this.einstellungen.rohWurzel(), bekannte)
|
||||
for (const f of fremde) rohGesamtBytes += f.bytes
|
||||
return {
|
||||
vorgaenge,
|
||||
rohGesamtBytes,
|
||||
fremde,
|
||||
regel: regelText(regel),
|
||||
kompression: { ...this.warteschlange.stand(), pausiert: this.warteschlange.istPausiert() },
|
||||
statistik: statistikAus(liste),
|
||||
}
|
||||
}
|
||||
|
||||
senden(): void {
|
||||
try {
|
||||
this.kontext.anHauptUndFenster({ art: 'vorgaenge', stand: this.stand() })
|
||||
} catch (fehler) {
|
||||
this.kontext.fehler(`Vorgänge: ${String(fehler)}`)
|
||||
}
|
||||
this.speicherSenden()
|
||||
}
|
||||
|
||||
// ── Pause (5.5.0) ───────────────────────────────────────────────────────
|
||||
pause(an: boolean): void {
|
||||
this.warteschlange.pausieren(an)
|
||||
this.kontext.hinweis(an ? 'Kompression angehalten — der laufende Film wird fertig, danach wartet die Schlange.' : 'Kompression läuft weiter.')
|
||||
this.senden()
|
||||
}
|
||||
|
||||
// ── Roh-Aufräumen ───────────────────────────────────────────────────────
|
||||
rohAufraeumenLauf(): void {
|
||||
if (this.db === null) return
|
||||
try {
|
||||
const regel = regelLesen(this.einstellungen.lesen(AUFBEWAHRUNG_SCHLUESSEL, ''))
|
||||
const kandidaten = this.db.vorgangListe().map((v) => ({
|
||||
id: v.id,
|
||||
phase: v.phase,
|
||||
komprimiert: v.komprimiert,
|
||||
rohOrdner: v.rohOrdner,
|
||||
rohGeloeschtAm: v.rohGeloeschtAm,
|
||||
beendet: v.beendet,
|
||||
fertigeDateien: dateienAusJson(v.fertigeDateien, (g) => this.kontext.protokoll.schreiben(`${v.id}: ${g}`)),
|
||||
}))
|
||||
const ergebnis = aufraeumen(kandidaten, regel, new Date())
|
||||
const jetzt = new Date().toISOString()
|
||||
for (const id of ergebnis.geloescht) {
|
||||
this.db.vorgangAktualisieren(id, { rohGeloeschtAm: jetzt, rohBytes: this.db.vorgangLesen(id)?.rohBytes ?? 0 })
|
||||
this.kontext.hinweis(`Roh-Dateien von „${this.db.vorgangLesen(id)?.titel ?? id}" gelöscht (${regelText(regel)})`)
|
||||
}
|
||||
for (const f of ergebnis.fehler) this.kontext.fehler(`Roh-Ordner von ${f.id} nicht gelöscht: ${f.grund}`)
|
||||
} catch (fehler) {
|
||||
this.kontext.fehler(`Roh-Aufräumen: ${String(fehler)}`)
|
||||
}
|
||||
this.senden()
|
||||
}
|
||||
|
||||
rohLoeschen(vorgangId: string): void {
|
||||
const v = this.db?.vorgangLesen(vorgangId)
|
||||
if (v === null || v === undefined) {
|
||||
this.kontext.fehler(`Vorgang ${vorgangId}: unbekannt.`)
|
||||
return
|
||||
}
|
||||
if (v.phase === 'rippt' || v.phase === 'wartet' || v.phase === 'komprimiert' || v.phase === 'legt-ab' || this.warteschlange.kennt(vorgangId)) {
|
||||
this.kontext.fehler(`„${v.titel}" läuft noch (${v.phase}) — erst abbrechen, dann löschen.`)
|
||||
return
|
||||
}
|
||||
// Ein alter Roh-Ordner darf auch unter einer FRÜHEREN Roh-Wurzel liegen
|
||||
// (Arbeitsordner gewechselt) — geprüft wird nur, dass er ein Ordner
|
||||
// direkt unter irgendeinem roh\ ist.
|
||||
if (!/[\\/]roh[\\/][^\\/]+$/i.test(v.rohOrdner)) {
|
||||
this.kontext.fehler(`Roh-Ordner von „${v.titel}" liegt nicht unter einem roh\\-Ordner — nicht angefasst.`)
|
||||
return
|
||||
}
|
||||
try {
|
||||
rmSync(v.rohOrdner, { recursive: true, force: true })
|
||||
this.db?.vorgangAktualisieren(vorgangId, { rohGeloeschtAm: new Date().toISOString() })
|
||||
this.kontext.hinweis(`Roh-Dateien von „${v.titel}" gelöscht (von Hand).`)
|
||||
} catch (fehler) {
|
||||
this.kontext.fehler(`Roh-Ordner von „${v.titel}" nicht gelöscht: ${String(fehler)}`)
|
||||
}
|
||||
this.senden()
|
||||
}
|
||||
|
||||
fremdenOrdnerLoeschen(ordner: string): void {
|
||||
if (!istUnterRohWurzel(this.einstellungen.rohWurzel(), ordner)) {
|
||||
this.kontext.fehler(`„${ordner}" liegt nicht direkt unter ${this.einstellungen.rohWurzel()} — nicht angefasst.`)
|
||||
return
|
||||
}
|
||||
const bekannt = (this.db?.vorgangListe() ?? []).some((v) => v.rohOrdner.toLowerCase() === ordner.toLowerCase())
|
||||
if (bekannt) {
|
||||
this.kontext.fehler(`„${ordner}" gehört zu einem Vorgang — dort löschen.`)
|
||||
return
|
||||
}
|
||||
try {
|
||||
rmSync(ordner, { recursive: true, force: true })
|
||||
this.kontext.hinweis(`Ordner „${ordner}" gelöscht (von Hand).`)
|
||||
} catch (fehler) {
|
||||
this.kontext.fehler(`„${ordner}" nicht gelöscht: ${String(fehler)}`)
|
||||
}
|
||||
this.senden()
|
||||
}
|
||||
|
||||
/** „Wiederholen ab Kompression": Die Roh-Dateien liegen genau dafür da. */
|
||||
neuKomprimieren(vorgangId: string, preset: string): void {
|
||||
const v = this.db?.vorgangLesen(vorgangId)
|
||||
if (v === null || v === undefined) {
|
||||
this.kontext.fehler(`Vorgang ${vorgangId}: unbekannt.`)
|
||||
return
|
||||
}
|
||||
if (this.warteschlange.kennt(vorgangId)) {
|
||||
this.kontext.fehler(`„${v.titel}" ist schon in der Warteschlange.`)
|
||||
return
|
||||
}
|
||||
const auftrag = auftragAusJson(v.auftrag)
|
||||
if (auftrag === null) {
|
||||
this.kontext.fehler(`„${v.titel}": Der Auftrag ist nicht mehr lesbar — nur ein neuer Rip hilft.`)
|
||||
return
|
||||
}
|
||||
const vorhandene = auftrag.dateien.filter((d) => existsSync(d.pfad))
|
||||
if (vorhandene.length === 0) {
|
||||
this.kontext.fehler(`„${v.titel}": Die Roh-Dateien sind nicht mehr da (${v.rohOrdner}).`)
|
||||
return
|
||||
}
|
||||
if (vorhandene.length < auftrag.dateien.length) {
|
||||
this.kontext.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: KompressionsAuftrag = {
|
||||
...auftrag,
|
||||
dateien: vorhandene,
|
||||
preset,
|
||||
// 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) : [],
|
||||
}
|
||||
this.db?.vorgangAktualisieren(vorgangId, { phase: 'wartet', fehler: '', auftrag: auftragZuJson(neu) })
|
||||
const platz = this.warteschlange.einreihen(neu, v.titel)
|
||||
this.kontext.hinweis(`„${v.titel}": Kompression eingereiht${platz > 1 ? ` (Platz ${platz})` : ''}${preset.length > 0 ? ` mit Preset „${preset}"` : ''}.`)
|
||||
this.senden()
|
||||
}
|
||||
|
||||
/** Nach einem Neustart: Was wartete noch? Was war mitten im Rip? */
|
||||
wiederaufnehmen(): void {
|
||||
if (this.db === null) return
|
||||
const jetzt = new Date().toISOString()
|
||||
for (const v of this.db.vorgangListe()) {
|
||||
if (v.phase === 'rippt') {
|
||||
this.db.vorgangAktualisieren(v.id, {
|
||||
phase: 'abgebrochen',
|
||||
beendet: jetzt,
|
||||
fehler: 'Rippy wurde beendet, bevor der Rip fertig war — die Roh-Dateien können unvollständig sein.',
|
||||
})
|
||||
this.kontext.protokoll.schreiben(`Vorgang ${v.id}: war beim letzten Beenden mitten im Rip — als abgebrochen vermerkt`)
|
||||
continue
|
||||
}
|
||||
if (v.phase === 'wartet' || v.phase === 'komprimiert' || v.phase === 'legt-ab') {
|
||||
const auftrag = auftragAusJson(v.auftrag)
|
||||
if (auftrag !== null && existsSync(v.rohOrdner) && auftrag.dateien.some((d) => existsSync(d.pfad))) {
|
||||
this.warteschlange.einreihen({ ...auftrag, dateien: auftrag.dateien.filter((d) => existsSync(d.pfad)) }, v.titel)
|
||||
this.kontext.hinweis(`„${v.titel}": Kompression nach dem Neustart wieder aufgenommen.`)
|
||||
} else {
|
||||
this.db.vorgangAktualisieren(v.id, {
|
||||
phase: 'fehler',
|
||||
beendet: jetzt,
|
||||
fehler: 'Nach dem Neustart fehlen die Roh-Dateien — die Kompression kann nicht wieder aufgenommen werden.',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user