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>
421 lines
18 KiB
TypeScript
421 lines
18 KiB
TypeScript
// 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.',
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|