real-debrid-downloader/tasks/todo.md
Sucukdeluxe 468df99142 Add extended diagnostics logging
- Electron crash handlers (render-process-gone, child-process-gone,
  unresponsive/responsive, process warnings) with a circuit-breaker
  auto-reload for renderer crashes
- Renderer error capture (window.onerror, unhandledrejection, React
  ErrorBoundary) forwarded to the main log via a one-way IPC channel
- Memory-pressure heartbeat measured against the V8 heap_size_limit
- Gated DEBUG log level (RD_DEBUG) and an in-memory ring of recent
  WARN/ERROR lines, exposed via the /errors endpoint and support bundle
- Disk-error classification (ENOSPC etc.) on download failures and
  integrity-check pass/fail logging
2026-06-07 17:00:06 +02:00

14 KiB
Raw Permalink Blame History

Erweitertes Logging (2026-06-07) — Goal: Tool besser kontrollieren bei Problemen/Fehlern

Recon (4-Agent-Workflow) fand 40+ Lücken. Bewusste Scope-Disziplin: NICHT alle 40 instrumentieren (die meisten leeren catches sind legitimes Cleanup → würden das Log fluten). Fokus: strukturelle Sichtbarkeit für unbeaufsichtigten Windows-Server, wo "Crash/Hang ohne Log" der schlimmste Fall ist. Advisor-gegengeprüft.

Tier 1 — Prozess-/Renderer-Crash-Sichtbarkeit (höchster Wert)

  • main.ts: render-process-gone (+ Auto-Reload mit Circuit-Breaker max 3/5min)
  • main.ts: app.on("child-process-gone") (GPU/Utility/Renderer-Subprozesse)
  • main.ts: webContents.on("unresponsive"/"responsive") — NUR loggen, nie killen
  • main.ts: process.on("warning") (Node-Warnungen, z.B. MaxListenersExceeded)
  • Renderer-Fehler-Capture: window.onerror + unhandledrejection + React ErrorBoundary → über neuen Einweg-IPC LOG_RENDERER_ERROR ins Main-Log (kein stilles White-Screen)
  • Memory-Heartbeat: im vorhandenen runtimeStatsTimer (60s), warn bei heapUsed/heapTotal > 0.9

Tier 2 — Logging-Infrastruktur

  • logger.ts: DEBUG-Level, gated über RD_DEBUG env (no-op wenn aus → keine Format-Kosten)
  • error-ring.ts (NEU, pure+getestet): letzte N WARN/ERROR im RAM, gefüttert im write()-Chokepoint
  • debug-server.ts: /errors Endpoint
  • support-bundle.ts: overview/recent-errors.json

Tier 3 — Gezielte High-Value-Catches

  • fs-error.ts (NEU, pure+getestet): classifyDiskError(err) — ENOSPC/EACCES/EROFS/EMFILE
  • download-manager.ts: ENOSPC-Klassifizierung am vorhandenen Attempt-catch (reine Log-Anreicherung)
  • download-manager.ts: Integrity-Check PASS ins item-log (Symmetrie: bisher nur Fail geloggt)

Verifikation — ERLEDIGT 2026-06-07

  • Alle Tier 1/2/3 Punkte umgesetzt (siehe unten)
  • tsc = 6 (Baseline unverändert — eine versehentlich eingeführte pkg-Referenz sofort gefixt)
  • 728 Tests grün (715 Baseline + 13 neu: error-ring 5, fs-error/debug-gate 8), 41 Dateien
  • npm run build grün (tsup main 1.04MB + vite renderer 33 Module)
  • Grep-Konsistenz LOG_RENDERER_ERROR/reportRendererError über alle 5 Dateien (Kette lückenlos: ipc.ts → preload-api.ts → preload.ts(send) → main.ts(ipcMain.on, einweg) → main.tsx + error-boundary.tsx)
  • Renderer-tsc-Abdeckung BEWIESEN (nicht angenommen): absichtlicher Typfehler in error-boundary.tsx wurde von tsc --noEmit geflaggt → die neuen Renderer-Dateien sind echt typgeprüft (vite build prüft NICHT)
  • Advisor-Feinschliff: Memory-Heartbeat misst gegen v8.getHeapStatistics().heap_size_limit (echte OOM-Decke) statt heapUsed/heapTotal — sonst Fehlalarm, der die Error-Ring zumüllt

Geänderte/neue Dateien

