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,137 @@
|
||||
// Einstellungen (§ 6.7): Das Fenster liest und schreibt NUR über den Kern;
|
||||
// erlaubt sind genau die Schlüssel hier. Dazu die Pfad-Regeln: Ablage
|
||||
// (Fertiges — beim Commander der Server) und seit 5.5.0 der Arbeitsordner
|
||||
// (Roh-Dateien und Kompression — lokal, schnell; Vorgabe: unter der Ablage,
|
||||
// also wie bisher).
|
||||
import { join } from 'node:path'
|
||||
import { AUFBEWAHRUNG_SCHLUESSEL } from '../ablage/rohaufraeumen'
|
||||
import { einstellung, type KernKontext } from '../kontext'
|
||||
|
||||
export const ERLAUBTE_EINSTELLUNGEN: ReadonlySet<string> = new Set([
|
||||
'ablage',
|
||||
'arbeitsordner',
|
||||
'transcodeEnabled',
|
||||
'transcodePresetDvd',
|
||||
'transcodePresetBluray',
|
||||
'transcodePresetUhd',
|
||||
'audioSprachen',
|
||||
'untertitelSprachen',
|
||||
'mediaServer',
|
||||
'mediaServerUrl',
|
||||
'mediaServerApiKey',
|
||||
'tmdbApiKey',
|
||||
'omdbApiKey',
|
||||
'makemkvAppKey',
|
||||
'updateUrl',
|
||||
'setup.done',
|
||||
'autoAuswurf',
|
||||
'werkzeug.makemkv',
|
||||
'werkzeug.handbrake',
|
||||
'werkzeug.flac',
|
||||
AUFBEWAHRUNG_SCHLUESSEL,
|
||||
// 5.5.0
|
||||
'automatik',
|
||||
'automatikSekunden',
|
||||
'benachrichtigungen',
|
||||
'discordWebhook',
|
||||
'telegramToken',
|
||||
'telegramChatId',
|
||||
])
|
||||
|
||||
/** Die Schlüssel, die Rip und Kompression lesen (Pipeline/Ablage). Das
|
||||
* frühere „Allgemeine Preset" (transcodePreset) ist seit 5.5.0 raus —
|
||||
* Commander-Wunsch: je Disc-Typ reicht, ein unsichtbarer Übersteuerer
|
||||
* verwirrt nur. */
|
||||
const PIPELINE_EINSTELLUNGEN = [
|
||||
'transcodeEnabled',
|
||||
'transcodePresetDvd',
|
||||
'transcodePresetBluray',
|
||||
'transcodePresetUhd',
|
||||
'audioSprachen',
|
||||
'untertitelSprachen',
|
||||
'mediaServer',
|
||||
'mediaServerUrl',
|
||||
'mediaServerApiKey',
|
||||
] as const
|
||||
|
||||
export const AUTOMATIK_SEKUNDEN_VORGABE = 30
|
||||
|
||||
export class EinstellungsDienst {
|
||||
constructor(private readonly kontext: KernKontext) {}
|
||||
|
||||
lesen(schluessel: string, vorgabe = ''): string {
|
||||
return einstellung(this.kontext, schluessel, vorgabe)
|
||||
}
|
||||
|
||||
/** Wurzel der Ablage — Fertiges, Musik, ISO. */
|
||||
ablageOrdner(): string {
|
||||
const eingestellt = this.lesen('ablage')
|
||||
if (eingestellt.length > 0) return eingestellt
|
||||
return join(process.env['USERPROFILE'] ?? '.', 'Videos', 'Rippy')
|
||||
}
|
||||
|
||||
/** Ein eigener Arbeitsordner — '' wenn keiner gesetzt ist. */
|
||||
arbeitsordnerEigen(): string {
|
||||
return this.lesen('arbeitsordner').trim()
|
||||
}
|
||||
|
||||
/** Wo gearbeitet wird: der eigene Arbeitsordner, sonst die Ablage. */
|
||||
arbeitsordner(): string {
|
||||
const eigen = this.arbeitsordnerEigen()
|
||||
return eigen.length > 0 ? eigen : this.ablageOrdner()
|
||||
}
|
||||
|
||||
/** Wo die Roh-Ordner liegen. */
|
||||
rohWurzel(): string {
|
||||
return join(this.arbeitsordner(), 'roh')
|
||||
}
|
||||
|
||||
/** Was Pipeline und Ablage brauchen. */
|
||||
fuerPipeline(): Record<string, string> {
|
||||
const werte: Record<string, string> = {}
|
||||
for (const schluessel of PIPELINE_EINSTELLUNGEN) {
|
||||
const wert = this.kontext.db?.einstellungLesen(schluessel)
|
||||
if (wert !== null && wert !== undefined) werte[schluessel] = wert
|
||||
}
|
||||
return werte
|
||||
}
|
||||
|
||||
automatikAn(): boolean {
|
||||
return this.lesen('automatik', 'an') !== 'aus'
|
||||
}
|
||||
|
||||
automatikSekunden(): number {
|
||||
const n = Number(this.lesen('automatikSekunden', String(AUTOMATIK_SEKUNDEN_VORGABE)))
|
||||
return Number.isInteger(n) && n >= 5 && n <= 600 ? n : AUTOMATIK_SEKUNDEN_VORGABE
|
||||
}
|
||||
|
||||
/** Alle erlaubten Werte ans Fenster — AUCH ans Haupt (Update-Adresse, § 6.8). */
|
||||
senden(): void {
|
||||
const werte: Record<string, string> = { ablage: this.ablageOrdner() }
|
||||
for (const schluessel of ERLAUBTE_EINSTELLUNGEN) {
|
||||
const wert = this.kontext.db?.einstellungLesen(schluessel)
|
||||
if (wert !== null && wert !== undefined) werte[schluessel] = wert
|
||||
}
|
||||
this.kontext.anHauptUndFenster({ art: 'einstellungen', werte })
|
||||
}
|
||||
|
||||
/** Schreiben — nur erlaubte Schlüssel, nur mit Datenbank. Liefert, ob es klappte. */
|
||||
setzen(schluessel: string, wert: string): boolean {
|
||||
if (!ERLAUBTE_EINSTELLUNGEN.has(schluessel)) {
|
||||
this.kontext.fehler(`Unbekannter Einstellungs-Schlüssel: ${schluessel}`)
|
||||
return false
|
||||
}
|
||||
if (this.kontext.db === null) {
|
||||
this.kontext.fehler('Einstellung nicht gespeichert — die Datenbank ist nicht da.')
|
||||
return false
|
||||
}
|
||||
this.kontext.db.einstellungSchreiben(schluessel, wert)
|
||||
this.kontext.protokoll.schreiben(`Einstellung ${schluessel} gesetzt${istGeheim(schluessel) ? '' : `: ${wert.slice(0, 120)}`}`)
|
||||
this.senden()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function istGeheim(schluessel: string): boolean {
|
||||
return /key|token|webhook|passwort/i.test(schluessel)
|
||||
}
|
||||
Reference in New Issue
Block a user