Initial commit mit MkDocs-Dokumentation
This commit is contained in:
221
docs/architecture/backend.md
Normal file
221
docs/architecture/backend.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Backend-Services
|
||||
|
||||
Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, die jeweils eine klar abgegrenzte Verantwortlichkeit haben.
|
||||
|
||||
---
|
||||
|
||||
## pipelineService.js
|
||||
|
||||
**Der Kern von Ripster** – orchestriert den gesamten Ripping-Workflow.
|
||||
|
||||
### Zuständigkeiten
|
||||
|
||||
- Verwaltung des Pipeline-Zustands als State Machine
|
||||
- Koordination zwischen allen externen Tools
|
||||
- Generierung von Encode-Plänen
|
||||
- Fehlerbehandlung und Recovery
|
||||
|
||||
### Haupt-Methoden
|
||||
|
||||
| Methode | Beschreibung |
|
||||
|---------|-------------|
|
||||
| `analyzeDisc()` | Startet MakeMKV-Analyse der eingelegten Disc |
|
||||
| `selectMetadata(jobId, omdbData, playlist)` | Setzt Metadaten und Playlist für einen Job |
|
||||
| `startJob(jobId)` | Startet den Ripping-Prozess |
|
||||
| `confirmEncode(jobId, trackSelection)` | Bestätigt Encode mit Track-Auswahl |
|
||||
| `cancelPipeline()` | Bricht aktiven Prozess ab |
|
||||
| `retryJob(jobId)` | Wiederholt fehlgeschlagenen Job |
|
||||
| `reencodeJob(jobId)` | Encodiert bestehende Raw-MKV neu |
|
||||
|
||||
### Zustandsübergänge
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
IDLE --> ANALYZING: analyzeDisc()
|
||||
ANALYZING --> METADATA_SELECTION: MakeMKV fertig
|
||||
METADATA_SELECTION --> READY_TO_START: selectMetadata()
|
||||
READY_TO_START --> RIPPING: startJob()
|
||||
RIPPING --> MEDIAINFO_CHECK: MKV erstellt
|
||||
MEDIAINFO_CHECK --> READY_TO_ENCODE: Tracks analysiert
|
||||
READY_TO_ENCODE --> ENCODING: confirmEncode()
|
||||
ENCODING --> FINISHED: HandBrake fertig
|
||||
ENCODING --> ERROR: Fehler
|
||||
RIPPING --> ERROR: Fehler
|
||||
ERROR --> IDLE: retryJob() / cancel
|
||||
FINISHED --> IDLE: cancel / neue Disc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## diskDetectionService.js
|
||||
|
||||
Überwacht das Disc-Laufwerk auf Disc-Einleger- und Auswurf-Ereignisse.
|
||||
|
||||
### Modi
|
||||
|
||||
| Modus | Beschreibung |
|
||||
|------|-------------|
|
||||
| `auto` | Erkennt verfügbare Laufwerke automatisch |
|
||||
| `explicit` | Überwacht ein bestimmtes Gerät (z.B. `/dev/sr0`) |
|
||||
|
||||
### Polling
|
||||
|
||||
Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 5000ms) und emittiert Events:
|
||||
|
||||
```js
|
||||
// Ereignisse
|
||||
emit('disc-detected', { device: '/dev/sr0' })
|
||||
emit('disc-removed', { device: '/dev/sr0' })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## processRunner.js
|
||||
|
||||
Verwaltet externe CLI-Prozesse.
|
||||
|
||||
### 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)
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## websocketService.js
|
||||
|
||||
WebSocket-Server für Echtzeit-Client-Kommunikation.
|
||||
|
||||
### Betrieb
|
||||
|
||||
- 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({ type: 'PIPELINE_STATE_CHANGE', data: { state, jobId } });
|
||||
broadcast({ type: 'PROGRESS_UPDATE', data: { progress, eta } });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## omdbService.js
|
||||
|
||||
Integration mit der [OMDb API](https://www.omdbapi.com/).
|
||||
|
||||
### Methoden
|
||||
|
||||
| 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"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## settingsService.js
|
||||
|
||||
Verwaltet alle Anwendungseinstellungen.
|
||||
|
||||
### Features
|
||||
|
||||
- **Schema-getriebene Validierung**: Jede Einstellung hat Typ, Grenzen und Pflichtfeld-Flag
|
||||
- **Kategorisierung**: Einstellungen sind in Kategorien gruppiert (Paths, Tools, Encoding, ...)
|
||||
- **Persistenz**: Werte in SQLite, Schema ebenfalls in SQLite
|
||||
- **Defaults**: `defaultSettings.js` definiert Standardwerte
|
||||
|
||||
### Einstellungs-Kategorien
|
||||
|
||||
| Kategorie | Einstellungen |
|
||||
|-----------|--------------|
|
||||
| `paths` | `raw_dir`, `movie_dir`, `log_dir` |
|
||||
| `tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command` |
|
||||
| `encoding` | `handbrake_preset`, `handbrake_extra_args`, `output_extension`, `filename_template` |
|
||||
| `drive` | `drive_mode`, `drive_device`, `disc_poll_interval_ms` |
|
||||
| `makemkv` | `makemkv_min_length_minutes`, `makemkv_backup_mode` |
|
||||
| `omdb` | `omdb_api_key`, `omdb_default_type` |
|
||||
| `notifications` | `pushover_user_key`, `pushover_api_token` |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
161
docs/architecture/database.md
Normal file
161
docs/architecture/database.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Datenbank
|
||||
|
||||
Ripster verwendet **SQLite3** als Datenbank. Die Datenbankdatei liegt unter `backend/data/ripster.db`.
|
||||
|
||||
---
|
||||
|
||||
## Schema-Übersicht
|
||||
|
||||
```sql
|
||||
-- Vier Haupt-Tabellen
|
||||
settings_schema -- Einstellungs-Definitionen
|
||||
settings_values -- Benutzer-Werte
|
||||
jobs -- Rip-Job-Datensätze
|
||||
pipeline_state -- Aktueller Pipeline-Zustand (Singleton)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tabelle: jobs
|
||||
|
||||
Die wichtigste Tabelle – speichert alle Ripping-Jobs.
|
||||
|
||||
```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
|
||||
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
|
||||
);
|
||||
```
|
||||
|
||||
### 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 |
|
||||
|
||||
---
|
||||
|
||||
## Tabelle: pipeline_state
|
||||
|
||||
Singleton-Tabelle für den aktuellen Pipeline-Zustand (immer genau 1 Zeile).
|
||||
|
||||
```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
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tabelle: settings_schema
|
||||
|
||||
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
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tabelle: settings_values
|
||||
|
||||
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
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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/quarantine/ 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
|
||||
|
||||
```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;
|
||||
```
|
||||
190
docs/architecture/frontend.md
Normal file
190
docs/architecture/frontend.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Frontend-Komponenten
|
||||
|
||||
Das Frontend ist mit **React 18** und **PrimeReact** gebaut und kommuniziert über REST-API und WebSocket mit dem Backend.
|
||||
|
||||
---
|
||||
|
||||
## Seiten (Pages)
|
||||
|
||||
### DashboardPage.jsx
|
||||
|
||||
Die Hauptseite von Ripster – zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen.
|
||||
|
||||
**Funktionen:**
|
||||
- Anzeige des aktuellen Pipeline-Zustands (IDLE, ANALYZING, RIPPING, 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)
|
||||
|
||||
**Zugehörige Komponenten:**
|
||||
- `PipelineStatusCard` – Status-Widget
|
||||
- `MetadataSelectionDialog` – OMDb-Suche und Playlist-Auswahl
|
||||
- `MediaInfoReviewPanel` – Track-Auswahl vor dem Encoding
|
||||
- `DiscDetectedDialog` – Benachrichtigung bei Disc-Erkennung
|
||||
|
||||
### SettingsPage.jsx
|
||||
|
||||
Konfigurationsoberfläche für alle Ripster-Einstellungen.
|
||||
|
||||
**Funktionen:**
|
||||
- Dynamisch generiertes Formular aus dem Settings-Schema
|
||||
- Echtzeit-Validierungsfeedback
|
||||
- PushOver-Verbindungstest
|
||||
- Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen
|
||||
|
||||
### HistoryPage.jsx
|
||||
|
||||
Job-Historie 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
|
||||
|
||||
---
|
||||
|
||||
## Komponenten (Components)
|
||||
|
||||
### 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 │
|
||||
├─────────────────────────────────────┤
|
||||
│ [Encodierung 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.
|
||||
|
||||
---
|
||||
|
||||
## Hooks
|
||||
|
||||
### useWebSocket.js
|
||||
|
||||
Zentraler Custom-Hook für die WebSocket-Verbindung.
|
||||
|
||||
```js
|
||||
const { status, lastMessage } = useWebSocket({
|
||||
onMessage: (msg) => {
|
||||
if (msg.type === 'PIPELINE_STATE_CHANGE') {
|
||||
setPipelineState(msg.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Automatische Verbindung zu `/ws`
|
||||
- Reconnect mit exponential backoff
|
||||
- Message-Parsing (JSON)
|
||||
- Status-Tracking (connecting, connected, disconnected)
|
||||
|
||||
---
|
||||
|
||||
## API-Client (client.js)
|
||||
|
||||
Zentraler HTTP-Client für alle Backend-Anfragen.
|
||||
|
||||
```js
|
||||
// Beispiel-Aufrufe
|
||||
const state = await api.getPipelineState();
|
||||
const results = await api.searchOmdb('Inception');
|
||||
await api.selectMetadata(jobId, omdbData, playlist);
|
||||
await api.confirmEncode(jobId, { audioTracks: [0, 1], subtitleTracks: [0] });
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Zentralisierte Fehlerbehandlung
|
||||
- Automatische JSON-Serialisierung
|
||||
- Basis-URL aus Umgebungsvariable (`VITE_API_BASE`)
|
||||
|
||||
---
|
||||
|
||||
## Build & Entwicklung
|
||||
|
||||
### Entwicklungsserver
|
||||
|
||||
```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/
|
||||
```
|
||||
112
docs/architecture/index.md
Normal file
112
docs/architecture/index.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Architektur
|
||||
|
||||
Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikation über WebSockets aufgebaut.
|
||||
|
||||
---
|
||||
|
||||
## Systemüberblick
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Browser["Browser (React)"]
|
||||
Dashboard["Dashboard"]
|
||||
Settings["Einstellungen"]
|
||||
History["History"]
|
||||
end
|
||||
|
||||
subgraph Backend["Node.js Backend"]
|
||||
API["REST API\n(Express)"]
|
||||
WS["WebSocket\nServer"]
|
||||
Pipeline["Pipeline\nService"]
|
||||
DB["SQLite\nDatenbank"]
|
||||
end
|
||||
|
||||
subgraph ExternalTools["Externe Tools"]
|
||||
MakeMKV["makemkvcon"]
|
||||
HandBrake["HandBrakeCLI"]
|
||||
MediaInfo["mediainfo"]
|
||||
end
|
||||
|
||||
subgraph ExternalAPIs["Externe APIs"]
|
||||
OMDb["OMDb API"]
|
||||
PushOver["PushOver"]
|
||||
end
|
||||
|
||||
Browser <-->|HTTP REST| API
|
||||
Browser <-->|WebSocket| WS
|
||||
Pipeline --> MakeMKV
|
||||
Pipeline --> HandBrake
|
||||
Pipeline --> MediaInfo
|
||||
Pipeline <-->|Metadaten| OMDb
|
||||
Pipeline -->|Benachrichtigungen| PushOver
|
||||
API --> DB
|
||||
Pipeline --> DB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schichten-Architektur
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```
|
||||
App.jsx (React Router)
|
||||
├── Pages
|
||||
│ ├── DashboardPage.jsx ← Haupt-Interface
|
||||
│ ├── SettingsPage.jsx
|
||||
│ └── HistoryPage.jsx
|
||||
├── Components
|
||||
│ ├── PipelineStatusCard.jsx
|
||||
│ ├── MetadataSelectionDialog.jsx
|
||||
│ ├── MediaInfoReviewPanel.jsx
|
||||
│ ├── DynamicSettingsForm.jsx
|
||||
│ └── JobDetailDialog.jsx
|
||||
├── Hooks
|
||||
│ └── useWebSocket.js
|
||||
└── API
|
||||
└── client.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Weiterführende Dokumentation
|
||||
|
||||
<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)
|
||||
|
||||
</div>
|
||||
144
docs/architecture/overview.md
Normal file
144
docs/architecture/overview.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Architektur-Übersicht
|
||||
|
||||
---
|
||||
|
||||
## Kern-Designprinzipien
|
||||
|
||||
### Event-Driven 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.
|
||||
|
||||
```
|
||||
Zustandswechsel → Event → WebSocket → Frontend-Update
|
||||
```
|
||||
|
||||
### Service-Layer-Muster
|
||||
|
||||
```
|
||||
HTTP-Route → Service → Datenbank
|
||||
```
|
||||
|
||||
Routes delegieren die gesamte Business-Logik an Services. Services sind voneinander unabhängig und können einzeln getestet werden.
|
||||
|
||||
### Schema-getriebene Einstellungen
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Echtzeit-Kommunikation
|
||||
|
||||
### WebSocket-Protokoll
|
||||
|
||||
Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON übertragen:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "PIPELINE_STATE_CHANGE",
|
||||
"data": {
|
||||
"state": "ENCODING",
|
||||
"jobId": 42,
|
||||
"progress": 73.5,
|
||||
"eta": "00:12:34"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Nachrichtentypen:**
|
||||
|
||||
| Typ | Beschreibung |
|
||||
|----|-------------|
|
||||
| `PIPELINE_STATE_CHANGE` | Pipeline-Zustand hat gewechselt |
|
||||
| `PROGRESS_UPDATE` | Fortschritt (% und ETA) |
|
||||
| `DISC_DETECTED` | Disc wurde erkannt |
|
||||
| `DISC_REMOVED` | Disc wurde entfernt |
|
||||
| `ERROR` | Fehler aufgetreten |
|
||||
| `JOB_COMPLETE` | Job abgeschlossen |
|
||||
|
||||
### Reconnect-Logik
|
||||
|
||||
Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit exponential backoff bei Verbindungsabbrüchen.
|
||||
|
||||
---
|
||||
|
||||
## Prozess-Management
|
||||
|
||||
### processRunner.js
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Datenpersistenz
|
||||
|
||||
### SQLite-Datenbank
|
||||
|
||||
Ripster verwendet eine **einzige SQLite-Datei** für alle persistenten Daten:
|
||||
|
||||
```
|
||||
backend/data/ripster.db
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
### Strukturierte Fehler
|
||||
|
||||
Alle Fehler werden mit Kontext-Metadaten protokolliert:
|
||||
|
||||
```js
|
||||
logger.error('Encoding fehlgeschlagen', {
|
||||
jobId: job.id,
|
||||
command: cmd,
|
||||
exitCode: code,
|
||||
stderr: lastLines
|
||||
});
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### 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.
|
||||
Reference in New Issue
Block a user