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
|
||||
|
||||
Reference in New Issue
Block a user