NEU: error-ring.ts, fs-error.ts, renderer/error-boundary.tsx, tests/error-ring.test.ts, tests/fs-error.test.ts GEÄNDERT: logger.ts (DEBUG-Level+Gate+Ring-Feed), main.ts (4 Crash-Handler + Renderer-IPC), app-controller.ts (Memory-Heartbeat), debug-server.ts (/errors), support-bundle.ts (recent-errors.json), download-manager.ts (ENOSPC-Klassifizierung + Integrity-PASS-Log), shared: ipc.ts/types.ts/preload-api.ts, preload.ts, renderer/main.tsx

NOCH OFFEN

  • Release (Gitea + GitHub-Mirror) — wartet auf User-Go ("jo mach releasen")

Real-Debrid-Downloader — Analyse & Verbesserungen (2026-05-23)

Tiefe Analyse via 3 parallele Subagents (Bugs / Features / UI) + 4 Design-Mockups.


A. BUGS / ROBUSTHEIT (verifiziert gegen Quellcode)

Roter Faden: Die Deferred-Post-Processing-Pipeline (eingeführt um den Extract-Slot schnell freizugeben) ist nur halb ins Abbruch-/Lifecycle-Management integriert. Genau der Bereich des v1.7.156-Fixes.

HOCH

  • H1 — Globaler Stop/Shutdown bricht Deferred-Post-Processing nicht ab. abortPostProcessing (download-manager.ts:7053) iteriert nur über packagePostProcessAbortControllers, nie über packageDeferredPostProcessAbortControllers. Bei Stop/Shutdown/clearAll laufen MKV-Move/Archiv-Cleanup/Rename weiter, während synchron persistiert wird → FS-Zustand ≠ Session-State (halb verschobene Datei, halb gelöschtes Archiv).
  • H2 — Hybrid-Post-Extract feuert MKV-Collection/Rename als losgelöstes Promise (download-manager.ts:11334). In keiner Tracking-Map, kein shouldAbort. Cancel/Reset während Hybrid-Collect → Dateien werden trotzdem verschoben/gelöscht.
  • H3 — 0-Byte-Datei wird als vollständig akzeptiert wenn keine Größeninfo (download-completion.ts:129, source "stream-end"). Hoster antwortet HTTP 200 ohne Content-Length + schließt sofort → Item "Fertig" mit leerer Datei, kein Auto-Redownload.

MITTEL

  • M1 — Deferred-Post-Extraction nicht in packagePostProcessTasks (download-manager.ts:11974). Scheduler-Abschluss (8154) + finishRun (12310) sehen Deferred-Tasks nicht → Run-Ende/Summary feuert während noch Dateien verschoben werden, State-Reset mitten in FS-Arbeit.
  • M2 — blockAllPersistence wird nach Backup-Import nie zurückgesetzt (app-controller.ts:678). Weiterarbeiten ohne Neustart → persistSoon ist dauerhaft No-Op → bei hartem Crash alle Änderungen weg.
  • M3 — cancelPendingAsyncSaves wartet nicht auf laufenden Async-Save (storage.ts:1064). I/O-Overlap beim Import (Datenintegrität durch Generation-Guard geschützt, nur Robustheit).

NIEDRIG

  • N1 — Toter Code in findReadyArchiveSets (download-manager.ts:10847). Unbedingtes ready.add+continue macht strengeren Disk-Fallback-Block (untracked-pending-Schutz) unerreichbar.

Empfehlung: H1 + H2 + M1 zusammen fixen (eine kohärente Härtung der Deferred-Pipeline). H3 ist klein & unabhängig. M2 trivial.


B. FEATURES / UX-GAPS (nach Mehrwert/Aufwand)

App läuft headless auf Windows-Server → Nutzer sitzt nicht davor. Größte Lücke: keine Benachrichtigungen.

  1. Webhook/Push-Benachrichtigungen (Discord/Telegram/ntfy) — SM. Bei Paket fertig/Fehler/Quota/Provider-down aufs Handy. Neuer notifier.ts, Hooks an Completion-Punkten. Höchster ROI.
  2. Fernsteuerung über bestehenden Debug-Server (POST-Endpunkte) — SM. Server hat schon HTTP + Token-Auth, aber nur GET. POST /control/add-links, /start, /stop → vom Handy steuern.
  3. URL-Duplikat-Erkennung beim Hinzufügen — S. History-urls existiert, wird aber nie geprüft → versehentliche Re-Downloads verschwenden Quota. Warnen: "3 Links bereits geladen".
  4. Vereinheitlichter Pre-Flight-Check + Bulk-Skip toter Links — M. Vor Start Größe/Name/Online für ganze Queue, "alle offline überspringen"-Button.
  5. Speicherplatz-Vorabprüfung vor Start — S. Aktuell keine Free-Space-Prüfung → Abbruch mitten im Download bei voller Platte.
  6. Konsolidierte Fehler-Ansicht — M. Alle fehlgeschlagenen Items flach + Fehlertext + "alle erneut versuchen".
  7. Per-Provider-Statistik — M. Rohdaten (providerTotalUsageBytes) existieren, werden nur nicht dargestellt. Welches Abo lohnt sich?
  8. Auto-Retry fehlgeschlagener Pakete nach Wartezeit — SM. Quota/Cooldown-Fails am nächsten Tag automatisch neu versuchen.
  9. Plex/Jellyfin Library-Refresh nach MKV-Move — S. Neue Folgen sofort sichtbar. Gleicher Hook wie #1.
  10. Watch-Folder für DLC/Link-Auto-Import — M. Ordner überwachen → automatisch importieren+starten.

