Bugfix and Docs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user