Bugfix and Docs

This commit is contained in:
2026-03-10 13:12:57 +00:00
parent 3516ff8486
commit ac4d77dddf
75 changed files with 3511 additions and 5142 deletions

View File

@@ -1,305 +1,116 @@
# Backend-Services
Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, die jeweils eine klar abgegrenzte Verantwortlichkeit haben.
Das Backend ist in Services aufgeteilt, die von Express-Routen orchestriert werden.
---
## pipelineService.js
## `pipelineService.js`
**Der Kern von Ripster** orchestriert den gesamten Ripping-Workflow.
Zentrale Workflow-Orchestrierung.
### Zuständigkeiten
Aufgaben:
- Verwaltung des Pipeline-Zustands als State Machine
- Koordination zwischen allen externen Tools
- Generierung von Encode-Plänen
- Fehlerbehandlung und Recovery
- Pipeline-State-Machine + Persistenz (`pipeline_state`)
- Disc-Analyse/Rip/Review/Encode
- Queue-Management (Jobs + `script|chain|wait` Einträge)
- Retry/Re-Encode/Restart-Flows
- WebSocket-Broadcasts für State/Progress/Queue
### Haupt-Methoden
Wichtige Methoden:
| Methode | Beschreibung |
|---------|-------------|
| `analyzeDisc()` | Legt Job an und öffnet Metadaten-Auswahl |
| `selectMetadata({...})` | Setzt Metadaten/Playlist und triggert Auto-Start |
| `startPreparedJob(jobId)` | Startet vorbereiteten Job (oder Queue) |
| `confirmEncodeReview(jobId, options)` | Bestätigt Review inkl. Track/Skript-Auswahl |
| `cancel(jobId)` | Bricht laufenden Job ab oder entfernt Queue-Eintrag |
| `retry(jobId)` | Startet fehlgeschlagenen/abgebrochenen Job neu |
| `reencodeFromRaw(jobId)` | Encodiert aus vorhandenem RAW neu |
| `restartReviewFromRaw(jobId)` | Berechnet Review aus RAW neu |
| `restartEncodeWithLastSettings(jobId)` | Neustart mit letzter bestätigter Auswahl |
| `resumeReadyToEncodeJob(jobId)` | Lädt READY_TO_ENCODE nach Neustart in die Session |
### Zustandsübergänge
<div class="pipeline-diagram">
```mermaid
flowchart LR
START(( )) --> IDLE
IDLE -->|analyzeDisc()| META[METADATA\nSELECTION]
META -->|selectMetadata()| RTS[READY_TO\nSTART]
RTS -->|Auto-Start/Queue| RIP[RIPPING]
RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK]
RIP -->|MKV erstellt| MIC[MEDIAINFO\nCHECK]
MIC -->|Playlist offen| WUD[WAITING_FOR\nUSER_DECISION]
WUD -->|selectMetadata(selectedPlaylist)| MIC
MIC -->|Tracks analysiert| RTE[READY_TO\nENCODE]
RTE -->|confirmEncodeReview() + startPreparedJob()| ENC[ENCODING]
ENC -->|Pre-Encode → HandBrake → Post-Encode fertig| FIN([FINISHED])
ENC -->|Abbruch| CAN([CANCELLED])
ENC -->|Fehler| ERR([ERROR])
RIP -->|Fehler| ERR
RIP -->|Abbruch| CAN
ERR -->|retry() / cancel()| IDLE
CAN -->|retry() / analyzeDisc()| IDLE
FIN -->|cancel / neue Disc| IDLE
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32
style CAN fill:#fff3e0,stroke:#fb8c00,color:#e65100
style ERR fill:#ffebee,stroke:#ef5350,color:#c62828
style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
style MIC fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
```
</div>
- `analyzeDisc()`
- `selectMetadata()`
- `startPreparedJob()`
- `confirmEncodeReview()`
- `cancel()`
- `retry()`
- `reencodeFromRaw()`
- `restartReviewFromRaw()`
- `restartEncodeWithLastSettings()`
- `resumeReadyToEncodeJob()`
- `enqueueNonJobEntry()`, `reorderQueue()`, `removeQueueEntry()`
---
## diskDetectionService.js
## `diskDetectionService.js`
Überwacht das Disc-Laufwerk auf Disc-Einleger- und Auswurf-Ereignisse.
Pollt Laufwerk(e) und emittiert:
### Modi
- `discInserted`
- `discRemoved`
- `error`
| Modus | Beschreibung |
|------|-------------|
| `auto` | Erkennt verfügbare Laufwerke automatisch |
| `explicit` | Überwacht ein bestimmtes Gerät (z.B. `/dev/sr0`) |
Zusatz:
### Polling
Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 4000ms) und emittiert Events:
```js
// Ereignisse
emit('discInserted', { path: '/dev/sr0', mediaProfile: 'bluray', ... })
emit('discRemoved', { path: '/dev/sr0' })
```
### Media-Profil-Erkennung
Das erkannte Gerät enthält ein `mediaProfile`-Feld (`"bluray"`, `"dvd"`, `"other"` oder `null`). Die Erkennung nutzt eine Heuristik aus drei Quellen (absteigend nach Priorität):
1. Explizit gesetztes `media_profile` aus den Settings
2. Disc-Label und Laufwerks-Modell (Regex gegen bekannte Begriffe)
3. Dateisystemtyp: `udf` → bevorzugt DVD, kombiniert mit Modell; `iso9660/cdfs` → DVD oder CD
- Modus `auto` oder `explicit`
- heuristische `mediaProfile`-Erkennung (`bluray`/`dvd`/`other`)
- `rescanAndEmit()` für manuellen Trigger
---
## processRunner.js
## `settingsService.js`
Verwaltet externe CLI-Prozesse.
Settings-Layer mit Validation/Serialisierung.
### Features
Features:
- **Streaming**: stdout/stderr werden zeilenweise gelesen
- **Progress-Callbacks**: Ermöglicht Echtzeit-Fortschrittsanzeige
- **Graceful Shutdown**: SIGINT → Warte-Timeout → SIGKILL
- **Prozess-Registry**: Verfolgt aktive Prozesse für sauberes Beenden
### Nutzung
```js
const result = await runProcess(
'HandBrakeCLI',
['--input', rawFile, '--output', outputFile, '--preset', preset],
{
onStderr: (line) => parseHandBrakeProgress(line),
onStdout: (line) => logger.debug(line)
}
);
```
- `getCategorizedSettings()` für UI-Form
- `setSettingValue()` / `setSettingsBulk()`
- profilspezifische Auflösung (`resolveEffectiveToolSettings`)
- CLI-Config-Building für MakeMKV/HandBrake/MediaInfo
- HandBrake-Preset-Liste via `HandBrakeCLI -z`
- MakeMKV-Registration-Command aus `makemkv_registration_key`
---
## websocketService.js
## `historyService.js`
WebSocket-Server für Echtzeit-Client-Kommunikation.
Historie + Dateioperationen.
### Betrieb
Features:
- Läuft auf Pfad `/ws` des Express-Servers
- Hält eine Registry aller verbundenen Clients
- Ermöglicht Broadcast an alle Clients oder gezieltes Senden
### API
```js
broadcast('PIPELINE_STATE_CHANGED', { state, activeJobId });
broadcast('PIPELINE_PROGRESS', { state, progress, eta, statusText });
broadcast('PIPELINE_QUEUE_CHANGED', queueSnapshot);
```
- Job-Liste/Detail inkl. Log-Tail
- Orphan-RAW-Erkennung und Import
- OMDb-Nachzuweisung
- Dateilöschung (`raw|movie|both`)
- Job-Löschung (`none|raw|movie|both`)
---
## omdbService.js
## `cronService.js`
Integration mit der [OMDb API](https://www.omdbapi.com/).
Integriertes Cron-System ohne externe Parser-Library.
### Methoden
Features:
| Methode | Beschreibung |
|---------|-------------|
| `searchByTitle(title, type)` | Suche nach Titel (movie/series) |
| `fetchById(imdbId)` | Vollständige Metadaten per IMDb-ID |
### Zurückgegebene Daten
```json
{
"imdbId": "tt1375666",
"title": "Inception",
"year": "2010",
"type": "movie",
"poster": "https://...",
"plot": "...",
"director": "Christopher Nolan"
}
```
- 5-Feld-Cron-Parser + `nextRun`-Berechnung
- Quellen: `script` oder `chain`
- Laufzeitlogs (`cron_run_logs`)
- manuelles Triggern
- WebSocket-Events: `CRON_JOBS_UPDATED`, `CRON_JOB_UPDATED`
---
## settingsService.js
## Weitere Services
Verwaltet alle Anwendungseinstellungen.
### Features
- **Schema-getriebene Validierung**: Jede Einstellung hat Typ, Grenzen und Pflichtfeld-Flag
- **Kategorisierung**: Einstellungen sind in Kategorien gruppiert (Pfade, Tools, Metadaten, …)
- **Persistenz**: Werte in SQLite, Schema ebenfalls in SQLite
- **Profil-Auflösung**: `resolveEffectiveToolSettings(settingsMap, mediaProfile)` wählt automatisch die profil-spezifischen Werte (`_bluray`/`_dvd`) und fällt auf den globalen Wert zurück
### Profil-Auflösung
```js
// Löst alle profil-spezifischen Keys auf und gibt einen effektiven Einstellungs-Map zurück
const effective = await settingsService.getEffectiveSettingsMap('bluray');
// effective.handbrake_preset → Wert aus handbrake_preset_bluray (falls gesetzt)
// effective.raw_dir → Wert aus raw_dir_bluray (kein Fallback bei Pfaden)
```
### Einstellungs-Kategorien
| Kategorie | Ausgewählte Schlüssel |
|-----------|----------------------|
| `Pfade` | `raw_dir[_bluray/_dvd/_other]`, `movie_dir[_bluray/_dvd/_other]`, `log_dir` |
| `Laufwerk` | `drive_mode`, `drive_device`, `disc_poll_interval_ms`, `makemkv_source_index` |
| `Monitoring` | `hardware_monitoring_enabled`, `hardware_monitoring_interval_ms` |
| `Tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command`, `pipeline_max_parallel_jobs` |
| `Tools Blu-ray` | `handbrake_preset_bluray`, `makemkv_rip_mode_bluray`, … |
| `Tools DVD` | `handbrake_preset_dvd`, `makemkv_rip_mode_dvd`, … |
| `Metadaten` | `omdb_api_key`, `omdb_default_type` |
| `Benachrichtigungen` | `pushover_enabled`, `pushover_token`, `pushover_notify_*` |
- `scriptService.js` (CRUD + Test + Wrapper-Ausführung)
- `scriptChainService.js` (CRUD + Step-Execution)
- `userPresetService.js` (HandBrake User-Presets)
- `hardwareMonitorService.js` (CPU/RAM/GPU/Storage)
- `websocketService.js` (Client-Registry + Broadcast)
- `notificationService.js` (PushOver)
- `logger.js` (rotierende Datei-Logs)
---
## userPresetService.js
## Bootstrapping (`src/index.js`)
Verwaltet benannte HandBrake-Preset-Sammlungen pro Medientyp.
Beim Start:
### Methoden
| Methode | Beschreibung |
|---------|-------------|
| `listPresets(mediaType?)` | Alle Presets; optional nach Medientyp filtern (`bluray`/`dvd`/`other`/`all`) |
| `getPresetById(id)` | Einzelnes Preset |
| `createPreset(payload)` | Neues Preset anlegen |
| `updatePreset(id, payload)` | Preset aktualisieren |
| `deletePreset(id)` | Preset löschen |
!!! info "mediaType = 'all'"
Presets mit `mediaType = 'all'` erscheinen bei Filterung nach jedem Medientyp.
---
## historyService.js
Datenbankoperationen für Job-Historie.
### Hauptoperationen
| Operation | Beschreibung |
|-----------|-------------|
| `listJobs(filters)` | Jobs nach Status/Titel filtern |
| `getJob(id)` | Job-Details mit Logs abrufen |
| `findOrphanRawFolders()` | Nicht-getrackte Raw-Ordner finden |
| `importOrphanRaw(path)` | Orphan-Ordner als Job importieren |
| `assignOmdb(id, omdbData)` | OMDb-Metadaten nachträglich zuweisen |
| `deleteJob(id, deleteFiles)` | Job und optional Dateien löschen |
---
## cronService.js
Eingebautes Cron-System ohne externe Abhängigkeiten.
### Features
- **Eigener Expression-Parser**: Unterstützt alle Standard-5-Felder-Cron-Ausdrücke (`* /n`, Bereiche, Listen)
- **Skripte und Ketten**: Cron-Jobs können ein Skript (`sourceType: "script"`) oder eine Kette (`sourceType: "chain"`) ausführen
- **Log-Rotation**: Max. 50 Logs pro Job, Ausgabe auf 100.000 Zeichen begrenzt
- **PushOver-Integration**: Optionale Benachrichtigung nach jeder Ausführung
- **Manuelle Auslösung**: `triggerJobManually(id)` läuft unabhängig vom Zeitplan
### Methoden
| Methode | Beschreibung |
|---------|-------------|
| `listJobs()` | Alle Cron-Jobs |
| `createJob(payload)` | Neuen Job anlegen |
| `updateJob(id, payload)` | Job aktualisieren |
| `deleteJob(id)` | Job löschen |
| `getJobLogs(id, limit)` | Ausführungs-Logs |
| `triggerJobManually(id)` | Sofortige Ausführung |
| `validateExpression(expr)` | Ausdruck validieren |
| `getNextRunTime(expr)` | Nächsten Ausführungszeitpunkt berechnen |
---
## notificationService.js
PushOver-Push-Benachrichtigungen.
```js
await notify({
title: 'Ripster: Job abgeschlossen',
message: 'Inception (2010) wurde erfolgreich encodiert'
});
```
---
## logger.js
Strukturiertes Logging mit täglicher Log-Rotation.
### Log-Level
| Level | Verwendung |
|-------|-----------|
| `debug` | Detaillierte Entwicklungs-Informationen |
| `info` | Normale Betriebsereignisse |
| `warn` | Warnungen, die Aufmerksamkeit benötigen |
| `error` | Fehler, die den Betrieb beeinträchtigen |
### Log-Dateien
```
logs/
├── ripster-2024-01-15.log ← Tages-Log
└── jobs/
└── job-42-handbrake.log ← Prozess-spezifische Logs
```
1. DB init/migrate
2. Pipeline-Init
3. Cron-Init
4. Express-Routes + Error-Handler
5. WebSocket-Server auf `/ws`
6. Hardware-Monitoring-Init
7. Disk-Detection-Start

View File

@@ -1,273 +1,112 @@
# Datenbank
Ripster verwendet **SQLite3** als Datenbank. Die Datenbankdatei liegt unter `backend/data/ripster.db`.
Ripster verwendet SQLite (`backend/data/ripster.db`).
---
## Schema-Übersicht
## Tabellen
```sql
settings_schema -- Einstellungs-Definitionen
settings_values -- Benutzer-Werte
jobs -- Rip-Job-Datensätze
pipeline_state -- Aktueller Pipeline-Zustand (Singleton)
scripts -- Shell-Skripte für Pre-/Post-Encode-Ausführung
script_chains -- Geordnete Ketten aus mehreren Skripten
script_chain_steps -- Einzelschritte einer Skript-Kette
user_presets -- Benannte HandBrake-Preset-Sammlungen pro Medientyp
cron_jobs -- Zeitgesteuerte Aufgaben (eigener Cron-Parser)
cron_run_logs -- Ausführungs-Protokolle für Cron-Jobs
```text
settings_schema
settings_values
jobs
pipeline_state
scripts
script_chains
script_chain_steps
user_presets
cron_jobs
cron_run_logs
```
---
## Tabelle: jobs
## `jobs`
Die wichtigste Tabelle speichert alle Ripping-Jobs.
Speichert Pipeline-Lifecycle und Artefakte pro Job.
```sql
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
status TEXT NOT NULL, -- Aktueller Status
title TEXT, -- Filmtitel (von OMDb)
imdb_id TEXT, -- IMDb-ID
omdb_year TEXT, -- Erscheinungsjahr
omdb_type TEXT, -- movie/series
omdb_poster TEXT, -- Poster-URL
raw_path TEXT, -- Pfad zur Raw-MKV
output_path TEXT, -- Pfad zur Ausgabedatei
playlist TEXT, -- Gewählte Blu-ray Playlist
rip_successful INTEGER NOT NULL DEFAULT 0, -- 1 wenn Rip abgeschlossen
makemkv_output TEXT, -- MakeMKV-Ausgabe (JSON)
mediainfo_output TEXT, -- MediaInfo-Ausgabe (JSON)
encode_plan TEXT, -- Encode-Plan (JSON)
handbrake_log TEXT, -- HandBrake Log-Pfad
error_message TEXT, -- Fehlermeldung bei ERROR
error_details TEXT -- Detaillierte Fehler-Infos
);
```
Zentrale Felder:
!!! info "rip_successful"
Das Feld `rip_successful` wird auf `1` gesetzt, sobald MakeMKV den Rip-Schritt erfolgreich abgeschlossen hat unabhängig davon, ob danach ein Encode-Fehler auftritt. Damit lässt sich in der History unterscheiden, ob eine Raw-Datei vorhanden ist.
### Job-Status-Werte
| Status | Beschreibung |
|--------|-------------|
| `ANALYZING` | MakeMKV analysiert die Disc |
| `METADATA_SELECTION` | Wartet auf Benutzer-Metadaten-Auswahl |
| `READY_TO_START` | Bereit zum Starten |
| `RIPPING` | MakeMKV rippt die Disc |
| `MEDIAINFO_CHECK` | MediaInfo analysiert die Raw-Datei |
| `READY_TO_ENCODE` | Wartet auf Encode-Bestätigung |
| `ENCODING` | HandBrake encodiert |
| `FINISHED` | Erfolgreich abgeschlossen |
| `ERROR` | Fehler aufgetreten |
- Metadaten: `title`, `year`, `imdb_id`, `poster_url`, `omdb_json`, `selected_from_omdb`
- Laufzeit: `start_time`, `end_time`, `status`, `last_state`
- Pfade: `raw_path`, `output_path`, `encode_input_path`
- Tool-Ausgaben: `makemkv_info_json`, `handbrake_info_json`, `mediainfo_info_json`, `encode_plan_json`
- Kontrolle: `encode_review_confirmed`, `rip_successful`, `error_message`
- Audit: `created_at`, `updated_at`
---
## Tabelle: pipeline_state
## `pipeline_state`
Singleton-Tabelle für den aktuellen Pipeline-Zustand (immer genau 1 Zeile).
Singleton-Tabelle (`id = 1`) für aktiven Snapshot:
```sql
CREATE TABLE pipeline_state (
id INTEGER PRIMARY KEY CHECK(id = 1),
state TEXT NOT NULL DEFAULT 'IDLE',
job_id INTEGER, -- Aktiver Job (NULL wenn IDLE)
progress REAL, -- Fortschritt 0-100
eta TEXT, -- Geschätzte Restzeit
updated_at TEXT NOT NULL
);
```
- `state`
- `active_job_id`
- `progress`
- `eta`
- `status_text`
- `context_json`
- `updated_at`
---
## Tabelle: settings_schema
## `settings_schema` + `settings_values`
Definiert alle verfügbaren Einstellungen mit Metadaten.
```sql
CREATE TABLE settings_schema (
key TEXT PRIMARY KEY,
category TEXT NOT NULL, -- paths, tools, encoding, ...
type TEXT NOT NULL, -- string, number, boolean, select
label TEXT NOT NULL, -- Anzeigename
description TEXT, -- Hilfetext
default_val TEXT, -- Standardwert
required INTEGER, -- 1 = Pflichtfeld
min_val REAL, -- Minimalwert (für number)
max_val REAL, -- Maximalwert (für number)
options TEXT -- JSON-Array für select-Typ
);
```
- `settings_schema`: Definition (Typ, Default, Validation, Reihenfolge)
- `settings_values`: aktueller Wert pro Key
---
## Tabelle: settings_values
## `scripts`, `script_chains`, `script_chain_steps`
Speichert benutzer-konfigurierte Werte.
```sql
CREATE TABLE settings_values (
key TEXT PRIMARY KEY REFERENCES settings_schema(key),
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
- `scripts`: Shell-Skripte (`name`, `script_body`, `order_index`)
- `script_chains`: Ketten (`name`, `order_index`)
- `script_chain_steps`: Schritte je Kette
- `step_type`: `script` oder `wait`
- `script_id` oder `wait_seconds`
---
## Tabelle: scripts
## `user_presets`
Verwaltet Shell-Skripte, die vor oder nach dem Encode-Schritt ausgeführt werden können.
Benannte HandBrake-Preset-Sets:
```sql
CREATE TABLE scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
script_body TEXT NOT NULL,
order_index INTEGER NOT NULL DEFAULT 0, -- Sortierposition in der UI
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
## Tabelle: script_chains
Geordnete Ketten, die mehrere Skripte sequenziell zusammenfassen.
```sql
CREATE TABLE script_chains (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
order_index INTEGER NOT NULL DEFAULT 0, -- Sortierposition in der UI
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE script_chain_steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chain_id INTEGER NOT NULL REFERENCES script_chains(id) ON DELETE CASCADE,
script_id INTEGER NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
step_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Sortierung"
`order_index` in `scripts` und `script_chains` wird über die API (`reorderScripts` / `reorderChains`) per Drag & Drop in der UI gesetzt und bleibt persistent gespeichert.
- `name`
- `media_type` (`bluray|dvd|other|all`)
- `handbrake_preset`
- `extra_args`
- `description`
---
## Tabelle: user_presets
## `cron_jobs` + `cron_run_logs`
Speichert benannte HandBrake-Preset-Sammlungen pro Medientyp.
```sql
CREATE TABLE user_presets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
media_type TEXT NOT NULL DEFAULT 'all', -- 'bluray', 'dvd', 'other', 'all'
handbrake_preset TEXT,
extra_args TEXT,
description TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Medientyp-Filter"
`GET /api/settings/user-presets?mediaType=bluray` gibt Presets mit `media_type = 'bluray'` **und** `media_type = 'all'` zurück.
- `cron_jobs`: Zeitplan + Status
- `cron_run_logs`: einzelne Läufe
- `status`: `running|success|error`
- `output`
- `error_message`
---
## Tabellen: cron_jobs & cron_run_logs
## Migration/Recovery
Speichern den Zeitplan und die Ausführungs-Historie des eingebauten Cron-Systems.
Beim Start werden Schema und Settings-Metadaten automatisch abgeglichen.
```sql
CREATE TABLE cron_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
cron_expression TEXT NOT NULL, -- 5-Felder-Ausdruck (min h d m wd)
source_type TEXT NOT NULL, -- "script" oder "chain"
source_id INTEGER NOT NULL, -- ID des Skripts/der Kette
enabled INTEGER NOT NULL DEFAULT 1,
pushover_enabled INTEGER NOT NULL DEFAULT 1,
last_run_at TEXT,
last_run_status TEXT, -- "success", "error", "running"
next_run_at TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Bei korruptem SQLite-File:
CREATE TABLE cron_run_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cron_job_id INTEGER NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL, -- "success", "error", "running"
exit_code INTEGER,
stdout TEXT,
stderr TEXT,
triggered_by TEXT NOT NULL DEFAULT 'cron', -- "cron" oder "manual"
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Log-Rotation"
Pro Cron-Job werden maximal **50 Log-Einträge** gespeichert; ältere Einträge werden automatisch gelöscht. Stdout/Stderr werden auf **100.000 Zeichen** begrenzt.
1. Datei wird nach `backend/data/corrupt-backups/` verschoben
2. neue DB wird initialisiert
3. Schema wird neu aufgebaut
---
## Schema-Migrationen
`database.js` implementiert **automatische Migrationen**:
1. Beim Start wird das aktuelle Schema geprüft
2. Fehlende Tabellen werden erstellt
3. Fehlende Spalten werden hinzugefügt
4. Neue Default-Einstellungen werden eingefügt
### Korruptions-Recovery
Falls die Datenbankdatei korrupt ist:
```
1. Korrupte Datei wird erkannt (Verbindungsfehler / Integritätsprüfung)
2. Datei wird in backend/data/corrupt-backups/ verschoben
3. Neue, leere Datenbank wird erstellt
4. Schema wird neu initialisiert
5. Log-Eintrag mit Warnung
```
---
## Datenbankpfad konfigurieren
Standard: `./data/ripster.db` (relativ zum Backend-Verzeichnis)
Über Umgebungsvariable anpassen:
```env
DB_PATH=/var/lib/ripster/ripster.db
```
---
## Direkte Datenbankinspektion
## Direkte Inspektion
```bash
# SQLite3-CLI
sqlite3 backend/data/ripster.db
# Alle Jobs anzeigen
.mode table
SELECT id, status, title, created_at FROM jobs ORDER BY created_at DESC;
# Einstellungen anzeigen
SELECT key, value FROM settings_values;
SELECT key, value FROM settings_values ORDER BY key;
```

View File

@@ -1,192 +1,82 @@
# Frontend-Komponenten
Das Frontend ist mit **React 18** und **PrimeReact** gebaut und kommuniziert über REST-API und WebSocket mit dem Backend.
Frontend: React + PrimeReact + Vite.
---
## Seiten (Pages)
## Hauptseiten
### DashboardPage.jsx
### `DashboardPage.jsx`
Die Hauptseite von Ripster zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen.
Pipeline-Steuerung:
**Funktionen:**
- Anzeige des aktuellen Pipeline-Zustands (IDLE, DISC_DETECTED, METADATA_SELECTION, RIPPING, MEDIAINFO_CHECK, READY_TO_ENCODE, ENCODING, ...)
- Live-Fortschrittsbalken mit ETA
- Trigger für Metadaten-Dialog
- Playlist-Entscheidungs-UI (bei Blu-ray Obfuskierung)
- Encode-Review mit Track-Auswahl
- Job-Steuerung (Start, Abbruch, Retry, Queue-Interaktion)
- Status/Progress/ETA
- Metadaten-Dialog
- Playlist-Entscheidung
- Review-Panel
- Queue-Interaktion (reorder/add/remove)
- Job-Aktionen (Start/Cancel/Retry/Re-Encode)
- Hardware-Monitoring-Anzeige
**Zugehörige Komponenten:**
- `PipelineStatusCard` Status-Widget
- `MetadataSelectionDialog` OMDb-Suche und Playlist-Auswahl
- `MediaInfoReviewPanel` Track-Auswahl vor dem Encoding
- Queue- und Job-Karten-UI direkt in `DashboardPage`
### `SettingsPage.jsx`
### SettingsPage.jsx
Konfiguration:
Konfigurationsoberfläche für alle Ripster-Einstellungen.
- dynamisches Settings-Formular (`DynamicSettingsForm`)
- Skripte/Ketten inkl. Reorder/Test
- User-Presets
- Cron-Jobs (`CronJobsTab`)
**Funktionen:**
- Dynamisch generiertes Formular aus dem Settings-Schema
- Echtzeit-Validierungsfeedback
- PushOver-Verbindungstest
- Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen
### `HistoryPage.jsx`
### DatabasePage.jsx (`/history`)
Historie:
Job-Historie und Datenbankansicht mit vollständigem Audit-Trail.
**Funktionen:**
- Sortier- und filterbares Job-Verzeichnis
- Statusfilter (FINISHED, ERROR, WAITING_FOR_USER_DECISION, ...)
- Job-Detail-Dialog mit vollständigen Logs
- Re-Encode, Löschen und Metadaten-Zuweisung
- Import von Orphan-Raw-Ordnern
- Job-Liste/Filter
- Job-Details + Logs
- OMDb-Nachzuweisung
- Re-Encode/Restart-Workflows
---
## Komponenten (Components)
## Wichtige Komponenten
### MetadataSelectionDialog.jsx
Dialog für die Metadaten-Auswahl nach der Disc-Analyse.
```
┌─────────────────────────────────────┐
│ Metadaten auswählen │
├─────────────────────────────────────┤
│ Suche: [Inception ] 🔍 │
├─────────────────────────────────────┤
│ Ergebnisse: │
│ ▶ Inception (2010) Movie │
│ Inception: ... (2011) Series │
├─────────────────────────────────────┤
│ Playlist (nur Blu-ray): │
│ ▶ 00800.mpls (2:30:15) ✓ Empfohlen │
│ 00801.mpls (0:01:23) │
├─────────────────────────────────────┤
│ [Bestätigen] │
└─────────────────────────────────────┘
```
### MediaInfoReviewPanel.jsx
Track-Auswahl-Panel vor dem Encoding.
```
┌─────────────────────────────────────┐
│ Encode-Review │
├─────────────────────────────────────┤
│ Audio-Tracks: │
│ ☑ Track 1: Deutsch (AC-3, 5.1) │
│ ☑ Track 2: English (TrueHD, 7.1) │
│ ☐ Track 3: Français (AC-3, 2.0) │
├─────────────────────────────────────┤
│ Untertitel: │
│ ☑ Track 1: Deutsch │
│ ☐ Track 2: English │
├─────────────────────────────────────┤
│ [Encoding starten] │
└─────────────────────────────────────┘
```
### DynamicSettingsForm.jsx
Wiederverwendbares Formular, das aus dem Settings-Schema generiert wird.
**Unterstützte Feldtypen:**
| Typ | UI-Element |
|----|-----------|
| `string` | Text-Input |
| `number` | Zahlen-Input mit Min/Max |
| `boolean` | Toggle/Checkbox |
| `select` | Dropdown |
| `password` | Passwort-Input |
### PipelineStatusCard.jsx
Status-Anzeige-Widget für die Dashboard-Seite.
### JobDetailDialog.jsx
Vollständiger Job-Detail-Dialog mit Logs-Viewer.
- `PipelineStatusCard.jsx`
- `MetadataSelectionDialog.jsx`
- `MediaInfoReviewPanel.jsx`
- `JobDetailDialog.jsx`
- `CronJobsTab.jsx`
---
## Hooks
## API-Client (`api/client.js`)
### useWebSocket.js
Zentraler Custom-Hook für die WebSocket-Verbindung.
```js
useWebSocket({
onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGED') {
setPipelineState(msg.payload);
}
}
});
```
**Features:**
- Automatische Verbindung zu `/ws`
- Reconnect mit festem Intervall (`1500ms`)
- Message-Parsing (JSON)
- zentraler `request()` mit JSON-Handling
- Fehlerobjekt aus API wird auf `Error(message)` gemappt
- `VITE_API_BASE` default `/api`
---
## API-Client (client.js)
## WebSocket (`hooks/useWebSocket.js`)
Zentraler HTTP-Client für alle Backend-Anfragen.
- URL: `VITE_WS_URL` oder automatisch `ws(s)://<host>/ws`
- Auto-Reconnect mit 1500ms Intervall
```js
// Beispiel-Aufrufe
const state = await api.getPipelineState();
const results = await api.searchOmdb('Inception');
await api.selectMetadata({ jobId, title, year, imdbId, selectedPlaylist });
await api.confirmEncodeReview(jobId, {
selectedEncodeTitleId: 1,
selectedTrackSelection: { 1: { audioTrackIds: [1], subtitleTrackIds: [3] } }
});
```
In `App.jsx` werden u. a. verarbeitet:
**Features:**
- Zentralisierte Fehlerbehandlung
- Automatische JSON-Serialisierung
- Basis-URL aus Umgebungsvariable (`VITE_API_BASE`)
- `PIPELINE_STATE_CHANGED`
- `PIPELINE_PROGRESS`
- `PIPELINE_QUEUE_CHANGED`
- `DISC_DETECTED` / `DISC_REMOVED`
- `HARDWARE_MONITOR_UPDATE`
---
## Build & Entwicklung
### Entwicklungsserver
## Build/Run
```bash
cd frontend
npm run dev
# → http://localhost:5173
```
### Vite-Proxy-Konfiguration
In der Entwicklungsumgebung proxied Vite API-Anfragen zum Backend:
```js
// vite.config.js
proxy: {
'/api': 'http://localhost:3001',
'/ws': { target: 'ws://localhost:3001', ws: true }
}
```
### Production-Build
```bash
cd frontend
npm run build
# → frontend/dist/
# dev
npm run dev --prefix frontend
# prod build
npm run build --prefix frontend
```

View File

@@ -1,6 +1,6 @@
# Architektur
Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikation über WebSockets aufgebaut.
Ripster ist eine Client-Server-Anwendung mit REST + WebSocket.
---
@@ -9,104 +9,63 @@ Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikatio
```mermaid
graph TB
subgraph Browser["Browser (React)"]
Dashboard["Dashboard"]
Settings["Einstellungen"]
History["History"]
Dashboard[Dashboard]
Settings[Einstellungen]
History[Historie]
end
subgraph Backend["Node.js Backend"]
API["REST API\n(Express)"]
WS["WebSocket\nServer"]
Pipeline["Pipeline\nService"]
DB["SQLite\nDatenbank"]
API[REST API\nExpress]
WS[WebSocket\n/ws]
Pipeline[pipelineService]
Cron[cronService]
DB[(SQLite)]
end
subgraph ExternalTools["Externe Tools"]
MakeMKV["makemkvcon"]
HandBrake["HandBrakeCLI"]
MediaInfo["mediainfo"]
subgraph Tools["Externe Tools"]
MakeMKV[makemkvcon]
HandBrake[HandBrakeCLI]
MediaInfo[mediainfo]
end
subgraph ExternalAPIs["Externe APIs"]
OMDb["OMDb API"]
PushOver["PushOver"]
end
Browser <-->|HTTP REST| API
Browser <-->|HTTP| API
Browser <-->|WebSocket| WS
Pipeline --> MakeMKV
Pipeline --> HandBrake
Pipeline --> MediaInfo
Pipeline <-->|Metadaten| OMDb
Pipeline -->|Benachrichtigungen| PushOver
API --> DB
Pipeline --> DB
Cron --> DB
```
---
## Schichten-Architektur
## Schichten
### Backend
```
index.js (Express Server)
├── Routes (API-Endpunkte)
│ ├── pipelineRoutes.js
│ ├── settingsRoutes.js
│ └── historyRoutes.js
├── Services (Business Logic)
│ ├── pipelineService.js ← Kern-Orchestrierung
│ ├── diskDetectionService.js
│ ├── processRunner.js
│ ├── websocketService.js
│ ├── omdbService.js
│ ├── settingsService.js
│ ├── notificationService.js
│ ├── historyService.js
│ └── logger.js
├── Database
│ ├── database.js
│ └── defaultSettings.js
└── Utils
├── encodePlan.js
├── playlistAnalysis.js
├── progressParsers.js
└── files.js
```
- `src/index.js` (Bootstrapping, Routes, WS, Services)
- `src/routes/*` (Pipeline, Settings, History, Crons)
- `src/services/*` (Business-Logik)
- `src/db/database.js` (Init/Migration)
- `src/utils/*` (Parser, Dateifunktionen, Validierung)
### Frontend
```
App.jsx (React Router)
├── Pages
│ ├── DashboardPage.jsx ← Haupt-Interface
│ ├── SettingsPage.jsx
│ └── DatabasePage.jsx ← Historie/DB-Ansicht
├── Components
│ ├── PipelineStatusCard.jsx
│ ├── MetadataSelectionDialog.jsx
│ ├── MediaInfoReviewPanel.jsx
│ ├── DynamicSettingsForm.jsx
│ └── JobDetailDialog.jsx
├── Hooks
│ └── useWebSocket.js
└── API
└── client.js
```
- `App.jsx` + `pages/*` (Dashboard, Settings, History)
- `components/*` (Status-/Review-/Dialog-Komponenten)
- `api/client.js` (REST-Client)
- `hooks/useWebSocket.js` (WS-Reconnect)
---
## Weiterführende Dokumentation
## Weiterführend
<div class="grid cards" markdown>
- [:octicons-arrow-right-24: Übersicht](overview.md)
- [:octicons-arrow-right-24: Backend-Services](backend.md)
- [:octicons-arrow-right-24: Frontend-Komponenten](frontend.md)
- [:octicons-arrow-right-24: Datenbank](database.md)
- [:octicons-arrow-right-24: Übersicht](overview.md)
- [:octicons-arrow-right-24: Backend-Services](backend.md)
- [:octicons-arrow-right-24: Frontend-Komponenten](frontend.md)
- [:octicons-arrow-right-24: Datenbank](database.md)
</div>

View File

@@ -2,144 +2,93 @@
---
## Kern-Designprinzipien
## Kernprinzipien
### Event-Driven Pipeline
### Event-getriebene Pipeline
Der gesamte Ripping-Workflow ist als **State Machine** implementiert. Der `pipelineService` verwaltet den aktuellen Zustand und emittiert Ereignisse bei jedem Zustandswechsel. Der WebSocket-Service überträgt diese Ereignisse sofort an alle verbundenen Clients.
`pipelineService` hält einen Snapshot der State-Machine und broadcastet Änderungen sofort via WebSocket.
```
Zustandswechsel → Event → WebSocket → Frontend-Update
```text
State-Änderung -> PIPELINE_STATE_CHANGED/PIPELINE_PROGRESS -> Frontend-Update
```
### Service-Layer-Muster
### Service-Layer
```
HTTP-Route Service → Datenbank
```text
Route -> Service -> DB/Tool-Execution
```
Routes delegieren die gesamte Business-Logik an Services. Services sind voneinander unabhängig und können einzeln getestet werden.
Routes enthalten kaum Business-Logik.
### Schema-getriebene Einstellungen
### Schema-getriebene Settings
Die Settings-Konfiguration definiert **sowohl** die Validierungsregeln als auch die UI-Struktur in einer einzigen Quelle (`settings_schema`-Tabelle). Die `DynamicSettingsForm`-Komponente rendert das Formular dynamisch aus dem Schema.
Settings sind DB-schema-getrieben (`settings_schema` + `settings_values`), UI rendert dynamisch aus diesen Daten.
---
## Echtzeit-Kommunikation
### WebSocket-Protokoll
WebSocket läuft auf `/ws`.
Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON übertragen:
Wichtige Events:
```json
{
"type": "PIPELINE_STATE_CHANGED",
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5,
"eta": "00:12:34"
}
}
```
**Nachrichtentypen:**
| Typ | Beschreibung |
|----|-------------|
| `PIPELINE_STATE_CHANGED` | Pipeline-Zustand hat gewechselt |
| `PIPELINE_PROGRESS` | Fortschritt (% und ETA) |
| `PIPELINE_QUEUE_CHANGED` | Queue-Status geändert |
| `DISC_DETECTED` | Disc wurde erkannt |
| `DISC_REMOVED` | Disc wurde entfernt |
| `PIPELINE_ERROR` | Pipeline-Fehler aufgetreten |
| `DISK_DETECTION_ERROR` | Laufwerkserkennung-Fehler |
### Reconnect-Logik
Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit festem Intervall von 1500ms bei Verbindungsabbrüchen.
- `PIPELINE_STATE_CHANGED`, `PIPELINE_PROGRESS`, `PIPELINE_QUEUE_CHANGED`
- `DISC_DETECTED`, `DISC_REMOVED`
- `HARDWARE_MONITOR_UPDATE`
- `SETTINGS_UPDATED`, `SETTINGS_BULK_UPDATED`
- `SETTINGS_SCRIPTS_UPDATED`, `SETTINGS_SCRIPT_CHAINS_UPDATED`, `USER_PRESETS_UPDATED`
- `CRON_JOBS_UPDATED`, `CRON_JOB_UPDATED`
- `PIPELINE_ERROR`, `DISK_DETECTION_ERROR`
---
## Prozess-Management
## Prozessausführung
### processRunner.js
Externe Tools werden als Child-Processes gestartet (`processRunner`):
Externe Tools (MakeMKV, HandBrake, MediaInfo) werden als **Child Processes** gestartet:
```js
spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
```
- **stdout/stderr** werden zeilenweise gelesen und in Echtzeit verarbeitet
- **Progress-Parsing** erfolgt über reguläre Ausdrücke in `progressParsers.js`
- **Graceful Shutdown**: SIGINT → Timeout → SIGKILL
- **Prozess-Tracking**: Aktive Prozesse werden registriert für sauberes Beenden
- Streaming von stdout/stderr
- Progress-Parsing (`progressParsers.js`)
- kontrollierter Abbruch (SIGINT/SIGKILL-Fallback)
---
## Datenpersistenz
## Persistenz
### SQLite-Datenbank
SQLite-Datei: `backend/data/ripster.db`
Ripster verwendet eine **einzige SQLite-Datei** für alle persistenten Daten:
Kern-Tabellen:
```
backend/data/ripster.db
```
- `jobs`, `pipeline_state`
- `settings_schema`, `settings_values`
- `scripts`, `script_chains`, `script_chain_steps`
- `user_presets`
- `cron_jobs`, `cron_run_logs`
**Tabellen:**
| Tabelle | Inhalt |
|---------|--------|
| `jobs` | Alle Rip-Jobs mit Status, Logs, Metadaten |
| `pipeline_state` | Aktueller Pipeline-Zustand (Singleton) |
| `settings_schema` | Schema aller verfügbaren Einstellungen |
| `settings_values` | Benutzer-konfigurierte Werte |
### Migrations-Strategie
Beim Start prüft `database.js` automatisch, ob das Schema aktuell ist, und führt fehlende Migrationen aus. Korrupte Datenbankdateien werden in ein Quarantäne-Verzeichnis verschoben und eine neue Datenbank erstellt.
Beim Start werden Schema und Settings-Migrationen automatisch ausgeführt.
---
## Fehlerbehandlung
### Strukturierte Fehler
Zentrales Error-Handling liefert:
Alle Fehler werden mit Kontext-Metadaten protokolliert:
```js
logger.error('Encoding fehlgeschlagen', {
jobId: job.id,
command: cmd,
exitCode: code,
stderr: lastLines
});
```json
{
"error": {
"message": "...",
"statusCode": 400,
"reqId": "...",
"details": []
}
}
```
### Job-Fehler-Recovery
- Fehlgeschlagene Jobs bleiben in der Datenbank (Status `ERROR`)
- Vollständige Fehler-Logs werden im Job-Datensatz gespeichert
- **Retry-Funktion** ermöglicht Neustart von einem Fehler-Zustand
- **Re-Encode** erlaubt erneutes Encodieren ohne neu zu rippen
Fehlgeschlagene Jobs bleiben in der Historie (`ERROR` oder `CANCELLED`) und können erneut gestartet werden.
---
## Sicherheit
## CORS & Runtime-Konfig
### Eingabe-Validierung
- Alle Benutzer-Eingaben werden in `validators.js` validiert
- CLI-Argumente werden sicher über `commandLine.js` konstruiert (kein Shell-Injection-Risiko)
- Pfade werden sanitisiert bevor sie an externe Prozesse übergeben werden
### CORS-Konfiguration
```env
CORS_ORIGIN=http://localhost:5173
```
In Produktion sollte dieser Wert auf die tatsächliche Frontend-URL gesetzt werden.
- `CORS_ORIGIN` default: `*`
- `LOG_LEVEL` default: `info`
- DB-/Log-Pfade über `DB_PATH`/`LOG_DIR` konfigurierbar