C. DESIGN-MOCKUPS

4 Varianten in design-mockups/ (index.html = Vergleich):

  1. Aurora — verfeinerte Dark-Evolution (premium, vertraut, geringstes Risiko)
  2. Command — Terminal/Ops-Dashboard (max. Dichte, Monospace, Status-LEDs)
  3. Vellum — Light Editorial (warmes Papier, Serif, mutige helle Alternative)
  4. Nebula — Neon/Synthwave (Magenta-Cyan-Glow, auffällig)

→ Nutzer wählt Richtung (oder Mischung).


REVIEW / ERGEBNISSE (2026-05-23)

Umgesetzt (v1.7.158):

  • H1abortPostProcessing aborted jetzt auch alle Deferred- + Hybrid-Controller (globaler Stop/Shutdown/clearAll/external). Keine FS-Race gegen Shutdown-Save mehr.
  • H2 — Hybrid-Post-Extract läuft über neue packageHybridPostProcessControllers-Map (Set pro Package), Controller SYNCHRON vor dem detached Promise registriert, shouldAbort an Rename + MKV-Collect durchgereicht. abortPackagePostProcessing + clearAll räumen die Map. Cancel/Reset stoppt jetzt laufende Hybrid-Arbeit.
  • M1 — neuer hasAnyDeferredPostProcessPending(); Scheduler-Abschluss + finishRun-Clear gaten darauf. hasDeferredPostProcessPending (per-Package, für package_done-Cleanup) prüft jetzt auch Hybrid. Run endet erst wenn Background-FS-Arbeit fertig.
  • H3validateDownloadedFileCompletion: 0-Byte bei stream-endok:false (download_underflow), routet in den bestehenden Retry-Pfad. Regressionstest in tests/download-completion.test.ts (8 Tests).
  • N1 — toter Disk-Fallback-Block in findReadyArchiveSets + verwaiste pendingItemStatus-Map entfernt (verhaltensneutral).

Bewusst NICHT umgesetzt (mit Begründung):

  • M2 (blockAllPersistence nie zurückgesetzt) — GELÖST in v1.7.159 via Auto-Relaunch. In-Memory-Reload wäre unsicher (Task-finally-Blöcke settlen async gegen this.session.items[id] → Race beim Session-Swap, bräuchte async-Refactor). Stattdessen: nach erfolgreichem Import startet die App automatisch neu (main-getrieben in main.ts, nicht Renderer — robust gegen Renderer-Fehler). Der frische Prozess lädt die restored Session sauber via Standard-Startup-Pfad. skipShutdownPersist/blockAllPersistence schützen das ~1.5s-Fenster + den Quit (verifiziert: prepareForShutdown:5680 überspringt Persistenz sauber). Footgun eliminiert — User kann nicht mehr im blockierten Zustand weiterarbeiten.
  • ⏭️ M3 (cancelPendingAsyncSaves wartet nicht auf laufenden Save) — Report stuft selbst als reines I/O-Overlap ein; die Generation-Guard (storage.ts:1022) schützt die Datenintegrität bereits (stale Write wird verworfen). Kein Korrektheitsgewinn, daher kein Eingriff.

Verifikation: 30 Test-Dateien, 621 Tests grün. Build sauber. Advisor-Review vor Implementierung (fing H2-Falle: Hybrid-Controller nicht in die Deferred-Map legen, sonst killt runDeferredPostExtraction sie selbst).


D. DEFERRED-PFAD RENAME-GAP (2026-05-28, Opus-Verifikation von 18eada9)

Kontext: Eine abgestürzte Session (API 400 thinking-blocks) hinterließ ein uncommittetes Working-Tree, das drei releaste Commits revertierte (08372f9 Passwort + 18eada9 Hybrid-Rename + 98dc366 Support-Bundle, zurück auf v1.7.159). Kein dokumentierter Intent → als Crash-Debris bewertet, non-destruktiv gestasht (git stash — recoverable), HEAD/v1.7.162 wiederhergestellt.

Verifizierter Fund (Folge-Bug zu 18eada9):

  • 18eada9 schloss den "frische Datei landet unbenannt"-Bug nur für den Hybrid-Pfad (deferFreshFiles=true + Mehrfach-Pässe).
  • Der finale Deferred-Pass (runDeferredPostExtraction) macht Rename (12125) → Collect (12156, deferFreshFiles=false). Ist eine Datei beim Deferred-Rename noch frisch (< fileStabilizeMinAgeMs, prod=2000ms) — v.a. eine eben per Nested-Extraction (12045, unmittelbar davor) geschriebene Datei — überspringt der Frische-Gate sie, und der Collect moved sie mit Original-Scene-Namen in die Library. collectMkvFilesToLibrary benennt selbst nicht um (Move-Body: buildUniqueFlattenTargetPath, nur Flatten).
  • Pre-existierender Gap (Frische-Skip-Block älter als 18eada9); auch HEAD/v1.7.162 betroffen.

Gate (TDD, vor Fix): neuer Regressionstest "deferred final pass renames fresh files before collecting them" → reproduzierte den Bug zuverlässig gegen HEAD (Datei landete unbenannt).

Fix (minimal, Root-Cause): treatFilesAsStable-Param durch autoRenameExtractedVideoFiles(Impl). Im Deferred-Final-Pass (kein concurrent Extractor-Write mehr, Extraktion awaited) wird der Frische-Gate umgangen → alle Dateien werden umbenannt, bevor der Collect sie sammelt. Hybrid-Pfad unangetastet (nutzt ...Impl mit Default false → Frische-Skip bleibt aktiv, schützt weiter vor Rename mitten in concurrent Write).

Verifikation: neuer Test grün, Hybrid-Test grün (kein Regress), 623 Tests grün (31 Dateien), tsc unverändert (9 pre-existing). Advisor-Gate vor Fix (verlangte Repro-Test statt Timing-Argument).

Offen / bewusst nicht angefasst:

  • Gestashtes Crash-Debris (stash@{0}): enthält Revert von 08372f9/18eada9/98dc366 + log.old. Bei Bedarf inspizierbar/recoverbar; sonst irgendwann verwerfbar.
  • 08372f9 (Passwort-Daemon-Reset) bewusst nicht neu aufgerollt (außerhalb dieses Goals, kein Hinweis auf Defekt).
  • Untracked *-postprocess/ + fix-library-renames.mjs: alte Experimente (Apr/Mai), unverändert gelassen.

E. Mega-Debrid Account temporär deaktivieren (UI) — 2026-05-31

Goal: Einzelne Mega-Debrid-Accounts deaktivieren (statt löschen) → Rotation überspringt sie, nutzt die anderen.

Befund: Backend KOMPLETT vorhanden — megaDebridDisabledAccountIds (Typ/Defaults/Storage-Normalisierung) + Rotation-Skip (debrid.ts:1944) + Verfügbarkeits-Checks. Es fehlt NUR das UI-Toggle (Debrid-Link hat es bereits via keyStatsPopup). ID-Seam verifiziert: getMegaDebridAccountId(login) (trim+lowercase) ist auf beiden Seiten identisch → Test grün.

Ansatz (B-kohärent): Toggle in die bestehende Mega-Account-Liste im Bearbeiten-Dialog falten (draft-then-Save, KEIN Live-Persist → kohärent, minimal). Schritte:

  1. TDD: Test „skips a manually disabled Mega-Debrid account" (acc1 disabled → acc2) — grün gegen Backend.
  2. AccountDialogState: Feld megaDisabledIds: string[].
  3. Dialog-Init (createAccountDialogState): aus settings.megaDebridDisabledAccountIds.
  4. JSX Mega-Account-Liste: Aktivieren/Deaktivieren-Button + disabled-Styling pro Account.
  5. Entfernen-Handler: ID auch aus megaDisabledIds entfernen.
  6. buildSettingsFromDialog (megadebrid-api/web): megaDebridDisabledAccountIds aus Draft (gefiltert auf vorhandene Accounts) übernehmen.
  7. Verifizieren: tsc unverändert, volle Suite grün, Toggle-Test grün.