Live aus conversion.log belegt: bei API-first + maxParallel hammerten viele
parallele Umwandlungen DENSELBEN Account → Mega-Debrids 1-Token-pro-Account-
Regel → "Token error, please log-in" (Token gegenseitig invalidiert). Ein
einzelner generischer Null-getLink wurde dann als "Login oder Unrestrict
fehlgeschlagen" geworfen → classifyAccountFailure matchte das Wort "Login" →
category=invalid → 60-MINUTEN-Cooldown auf einen NACHWEISLICH funktionierenden
Account (39x OK davor, klappt auch auf der Webseite). Folge: alle Accounts
cooled, alle Slots haengen an Fehl-Umwandlungen (conv12/dl0), nichts laedt.
Fix A (download-manager): getSerializedValidatingLimit gilt jetzt auch fuer
megadebrid-api (nicht nur -web) = Anzahl nutzbarer Accounts ohne :api-Cooldown.
Damit laeuft hoechstens EINE API-Umwandlung pro Account gleichzeitig (Rotation
verteilt 1/Account) — respektiert die 1-Token-Regel, verhindert die Token-
Kollision und gibt Download-Slots frei.
Fix B (debrid): (1) generische Wurf-Meldung "Login oder Unrestrict
fehlgeschlagen" -> "Linkgenerierung lieferte kein Ergebnis" (kein "Login"-
Trigger mehr). (2) classifyAccountFailure: "token error"/"please log-in" ->
kurzer temporary-Cooldown (15s); invalid-Branch nur noch bei ECHTEN
Credential-Fehlern (bad login/incorrect password/invalid credentials/
unauthorized/forbidden/connectUser) statt losem login|auth. Ein transienter
Fehler sperrt einen guten Account nicht mehr 60 min, sondern hoechstens Sekunden.
Tests: API-Serialisierung 2-Acct->2-parallel + 1-Acct->1-at-a-time; kein
60-min-invalid bei Null-Ergebnis; token-error<60s. 844/844 gruen, tsc=6.
buildSupportBundle lief komplett synchron auf dem Electron-Main-Thread:
existsSync/readdirSync/statSync/readFileSync (via AdmZip.addLocalFile) ueber
ALLE Log-Dateien — bei Hunderten item-logs und gleichzeitig laufenden
Downloads (Platte ausgelastet) blockierte das den Event-Loop fuer 1-2 min,
die ganze Oberflaeche stand. Live belegt: ein Bundle mit Hunderten
item-logs-Dateien.
Fix: Alle Datei-Zugriffe im Bundle-Build auf fs.promises (nicht-blockierend)
umgestellt, buildSupportBundle ist jetzt async und liefert Promise<Buffer>.
Dateiinhalte werden per readFile gelesen und mit zip.addFile(buffer)
hinzugefuegt (gleiche Zip-Struktur wie vorher). Der Event-Loop bleibt
waehrend der IO-Wartezeiten frei → UI friert nicht mehr ein. Caller
angepasst (app-controller.exportSupportBundle async, main.ts await,
debug-server via .then).
Test support-bundle.test.ts: valides Zip mit Overview + echter Datei;
ein paralleler Timer feuert waehrend des Builds (Event-Loop nicht blockiert).
840/840 gruen, tsc unveraendert (6), Build ok.
Bewusst NUR Diagnose, KEINE Verhaltensaenderung — damit das naechste
Support-Bundle das echte aktuelle Verhalten zeigt und der naechste Fix
die Ursache trifft statt zu raten (live belegt: "Unrestrict Timeout nach
60s" R53/R64, API-"Token error, please log-in" kuehlt beide Accounts ab).
Neues Modul conversion-trace.ts: AsyncLocalStorage-Trace, der einen
unrestrict-Versuch ueber alle Schichten begleitet und EINEN strukturierten
Block pro Versuch nach conversion.log schreibt (Datei-Infra wie
account-rotation-log: Rotation bei 5 MB, 14 Tage Retention; im Support-
Bundle). tracePhase ist no-op ohne aktiven Trace — additiv, null Risiko.
Instrumentiert wird die Kern-Blindstelle der bisherigen Logs:
- download-manager Boundary: runWithConversionTrace + Slot-Belegung
(conv/dl/active/max) + Caller-Timeout-Attribution (was lief, als die 60s
feuerten).
- Provider-Kette (debrid): chain-try/ok/failed/aborted — zeigt, ob der
Web->API-Failover ueberhaupt feuert oder vom globalen Abbruch gekappt wird.
- Mega-Rotation: mega-account mit workMs + outcome (ok/failed/fatal/aborted)
+ Cooldown je Account.
- API-Token-Lifecycle: token cached/pending-join/fresh-login + connectMs,
getLink response_code/text — klaert die Herkunft von "Token error".
- Mega-Web runExclusive: web-queue mit queueWaitMs UND workMs getrennt —
klaert, ob die 60s Warten in der Queue oder echte langsame Arbeit sind.
Test conversion-trace.test.ts (Formatter + ALS-Kontext-Propagation).
838/838 gruen, tsc unveraendert (6 Baseline), Build ok.
getAvailableMegaDebridAccounts filtert disabled + Tageslimit, aber NICHT
Runtime-Cooldowns. Die Rotation (debrid.ts) ueberspringt einen Account
im Cooldown jedoch — ohne diesen Filter koennte der Scheduler also eine
Web-Umwandlung zu viel zulassen, die sich dann auf einen noch brauchbaren
Account stapelt und einen Download-Slot bis zum 90s-Queue-Timeout haelt.
getSerializedValidatingLimit zaehlt jetzt nur Accounts ohne aktiven
`${id}:web`-Cooldown, sodass die Admission exakt der tatsaechlichen
Rotation-Verfuegbarkeit entspricht.
Test: 2 Accounts, einer im Web-Cooldown -> nur 1 Web-Umwandlung
gleichzeitig (kein Over-Admit). afterEach setzt jetzt auch den
Mega-Runtime-State zurueck (Cooldowns/Cursor/In-Flight) als Hygiene.
R3 (Kern): Transiente Resolve-Fehler ("Datei beim Hoster gerade nicht
abrufbar", frz. "Fichier supprimé") werden jetzt mit kurzem Delay
(3–6–10s) neu versucht statt mit dem 5s..120s-Exponential. Ein solcher
Einzel-Fehler zählt nicht mehr zum Provider-Circuit-Breaker (der bei 20
Fehlern 30–300s Cooldown setzt und die Mega-Session invalidiert) und
löst keine Provider-Cooldown-Inflation aus — dadurch bremst er die
gesunden Links nicht mehr aus. Neuer Branch vor der generischen
Unrestrict-Retry-Logik; Detektor isMegaDebridTransientResolveFailure
matcht sowohl die rohe französische als auch die gerenderte deutsche
Meldung (am Download-Manager kommt die klassifizierte deutsche an).
R2 (parallel): getSerializedValidatingLimit für megadebrid-web ist nicht
mehr hart 1, sondern die Anzahl nutzbarer Accounts. Der harte Wert 1
stammte aus der Einzel-Account-Zeit (Commit a3e3d6f). Sicher, weil die
Web-Ebene ohnehin pro Account serialisiert (runExclusive, key=login) und
die Account-Rotation gleichzeitige Aufrufe atomar auf distinkte Accounts
verteilt (megaDebridInFlight wird synchron vor dem await erhöht). Ein
Account → Limit 1 (keine Änderung).
R4 (Sichtbarkeit): Der echte Grund steht inline im Status und als Tooltip
(item.lastError); kaputten "?"-Separator zu "(Rn)" gesäubert.
UI-Bugfixes: Account-Duplizierung beim Entfernen (React-Key-Kollision
API/Web → rowKey nach kind), Username-Spalte zeigt eingegebenen Login
statt Anbieter-Mail (Mail als Tooltip).
Tests: transientResolveRetryDelayMs, isMegaDebridTransientResolveFailure,
2 behaviorale Download-Manager-Tests (schneller Re-Try mit deutschem
Grund; 1 Web-Umwandlung pro Account parallel). 833/833 grün, tsc
unverändert.
v1.7.210 hat den franzoesischen Mega-Debrid-Fehler "Fichier supprime chez
l'hebergeur" als permanent toten Link behandelt (Item sofort gescheitert, kein
Web-Fallback). Die Support-Bundle-Logs widerlegen das: von 18 Links mit diesem
Fehler haben 4 Sekunden spaeter ein OK geliefert -- 1x ueber Web, 3x ueber
API-Retry (7-66s). Der Fehler ist TRANSIENT; v1.7.210 haette diese erholbaren
Links faelschlich dauerhaft gekillt und den rettenden Web-Fallback abgeklemmt.
Korrektur:
- classifyAccountFailure: "supprime"/"introuvable" -> temporaer, cooldownMs 0,
NICHT fatal. Der eigentliche Stall-Verursacher (30s Account-Cooldown, der bei
nur einem Account auch alle gesunden Links via SKIP_COOLDOWN blockierte) ist
damit weg, OHNE die erholbaren Links zu verlieren.
- isPermanentLinkError: franzoesische Phrasen wieder entfernt (nur echte
permanente engl. Signale fuer RD/AllDebrid bleiben).
- Provider-Kette + interner Web-Fallback: Kurzschluss entfernt -> Web-Fallback
wird wieder versucht (hat 1/4 der Links gerettet).
- Pacing kommt vom bestehenden 5s-Exponential-Backoff (unrestrictDelayMs) pro
Item, nicht vom Account-Cooldown -> weit unter Mega-Debrid 50 req/s, kein
IP-Ban-Risiko.
- Anzeige: deutsche, transient formulierte Meldung "Datei beim Hoster gerade
nicht abrufbar" statt franzoesisch und statt "tot".
- shared/dead-link.ts -> shared/mega-debrid-errors.ts, Semantik korrekt benannt.
Tests: tests/mega-debrid-errors.test.ts + behavioraler Test in debrid.test.ts
(supprime -> kein Account-Cooldown, deutsche Meldung, retrybar). Volle Suite
822/822 gruen.
Behebt die Queue-Quervergiftung; macht echte tote Links bei retryLimit 0 NICHT
aufgeben (vorbestehend, unveraendert). Parallel-Umwandlung folgt separat.
Mega-Debrid liefert bei geloeschten Hoster-Dateien franzoesische Fehlertexte
("Fichier supprime chez l'hebergeur"). Diese wurden weder in classifyAccountFailure
(debrid.ts) noch in isPermanentLinkError (download-manager.ts) erkannt und landeten
im generischen "temporaer"-Zweig: 30s Account-Cooldown + (bei retryLimit 0) endlose
Wiederholung. Mit nur einem Account (als API UND Web) blockierte ein einziger toter
Link ueber den Cooldown auch alle gesunden Links (SKIP_COOLDOWN) -- die Queue stand.
Beleg aus dem Support-Bundle: 479 von 983 Zeilen im Rotations-Log waren dieser Fehler.
- shared/dead-link.ts: zentrale Tot-Link-Erkennung (frz./dt./engl.), eine Quelle fuer
beide Schichten, damit die Muster nicht auseinanderdriften.
- classifyAccountFailure: toter Link -> fatal/skip, KEIN Account-Cooldown (Account ist
gesund, nur die Datei ist weg).
- isPermanentLinkError: toter Link -> Item sofort als gescheitert markiert, kein
Endlos-Retry. Greift auch im aggregierten Provider-Ketten-Fehlerstring.
- Provider-Kette + interner Web-Fallback: toter Link -> kein Fallback auf weitere
Provider/Modi (spart den 60s-Web-Timeout; saubere Meldung erreicht isPermanentLinkError
unveraendert).
- Anzeige: deutsche Klartext-Meldung "Link tot - Datei beim Hoster geloescht" statt frz.
- Account-Liste: Hinweis, dass Mega-Debrid API + Web derselbe Account in zwei Modi sind.
Tests: tests/dead-link.test.ts (inkl. aggregierter Provider-Ketten-String + akzentfreie
Variante). Volle Suite 823/823 gruen.
User-Wunsch: die Links eines Pakets parallel umwandeln statt seriell (single-flight),
damit sich die Download-Slots schneller fuellen. Gewaehlt: parallel ueber mehrere
Accounts (ein Login je Account laeuft parallel), NIE zwei gleichzeitig auf demselben
Account (Mega-Debrid-Sperr-Risiko).
- MegaWebFallback: globale Einzel-Warteschlange (this.queue) -> Warteschlange PRO Account
(this.queues: Map<login, Promise>). Gleicher Account serialisiert (kein Doppel-Login,
kein Hammern), verschiedene Accounts parallel. Key vor runExclusive berechnet.
- debrid.ts unrestrictWithAccounts: megaDebridInFlight zaehlt die LAUFENDE Tiefe pro
Account (Map<`${id}:${mode}`, number>). Kandidaten werden nach aufsteigender Tiefe
stabil sortiert (cursorOrder als Gleichstand-Tiebreak): gleichzeitige Aufloesungen
greifen den am wenigsten belegten Account -> auch bei mehr Links als Accounts
gleichmaessige Verteilung (4 Accounts, 8 parallel -> 2 je Account), statt sich hinter
dem Cursor-Account zu stauen. Sequenziell (alles Tiefe 0) bleibt es klebrig beim warmen
Account. add/inc vor dem try, dec/cleanup im finally (kein Leak).
- classifyAccountFailure: "Queue-Timeout" (lokaler Eigen-Stau) gibt jetzt cooldownMs:0 —
der warme Account wird nicht mehr faelschlich fuer Eigen-Stau mit Cooldown bestraft.
Vor Release adversarial per Multi-Agent-Workflow geprueft (4 Lenses + Verify). Der Review
fand genau die Ueberzahl-Stau-Schwaeche (binaeres belegt/frei staute >Accounts-Links hinter
einem Account) — daraufhin auf Tiefen-Zaehlung umgestellt. Sperr-Risiko strukturell
ausgeschlossen (per-Account-Queue serialisiert unabhaengig vom Set). 4 neue Tests
(gleicher Account 1 Login, verschiedene Accounts parallel, 4 gleichzeitig->4 Accounts,
8/2-Ueberzahl->4/4 ausgewogen). 813 Tests, tsc=6, self-check + build ok.
Regress aus v1.7.197/198: Das Round-Robin wechselte bei JEDEM Link den Account,
und die Latenz-Demotion (v1.7.198) stufte einen Account direkt nach seinem
zwangslaeufig langsamen KALTEN Login als "langsam" ein und rotierte weg — Mega-Web
cacht Sessions aber pro Account (~20 Min). Ergebnis: jeder Link zahlte einen kalten
Login in die serielle Single-Flight-Queue → minutenlanger Vorlauf, bevor die 8
parallelen Downloads anliefen. Vorher (First-Wins) lief alles ueber EINEN warmen
Account → schnell.
Fix:
- Latenz-Demotion (EMA-Sortierung) komplett entfernt — sie war die Ursache des
Kalt-Login-Teufelskreises (jeder Account galt nach seinem ersten Login als
langsam und wurde weggedraengt).
- Rotation jetzt KLEBRIG: gestartet wird beim Cursor (zuletzt erfolgreich genutzter,
warmer Account); der Cursor wird im Erfolgszweig nur weitergesetzt, wenn der
Schwung MEGA_DEBRID_STICKY_LINKS (25) erreicht ist — sonst bleibt er auf dem
Account. Aufeinanderfolgende Links laufen so auf demselben warmen Account
(schnell). Limit/Cooldown/Fehler ueberspringen den Account weiterhin und die
Rotation klebt dann am naechsten. Ueber die Zeit (alle 25 Links bzw. bei
Limits) kommen weiterhin alle Accounts dran — Account 4 inklusive.
Tests: 4 alte Round-Robin-/Demotions-Tests durch 3 klebrige ersetzt (bleibt auf 1
Account ueber 5 Links; wechselt erst nach 25 Links; ueberspringt gesperrten und
klebt am naechsten). 809 Tests, tsc=6 Baseline, self-check + build ok.
User-Wunsch: in der Account-Liste auf "Status" klicken und nach Premium-Restlaufzeit
sortieren (laengste<->kuerzeste). Da die Accounts pro Anbieter gruppiert sind, wird
INNERHALB jeder Gruppe sortiert (dort sitzen die Premium-Tage).
- Neuer Zustand accountStatusSort (none -> desc -> asc -> none) per Klick auf den
Status-Spaltenkopf (mit ▼/▲-Indikator).
- groupedAccountRows sortiert bei desc/asc jede Gruppe nach premiumUntilMs:
Premium-Accounts nach Restlaufzeit (desc = laengste zuerst, asc = kuerzeste
zuerst), Accounts ohne Premium/ohne Status bleiben am Ende.
Reine Renderer-Aenderung. tsc=6, build ok.
User-Wunsch: die Traffic-Werte (z.B. "Unbeschraenkt") in der Account-Liste
zentriert statt linksbuendig. text-align center fuer die Traffic-Spalte (Kopf +
Werte). Reine CSS-Aenderung. build ok.
Der In-App-Update-Dialog kuerzte den Changelog selbst: eine "compactLines"-Logik
schnitt jede Zeile NACH dem Doppelpunkt ab, wenn dahinter mehr als 60 Zeichen
standen. Aus "- Account-Liste: <lange Beschreibung>" wurde so nur "- Account-Liste:".
Zusaetzlich wurden eingerueckte Unterpunkte und Leerzeilen verworfen.
Fix: Die Kuerzungs-Logik entfernt. Der Changelog wird jetzt vollstaendig
uebernommen (nur Markdown-Sternchen/Backticks und ATX-Ueberschriften werden
gestrippt, 3+ Leerzeilen auf eine reduziert). Die Anzeige-Box bricht ohnehin um
(white-space: pre-wrap) und scrollt (max-height) — es wird also der ganze Text
lesbar dargestellt.
Hinweis: Der Fix greift fuer Update-Fenster AB der naechsten Version; das Fenster
fuer dieses Update wird noch von der alten (kuerzenden) Version gerendert.
Reine Renderer-Aenderung. tsc=6, build ok.
Kopfzeile und jede Datenzeile waren eigene CSS-Grids mit inhaltsabhaengigen
Spaltenbreiten (minmax(px, fr) + auto) — dadurch ergaben sich pro Zeile leicht
unterschiedliche Spaltenbreiten und die Werte (z.B. "Unbeschraenkt") standen nicht
unter ihrer Spaltenueberschrift, sondern nach links verschoben.
Fix: feste, inhaltsunabhaengige Spaltenverhaeltnisse minmax(0, Nfr) (plus fixe
Checkbox- und Aktions-Spalte). Dadurch loesen alle Grids identische Spaltenbreiten
auf und Kopf + Zeilen + Gruppen-Koepfe fluchten exakt. Zellen min-width:0 +
Ellipsis; Aktions-Spalte overflow sichtbar.
Reine CSS-Aenderung. build ok.
Single-Token-Anbieter (Real-Debrid, AllDebrid, 1Fichier, DDownload, LinkSnappy,
BestDebrid) haben keine Status-Pruefung. Bisher stand bei ihnen ein gruenes
"Konfiguriert"-Badge in der Status-Spalte, das wie "geprueft & ok" aussah, obwohl
nichts geprueft wurde. Jetzt zeigen sie dort ein dezentes graues "—" (Tooltip:
"Fuer diesen Anbieter gibt es keine Status-Pruefung"). Gruen/gelb/rot bleibt den
tatsaechlich geprueften Mega-Debrid-/Debrid-Link-Accounts vorbehalten.
Reine Renderer-Aenderung. tsc=6, build ok.
User-Idee: Anbieter mit mehreren Accounts zusammenfassbar machen, platzsparend.
- Anbieter mit >= 2 Accounts (Mega-Debrid, Debrid-Link) bekommen einen klickbaren
Gruppen-Kopf "<Anbieter> · N Accounts" mit Chevron zum Ein-/Ausklappen; Einzel-
account-Anbieter bleiben normale flache Zeilen.
- Gruppen-Kopf zeigt eine Mini-Status-Zusammenfassung als Badges: "X OK" (gruen),
"Y Problem" (rot, ungueltige Logins), "Z aus" (deaktiviert).
- Standard ausgeklappt (Status bleibt auf einen Blick sichtbar); Klapp-Zustand wird
pro Anbieter in localStorage gemerkt (rd-account-collapsed-groups-v1).
- Mitglied-Zeilen leicht eingerueckt mit Akzent-Leiste. Zebra-Streifen entfernt
(mit Gruppen-Koepfen unruhig).
- Zeilen-Rendering in renderAccountRow ausgelagert (fuer flache + Gruppen-Mitglieder
wiederverwendet); neues groupedAccountRows-useMemo gruppiert accountRows nach Service.
Reine Renderer-Aenderung. 810 Tests gruen, tsc=6, build ok.
Account-Rework (User-Wunsch nach JDownloader-Vorlage-Screenshot):
- Tabelle zeigt jetzt EINE ZEILE PRO EINZELNEM ACCOUNT statt eine Zeile pro
Anbieter. Mega-Debrid mit 4 Accounts = 4 Zeilen, Debrid-Link je Key eine Zeile,
Single-Token-Anbieter je eine Zeile. Neues geflachtes Modell accountRows
(useMemo) aus configuredAccounts: Mega ueber parseMegaDebridAccounts, DL ueber
entry.debridLinkKeys, Rest 1:1.
- Spalten wie JDownloader: aktiviert-Checkbox | Hoster (+Modus) | Download-Traffic
(Rest von Limit "X von Y uebrig" bzw. "Unbeschraenkt") | Status (farbig) |
Benutzername | Verfallsdatum | Aktion (Bearbeiten/Entfernen).
- STATUS INLINE + farbig (gruen Premium / gelb Free / rot ungueltig / grau nicht
geprueft / Deaktiviert) direkt in der Zeile, ohne erst "Bearbeiten" zu oeffnen.
Quelle: settings.debridAccountStatuses[accountId] (Mega/DL werden geprueft),
Benutzername aus status.email, Verfallsdatum aus premiumUntilMs. Single-Token-
Anbieter (Real-Debrid, AllDebrid, 1Fichier, ...) werden nicht geprueft -> "Konfiguriert".
- Problemzeilen (Login ungueltig) rot hinterlegt, deaktivierte Zeilen ausgegraut.
- aktiviert-Checkbox schaltet den EINZELNEN Account: Mega via neuem
onToggleMegaAccountEnabled (megaDebridDisabledAccountIds), DL via bestehendem
onToggleDebridLinkApiKeyEnabled, Single via onToggleAccountEnabled. Entfernen je
Account: Mega/DL ueber neue Handler (Zeile aus megaCredentials/debridLinkApiKeys
raus), Single ueber onRemoveAccount.
- PRUEFUNG BEIM HINZUFUEGEN: onSaveAccountDialog stoesst nach dem Speichern
checkAllAccounts() an -> Status erscheint sofort in der Liste + Toast meldet
"X/Y Login gueltig, Z Premium" (Speichern bleibt erlaubt, wie gewuenscht).
- Hoster-Reihenfolge- und Rotations-Verlauf-Panel unveraendert darunter. Alte
resizable Spalten + "Zugang einzeln"-Toggle entfernt (durch Zeilen ersetzt).
Web-Login-/AllDebrid-Status-Aktion bleibt als Knopf in der Single-Zeile.
- account-validity-badge.ok von Neon-Verlauf auf flaches Gruen, .disabled ergaenzt.
Reine Renderer-Aenderung. 810 Tests gruen, tsc=6 Baseline, build ok.
Erste Version nach Screenshot — Feinschliff der Optik nach User-Feedback.
Settings-Rework (User-Wunsch, mit Multi-Agent-Design-Runde fuer die Taxonomie:
3 Lenses -> Synthese, gegen das echte settingsDraft-Inventar auf Vollstaendigkeit
geprueft, 51 Einstellungen je genau einmal platziert).
UI (App.tsx + styles.css):
- 5 der 6 Bereiche (Allgemein/Entpacken/Geschwindigkeit/Bereinigung/Updates) in
klare Untergruppen mit Unter-Ueberschriften (.settings-subhead) gegliedert, je
Bereich ein Intro, und unter JEDER Einstellung ein knapper Erklaertext
(.setting-hint, <= ~90 Zeichen, echte Umlaute). Reihenfolge nach Aufgaben-Logik
(z.B. Allgemein: Speicherort / Download-Verhalten / Verlauf / Oberflaeche /
Discord; Entpacken: Ziel & Ablauf / Deutsche Tonspur / Ablageform / Leistung /
Passwoerter). Einige Labels praezisiert (z.B. "Codeberg Repo" -> "Update-Quelle",
"Light Mode" -> "Heller Modus"). Account-Bereich bleibt fuer Phase 3 unangetastet.
Verlauf-Retention konfigurierbar (vorher: hart 500, kein Zeitlimit):
- Neue Settings historyMaxEntries (Standard 500) + historyMaxAgeDays (Standard 0=aus)
in types/constants/normalizeSettings (geclamped 50..100000 bzw. 0..3650) + App-
Default-Snapshot. Zwei neue Zahlenfelder im Bereich Allgemein -> Verlauf, direkt
unter "Verlauf speichern"; ausgegraut wenn nicht "Dauerhaft".
- storage.ts: pruneHistoryEntries(entries, limits) wendet Alters- (completedAt <
jetzt - Tage) und Anzahl-Grenze an; load/save/addHistoryEntry + die *ForRetention-
Wrapper nehmen optionale Limits. app-controller reicht die Limits aus den Settings
durch (historyLimits()) und schneidet den Verlauf bei Aenderung der Grenzen aktiv
neu (damit "aelter als X Tage" wirklich von der Platte fliegt).
2 neue storage-Tests (Anzahl-Cap, Alters-Pruning). 810 Tests gruen, tsc=6 Baseline,
self-check + build ok.
Folge-Fix zu v1.7.197 (Round-Robin): User meldete direkt nach dem Update
deutlich langsamere Link-Umwandlung. Ursache: MegaWebFallback.runExclusive
ist eine GLOBALE Single-Flight-Queue — alle Umwandlungen laufen seriell.
Vor v1.7.197 liefen praktisch alle Links ueber denselben warmen, schnellen
Account (~800ms); das Round-Robin mischte nun auch die zuvor nie genutzten
Accounts in die Reihe. Ist einer davon langsam (kalte Session, traegere
Server, abgelaufenes Premium), blockiert seine Umwandlung in der seriellen
Queue ALLE nachfolgenden Links — gefuehlt wird alles langsam.
Fix: Pro Account+Modus wird ein EMA (0.7/0.3) der erfolgreichen
Unrestrict-Dauer gefuehrt. Bei jeder Aufloesung wird die Round-Robin-
Reihenfolge partitioniert: Accounts, deren EMA ueber
max(6s, 3x bestes verfuegbares EMA) liegt, wandern ans Ende der Reihe —
sie bleiben Failover-Reserve (werden weiter genutzt, wenn die schnellen
am Limit/Cooldown sind), laufen aber nicht mehr in der gleichmaessigen
Verteilung mit. Ungemessene Accounts gelten als gesund (bekommen ihre
Chance und damit ein EMA). Erholt sich ein Account (relativer Threshold),
rotiert er automatisch wieder mit. Schon nach EINEM langsamen Erfolg ist
ein Bremser-Account aus der Verteilung draussen. Fehlschlaege werden wie
bisher ueber die bestehenden Cooldowns behandelt.
Diagnose-Sichtbarkeit: das TEST-Event im account-rotation.log traegt jetzt
emaMs und slow=true, damit ein gebremster Account sofort erkennbar ist.
2 neue Regressionstests (langsamer Account wird depriorisiert; bleibt
Failover-Reserve wenn die schnellen gesperrt sind). Suite 808 gruen,
tsc=6 Baseline, self-check + build ok.
Symptom (User): 4 Accounts hinterlegt, aber die Rotation nutzte nur die
Accounts 1-3 — der 4. wurde nie angefasst, ausser man deaktivierte die
anderen manuell.
Ursache (im Live-Log des Servers belegt, rd_downloader.log): Die
Account-Schleife in unrestrictWithAccounts startete bei JEDER
Link-Aufloesung bei Account 1 und nahm den ersten brauchbaren
(First-Usable-Wins-Failover). Ein spaeterer Account kam nur dran, wenn
ALLE davor am Tageslimit/Cooldown/deaktiviert waren. Live-Verteilung:
Account 1 = 749 OK + 1314x "Tageslimit — bis Neustart gesperrt",
Account 2 = 1733 OK + 448x Tageslimit-Sperre, Account 3 = 1603 OK ohne
ein einziges Limit — die Kette endete deshalb IMMER spaetestens bei
Account 3, und Account 4 tauchte im gesamten Log mit keinem einzigen
Event auf (nicht getestet, nicht geskippt). Accounts 1-2 liefen also
staendig ins Tageslimit, waehrend die Kapazitaet von Account 4 jeden
Tag verfiel.
Fix: Round-Robin-Cursor in unrestrictWithAccounts. Jede Aufloesung
startet beim Account NACH dem zuletzt getesteten (Modulo ueber die
Liste), alle bestehenden Checks (deaktiviert, lokales Tageslimit,
Cooldown, Park-bis-Neustart) bleiben unveraendert und werden in der
neuen Reihenfolge durchlaufen. Damit verteilen sich Links gleichmaessig
ueber alle Accounts, kein Account wird mehr stumpf bis ans Limit
gehaemmert, und Account 4 nimmt automatisch teil. Der Cursor ist
Modul-State (bei App-Neustart wieder Account 1), die "naechster
Account"-Vorschau im Fehlerpfad folgt der Modulo-Reihenfolge, und
resetMegaDebridRuntimeStateForTests setzt ihn fuer deterministische
Tests zurueck. Die Debrid-Link-Key-Rotation (separate Schleife) bleibt
unveraendert First-Wins — dort ist kein Account konfiguriert; bei
Bedarf gleiches Muster nachziehen.
Nebenfix: Das Support-Bundle exportiert jetzt auch
logs/account-rotation.log (+ .old) — genau dieses Log fehlte im Bundle
und haette die Diagnose sofort geliefert (die Datei kollidiert
namentlich mit dem gleichnamigen Log des Multi-Hoster-Uploaders, daher
war zunaechst das falsche Log in der Analyse).
Tests: 2 neue Regressionstests (5 Links auf 4 Accounts -> 1,2,3,4,1;
Cooldown-Account wird in der Reihe uebersprungen), Suite 806 gruen,
tsc=6 Baseline, self-check + build ok.
Symptom: Bei manchen Episoden (z.B. Desperate Housewives S03E08/E11/E17)
blieb der .DL.-Marker stehen UND beide Tonspuren (DE+EN) erhalten — obwohl
keepGermanAudioOnly aktiv war und alle anderen Episoden des Pakets sauber
verarbeitet wurden. Die Vermutung "langer Titel" war falsch; die betroffenen
Episoden hatten gleich lange Namen wie die sauberen.
Ursache (im Support-Bundle bewiesen): genau die 3 Episoden hatten null
"Tonspur-Bereinigung OK"-Zeilen. Im Hybrid-Modus laeuft der Entpacker
SEPARAT von der per-Paket-Kette (autoRename -> keepGermanAudio -> collect).
Waehrend ein langsamer Remux einer anderen Episode (1,5 GB, ~3 s) lief,
entpackte der Extraktor frische .DL.-MKVs in denselben extractDir — also
NACH dem Datei-Scan von keepGermanAudio, aber bevor der Ketten-Abschluss-
collect lief. collectMkvFilesToLibrary griff sie dann mit .DL. ab und
verschob sie in die MKV-Bibliothek, bevor ihre eigene Tonspur-Bereinigung
je lief. Einmal aus extractDir heraus, fand der finale Deferred-
keepGermanAudio sie nicht mehr. Das 2000-ms-Frische-Gate schuetzte nicht,
weil die Dateien beim collect schon ~2,3 s alt waren.
Fix: collectMkvFilesToLibrary haelt im Hybrid-Lauf (deferFreshFiles=true)
bei aktivem keepGermanAudioOnly jede remuxbare Datei (.mkv/.mp4) zurueck,
die noch den .DL.-Marker traegt — sie hat die Tonspur-Bereinigung sicher
noch nicht durchlaufen. Sie bleibt in extractDir und wird von einer
spaeteren Hybrid-Runde ODER dem finalen Deferred-Pass (erst keepGermanAudio,
dann collect mit deferFreshFiles=false) bereinigt + gesammelt. Praezise auf
remuxbare Dateien begrenzt: eine .DL.avi ruehrt keepGermanAudio nie an, also
darf sie nicht haengen bleiben. Der Deferred-Pass (deferFreshFiles=false)
ist NICHT betroffen — dort darf eine legitim doppeltonige Datei (kein
DE-Track) weiterhin mit .DL. gesammelt werden, statt fuer immer liegen zu
bleiben.
Regressionstest tests/hybrid-collect-race.test.ts (3 Faelle): Hybrid haelt
remuxbare .DL.-Datei zurueck (sammelt bereinigte mkv + nicht-remuxbare
.DL.avi trotzdem), Deferred sammelt sie, keepGermanAudioOnly aus haelt
nichts zurueck.
Audit-Befunde R1, R2:
- Nach erfolgreichem Test mit ungespeicherten Einstellungen sagt der Toast
jetzt "Einstellungen jetzt noch speichern!" — vorher konnte man testen
(klappt!), nie speichern, und nach dem naechsten App-Neustart/Auto-Update
feuerte kein einziger Webhook, weil notifyUrl nie persistiert war.
- Button ist waehrend einer laufenden Quick-Action deaktiviert statt den
Klick still zu verschlucken (performQuickAction returnt sonst kommentarlos).
Audit-Befunde SET-2, SET-4:
- notifyUrl in die SENSITIVE_KEYS des Backup-Imports aufgenommen: ein mit
"***" maskierter Wert (haendisch redigierte Backup-Datei) wird wieder durch
den aktuellen ersetzt statt als kaputte URL persistiert zu werden — die
Webhook-URL ist ein Capability-Secret wie die Tokens.
- Support-Bundle enthaelt jetzt einen notifications-Block (konfiguriert?,
URL-Format plausibel?, welche Toggles an) — "warum kam kein Webhook" ist
damit aus einem Bundle diagnostizierbar, ohne die URL selbst zu leaken.
Audit-Befunde RUN-1 bis RUN-4:
- RUN-1: Manueller Stop beendete den Durchlauf, ohne dass je eine Run-Summary
kam (der Scheduler bricht an der while-Bedingung ab, finishRun wird nie
erreicht). stop() schickt jetzt "Durchlauf gestoppt" mit den bis dahin
gesammelten Zahlen (nur wenn der Run lief; beim Restart/Shutdown-Pfad
unterdrueckt — der Prozess stirbt gleich).
- RUN-2: Stop->Start innerhalb des Scheduler-Sleeps (~120-220ms) liess den
neuen Run fuehrerlos zurueck: ensureScheduler returnte (scheduleRunning noch
true), die alte Schleife exitete auf Generation-Mismatch — danach lief KEIN
Scheduler mehr, obwohl running=true: keine Downloads, kein finishRun, kein
Webhook, und erneutes Start() heilte nichts (early-return wegen running).
Die finally respawnt jetzt den Scheduler, wenn der Run aktiv ist und die
Generation weitergezogen wurde.
- RUN-3: Geplanter Start ueberlebte keinen App-Neustart (Timer lebte nur im
Prozess, Setting blieb stehen) — Auto-Update/Reboot verschluckte den
geplanten Run still. armScheduledStart extrahiert, beim Boot re-armt;
vergangene Zeit beim Boot wird geloggt+geleert statt blind zu starten
(Konflikt mit autoResumeOnStart-Gate).
- RUN-4: "Durchlauf beendet" feuerte in der Default-Konfiguration
(autoExtractWhenStopped) waehrend das Entpacken noch lief. Titel sagt jetzt
"Downloads beendet" + Hinweis "Entpacken laeuft noch — Paket-Meldungen
folgen", wenn Post-Processing aussteht.
Audit-Befunde DEDUP-1 (HIGH), DEDUP-3, DEDUP-4, NOTIFY-DEDUP-BEFORE-CONFIRM:
- DEDUP-1: recoverRetryableItems (Auto-Requeue gefailter Items beim Start)
loescht jetzt den notifiedPackages-Eintrag der betroffenen Pakete. Vorher
blieb der Failed-Marker aus Run 1 kleben — die Erfolgs-Benachrichtigung nach
geglueckter Recovery in Run 2 (genau die Nachricht, auf die man wartet) kam
nie; in Discord blieb das Paket fuer immer ❌.
- DEDUP-3: retryExtraction/extractNow loeschen den Dedup-Marker und treten dem
Run UNBEDINGT bei (runPackageIds.add) — der korrigierende ✅ nach manuell
wiederholtem Entpacken kam sonst nie an, v.a. wenn der Run schon vorbei war.
History-Dedup bewusst unangetastet (sonst doppelte History-Eintraege).
- DEDUP-4: start()/startPackages()/startItems() ersetzen runPackageIds; Pakete
mit noch LAUFENDEM Post-Processing (Task, Deferred, Hybrid) flogen aus dem
Set und ihre Abschluss-Benachrichtigung wurde nach dem naechsten finishRun
verworfen (autoExtractWhenStopped beendet den Run ~sofort). Neuer Helper
addTrailingPostProcessPackageIds ergaenzt das neue Set an allen 5 Stellen —
inkl. der Start-ohne-Queue-Pfade, die sogar die GERADE angestossenen
Entpackungen aus dem Set warfen.
- Dedup-Marker wird wieder freigegeben, wenn der Versand (nach den Sender-
Retries) endgueltig scheiterte — ein transienter Ausfall verbraucht den
Einmal-Slot nicht mehr dauerhaft.
Audit-Befunde N1/DEDUP-2 (HIGH), N2 (HIGH), N4 (MEDIUM):
N2: Drei terminale Fehler-Pfade (HTTP-416 erschoepft, permanent toter Link,
Debrid-Link-Terminalfehler) returnten VOR dem gemeinsamen Schwanz mit
refreshPackageStatus. Schlug das LETZTE offene Item eines Pakets ueber einen
dieser Pfade fehl, blieb das Paket bis zum Neustart auf "downloading"/"queued"
haengen und die Fehler-Benachrichtigung kam nie — bei Paketen voller toter
Links (haeufigster Fehlerfall) deterministisch. Jetzt rufen alle drei Pfade
refreshPackageStatus auf (defensiv eingefuegt statt fall-through, damit kein
nachfolgender Retry-Branch ein endgueltig gefailtes Item wieder einreiht).
N1: refreshPackageStatus benachrichtigte beim Failed-Uebergang nur bei
success===0. Gemischte Pakete (teils OK, teils Fehler), deren letztes Item
FEHLSCHLAEGT, erreichen das Post-Processing aber nie (Trigger haengt nur an
Completion-Pfaden) — die Fehler-Benachrichtigung war weg, und zwar genau in
der haeufigen Reihenfolge (Fehl-Items brennen ihre Retries nach den
Geschwistern ab). Gate entfernt; das Dedup-Set verhindert Doppel-Sends, falls
Post-Processing doch laeuft. Zusaetzlich wird fuer den gemischten Fall jetzt
der History-Eintrag geschrieben (fehlte komplett; dedup-sicher via
historyRecordedPackages).
N4: skipItems triggert Post-Processing jetzt fuer JEDES durch den Skip
terminal gewordene Paket (vorher nur autoExtract&&!hasFailed&&unextracted) —
sonst fehlten Completed-/Failed-Benachrichtigung, History-Eintrag und
package_done-Bereinigung, z.B. mit autoExtract aus oder wenn hybrid schon
alles entpackt hatte. Label-Reset bleibt auf den Entpack-Fall begrenzt.
Audit-Befunde N5/RATELIMIT (HIGH), DEDUP-BEFORE-CONFIRM, SLICE-SURROGATE,
BODY-UNCONSUMED, SET-1(a):
- Alle Sends laufen jetzt seriell durch eine Queue mit 450ms Mindestabstand —
Burst-Completions (viele Pakete gleichzeitig fertig) liefen sonst in Discords
5-pro-2s-Limit und die ueberzaehligen Benachrichtigungen waren weg.
- 429 wird mit Discords retry_after (Sekunden -> ms, Header oder JSON-Body)
wiederholt, 5xx/Netzwerkfehler mit Backoff (2 Retries); 4xx bleibt endgueltig.
- Response-Body wird immer konsumiert (undici-Verbindung nicht bis zum GC halten).
- 2000-Zeichen-Kappung surrogat-sicher (kein zerrissenes Emoji -> Discord 400).
- Ungueltige (nicht-leere) Webhook-URL loggt jetzt eine Warnung statt still zu
verwerfen.
- Tests: 17 (Retry-Pfade 429/5xx/Netz, 4xx ohne Retry, Serialisierung, Kappung).
Schickt sofort eine Test-Nachricht (inkl. Discord-Ping falls gesetzt) an die
EINGETRAGENEN Entwurfs-Werte — funktioniert also schon vor dem Speichern der
Einstellungen. Toast meldet Erfolg bzw. verweist bei Fehlschlag auf
Hilfe -> Letzte Fehler (dort steht der HTTP-Status/Fehlertext aus notify.ts).
Neuer IPC TEST_NOTIFY (main.ts ruft sendNotification direkt), Button im
input-row-Muster, deaktiviert solange die URL leer ist.
Neues Settings-Feld "Discord-Ping" unter der Webhook-URL: User-ID, @everyone
oder @here. Wird jeder Webhook-Nachricht vorangestellt, damit Discord wirklich
pingt statt nur still in den Kanal zu schreiben. Eine nackte Zahl wird als
<@id> verpackt (nur so pingt eine User-Erwaehnung); @everyone/@here und fertige
<@...>-Mentions gehen unveraendert durch. Default leer = Verhalten wie bisher.
5 neue Tests (Normalisierung + Content-Prefix).
User-Wunsch: Discord statt ntfy. notify.ts sendet jetzt einen JSON-Webhook-POST
({username, content} mit fettem Titel + Nachricht, auf Discords 2000-Zeichen-
Limit gekappt) statt ntfy-Headern. Emoji-Status im Titel (OK/Fehler/Ziel-
flagge) ersetzt Priority/Tags. Settings-UI: Label/Placeholder/Hinweis auf
Discord-Webhook umgestellt (Servereinstellungen -> Integrationen -> Webhooks).
Hooks, Dedup und Guards unveraendert. Tests auf das JSON-Format angepasst
(inkl. Discord-204-Antwort und 2000-Zeichen-Cap).
Headless-Server: Paket-Ausgaenge waren bisher nur per RDP+Log sichtbar. Neues
Modul notify.ts schickt einen fire-and-forget POST (ntfy-kompatibel: Title/
Priority/Tags als Header, Nachricht als Body) an eine konfigurierbare URL —
mit der kostenlosen ntfy-App aufs Handy, ohne Account/Port/Firewall (outbound).
- Settings: notifyUrl + 3 Ereignis-Toggles (Default aus) in Allgemein.
- Hook 1: Post-Processing-Ende (Paket completed/failed nach Entpacken).
- Hook 2: refreshPackageStatus fuer den Alle-Items-fehlgeschlagen-Fall (Link
tot -> Paket erreicht das Post-Processing nie; ohne diesen Hook schwiege
ausgerechnet der haeufigste Fehlerfall).
- Hook 3: finishRun mit Run-Summary (X/Y erfolgreich, Dauer, Schnitt).
- Dedup-Set pro Paket+Run, Lifecycle gespiegelt an historyRecordedPackages
(Run-Start-Clear, Retry-Deletes, removePackageFromSession). Guard
session.running || runPackageIds.has(id): nachlaufendes Entpacken nach
Run-Ende benachrichtigt noch, Startup-Recovery nach App-Neustart nicht
(sonst Doppel-Push fuer laengst fertige Pakete).
- 5s-Timeout, Fehler nur als logger.warn — blockiert nie den Download-Pfad.
- 9 Unit-Tests fuer notify.ts.
Der Error-Ring aus v1.7.185 war bisher nur ueber die Debug-Server-URL mit
Token erreichbar — per RDP ist ein Menueklick drastisch schneller als curl.
Neuer Menuepunkt im Hilfe-Dropdown zeigt die letzten 200 WARN/ERROR-Eintraege
(Kopf: "X Fehler, Y Warnungen") im bestehenden Bestaetigungs-Dialog mit
aufklappbaren Details; der Bestaetigen-Knopf kopiert die komplette Liste in
die Zwischenablage — direkt verwertbar fuer Bug-Reports.
IPC-Kette nach dem GET_DEBUG_SETUP_CHECK-Muster (ipc.ts, main.ts mit direktem
error-ring-Import wie debug-server/support-bundle, preload, preload-api).
Read-only auf den In-Memory-Snapshot, kein neues CSS.
Die Antwort auf "warum hat Paket X noch .DL.?" steht bisher nur in den
Rename-/Item-Logs — "kein Deutsch-Tag" ist INFO-Level und taucht nirgends im
UI auf. Jetzt speichert keepGermanAudioOnlyImpl pro Paket eine Zusammenfassung
(remuxed/kept-single/ohne-DE-Tag/ffmpeg-fehlt/Fehler + bis zu 100 Datei-Details
mit Aktion, Grund und erkannten Sprachen) direkt am PackageEntry:
- Status-Spalte zeigt "Tonspur: 5 OK / 1 ohne DE-Tag / ffmpeg fehlt" (rot bei
Auffaelligkeiten, flacher Stil), Datei-Details als Tooltip.
- Auch der ffmpeg-nicht-gefunden-Fruehausstieg schreibt die Summary.
- pkg.updatedAt wird gesetzt + Feld im Paket-Delta-Hash, damit der Snapshot
die Aenderung pusht; normalizeLoadedSession whitelistet das Feld mit
Shape-Validierung, sonst waere es nach jedem App-Neustart weg.
- 2 neue Integrationstests (Summary-Zaehler + ffmpeg-fehlt-Pfad).
importBackup wendete die Settings fuer beide Pfade ueber setSettings an, das bei
nicht-"never"-CleanupPolicy applyRetroactiveCleanupPolicy ausloest. Beim reinen
Settings-Restore purgte das die LIVE-Queue (fertige Items), obwohl der Vertrag
"running queue stays untouched" lautet (Dateien blieben auf Platte). Zudem rollte
der Import die laufenden Usage-/Status-Zaehler auf den (aelteren) Backup-Stand
zurueck (anders als updateSettings).
- setSettings bekommt optionales { suppressRetroactiveCleanup }; der Settings-only
Import setzt es. Die importierte Policy gilt weiter fuer KUENFTIGE Completions
ueber den normalen Vorwaertspfad (immediate/package_done) — nur der retroaktive
Sweep wird hier unterdrueckt.
- overlayLiveUsageCounters aus updateSettings extrahiert und im Settings-only Import
wiederverwendet (inkl. Key-Filter der Debrid-Link-Per-Key-Usage auf existierende
Keys). Nicht ueber updateSettings geroutet (vermeidet dessen resetHistoryForRetention).
Der Resume-Prune validiert Eintraege gegen die Top-Level-Archiv-Kandidaten auf
der Platte. Nested-Archiv-Schluessel (nested:<name>) haben dort kein Gegenstueck,
also wurden sie bei JEDEM extractPackageArchives-Aufruf geloescht — verschachtelte
Archive wurden beim Resume erneut entpackt. nested:-Schluessel werden im Prune
jetzt uebersprungen (sie werden mit dem Rest geleert, wenn das Paket fertig ist).
J: runPackagePostProcessing loescht im finally die Map-Eintraege fuer das Paket.
Hatte ein Abort den Handle schon entfernt und ein neuer Lauf einen frischen
Task+Controller gesetzt, riss das spaete finall des alten Tasks diesen neuen
Eintrag mit raus -> nicht abbrechbarer Waisen-Task + doppeltes paralleles
Post-Processing. Jetzt nur loeschen wenn Map noch auf DIESEN Task/Controller zeigt.
Q: collectFilesByExtensions filtert jetzt ~rd-Praefix (unsere Remux-Temp/Orphan-
Sidecars) aus, damit eine bei einem Crash mitten im Remux liegengebliebene
Teil-Datei nie in die MKV-Library gesammelt wird.
(dropItemContribution: Kommentar ergaenzt, dass das Nicht-Abziehen der
Session-Totals Absicht ist — kumulative Session-Zaehler, per Test abgesichert.)
flushAsync nahm eine Kopie der pending-Zeilen und entfernte sie nach dem await
per Index-Zaehlung (slice(snapshot.length)). Feuerte waehrend des awaits ein
write() den 1MB-Buffer-Cap, der vorne Zeilen wegshiftet, war die Zaehlung
desynchron und verwarf neu hinzugekommene, noch nicht geschriebene Zeilen.
Jetzt: pending-Zeilen per Move uebernehmen (Buffer auf [] zuruecksetzen) statt
kopieren; await-Zeit-writes laufen in einen frischen Buffer. Bei Schreibfehler
werden die Zeilen wieder vorn eingereiht und der Cap erneut angewandt.
- isGermanStream: Titel-Fallback nur noch ganze Woerter (german/deutsch); die
2-3-Buchstaben-Codes ger/deu sind im freien Titel-Text mehrdeutig und konnten
die falsche Spur als "deutsch" picken (und damit die echte deutsche loeschen).
Der Sprach-Tag-Check (ger/deu/de) bleibt unveraendert.
- looksLikeGermanRelease: 'dubbed' entfernt — ein nacktes "Dubbed" kann ein
italienischer/franzoesischer Dub sein und darf den German-first-Fallback nicht
ausloesen. Explizite german/deutsch-Tokens reichen.
- 2 Negativtests (3-Letter-Titel-Code, nicht-deutscher Dub).
Der atomare Ersetzen-Schritt loeschte das Original bevor der Ersatz bestaetigt
war; schlug das anschliessende Rename fehl (z.B. AV/Indexer-Lock), raeumte der
aeussere catch zusaetzlich die Temp-Datei weg -> null Kopien auf der Platte.
- Atomares Replace-over (MoveFileEx REPLACE_EXISTING / rename(2)) statt
rm-dann-rename: filePath haelt zu jedem Zeitpunkt entweder das volle Original
oder den vollen Remux.
- renameWithRetry: transiente Locks (EBUSY/EACCES/EPERM/EEXIST) mit Backoff
(200/500/1000ms) statt sofort abzubrechen.
- Eindeutiger Temp-Name (~rd<pid><rand>) statt fixem ~rdtmp -> keine Kollision
zwischen parallelen Paketen/Retries.
- 3 neue Tests (Recovery bei Replace-Fehler, Retry-Pfad EBUSY/EXDEV).
- tag mode: when no German-tagged audio track is found but the release name
says German/Dubbed, fall back to the first track (the dub is mislabeled, e.g.
German tagged "eng") instead of skipping; non-German names still skip safely
- comprehensive logging: per-package ffmpeg/ffprobe availability, plus per-file
detected audio languages, decision + reason, remux/rename result and the exact
error text when a file can't be processed
- shorter same-dir temp name so a long scene path + temp suffix cannot exceed
Windows MAX_PATH and silently fail the remux
When a Mega-Web account's unrestrict aborts because the shared unrestrict
timeout fired while it was running, give that account a 2-min cooldown
(only if it actually ran >=8s, so a quick user-cancel does not cool it
down). The download-manager retry then skips the cooled-down account and
rotates to the next one, instead of hammering the same account every 60s.
- debrid.ts: handle the abort in the rotation catch before classifyAccountFailure
- rotation log event TIMEOUT_COOLDOWN (+ renderer label) replaces the misleading
red "fataler Fehler" for this case
- RD_MEGA_ABORT_MIN_RUN_MS env override for the run-length threshold
- 2 regression tests (cooldown set -> next call rotates; quick abort -> no cooldown)
- New video-processor.ts: ffmpeg/ffprobe remux that keeps only the German
audio track (by language tag, with safe fallbacks) and strips the ".DL."
marker from the filename
- Runs after extraction in both the deferred and hybrid post-process paths,
inside the per-package file-op chain; abortable, disk-space checked,
mtime-preserving, atomic temp->replace so the original is never lost
- System ffmpeg via PATH / RD_FFMPEG_BIN; toggle + track-mode select in settings
- 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
Strip every comment from the source (parsed with the TypeScript compiler so
strings, template literals, regex literals and JSX are never touched), and drop
internal/working artifacts that do not belong in the public repository
(design mockups, internal analysis docs, a stray backup file and an old log).
No functional change: build is green, the full test suite passes.
Bei Serien, deren per-Episode-Ordner nur einen Episode-only-Token + Titel tragen
("Show.E01.Titel...-GRP", KEIN S01), benannte der Collect eine vom Auto-Rename bereits
korrekt benannte Datei ("Show.S01E01...-GRP.mkv") neu — und haengte den Staffel/Folgen-
Token HINTER die Scene-Gruppe ("...-GRP.S01E01"). In der Library stand dann der Episoden-
titel + ein angehaengtes S01E01 statt sauber S01E01 (gemeldet fuer "Steven Spielbergs Taken").
decideAutoRenameBaseName behaelt im Guard-B-Zweig "Ziel-Ordner ohne SxxExx" jetzt die
QUELLE, wenn sie ein nicht obfuskierter Scene-Name ist (sie traegt dort den einzigen echten
SxxExx-Token) — statt den Token an den Ordnernamen anzuhaengen. Obfuskierte/rohe Quellen
werden weiter aus dem Ordner sauber benannt. Wirkt in Collect und Auto-Rename.
Adversarial (Workflow) abgesichert: der Diskriminator ist allein "Quelle obfuskiert?" —
die Praefix-Laenge ist KEIN Kriterium, sonst fielen kurze Serien (ER, V, 24, Yu) durch und
zeigten denselben Bug. Regressionstest mit ER.S01E01 gepinnt. 4 Unit- + 1 Integrationstest.
Alte deutsche Dokus/Serien-Ordner ohne Gruppen-Suffix (Ordner endet auf bare Codec
".XviD", kein "-GROUP") wurden vom Auto-Rename als "kein Zielname" verworfen — die
Folge landete dann ROH in der Library (z.B. "safari-fm-s04e08a.avi" statt
"Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD.avi").
buildAutoRenameBaseName akzeptiert jetzt zusaetzlich einen vollstaendigen Episoden-
Ordner: echter SxxExx-Token IM Ordnernamen UND ein Codec-/Aufloesungs-Marker
(SCENE_RESOLUTION_MARKER_RE / SCENE_CODEC_MARKER_RE, inkl. xvid/divx). Der Part-
Buchstabe a/b bleibt erhalten (Ordnername dient unveraendert als Zielname), sodass
Teil 1 und Teil 2 nicht kollidieren. Konservativ: ein nackter "Show.S01E01"-Ordner
ohne Qualitaets-/Codec-Marker wird weiterhin nicht abgeleitet. Greift in Auto-Rename
und Collect. 5 Unit- + 1 Collect-Integrationstest; v1.7.180-Fallback nutzt jetzt
dieselben Module-Konstanten (DRY).
Eine Folge mit gueltigem SxxExx-Token ist eine echte Episode, niemals Bonus/Extras —
auch wenn ihr Titel oder der Episoden-Ordnername ein Bonus-Wort enthaelt
(Interview/Outtakes/Special/Featurette/Making-Of/...). Bisher stufte der Library-
Collect (und Auto-Rename) solche Folgen als Extras ein und verschob sie NIE in die
Bibliothek — extrahiert und korrekt benannt, aber stumm liegengelassen (Skip nur via
logger.info, im Paket-Log unsichtbar). Betraf u.a. Revenge S04E19 "Interview".
Neue isBonusContent()-Guard an beiden Call-Sites: erst SxxExx pruefen (extractEpisodeToken),
nur ohne Token greift der Bonus-Filter (isInsideBonusDir / BONUS_FILENAME_RE). Echte Extras
ohne Token bleiben gefiltert. 2 Integrationstests + 5 Unit-Tests.
User-Report (Desktop-Log): "Kreuzfahrt ins Glück" — 25 Folgen "bet_kig_01_hdt.mkv" (obfuskiert,
KEIN SxxExx-Token) landeten roh in der Library, obwohl der Episoden-Ordner
"Kreuzfahrt.ins.Glueck.01.Hochzeitsreise.nach.Burma.2007.German.720p.HDTV.x264-BET" bereits der
saubere Name ist (Episode als "01" statt S01E01).
Ursache (vorbestehend, nicht v1.7.178/179): buildAutoRenameBaseName gibt null zurueck, sobald die
QUELLE keinen SxxExx-Token hat — das "Folge 01"-Nummernformat wurde nie unterstuetzt.
Fix: Fallback in decideAutoRenameBaseName — fehlt der Quell-Episode-Token und kann normal kein
Name abgeleitet werden, aber ein folderCandidate ist ein VOLLSTAENDIGER Scene-Release-Ordner
(Scene-Gruppe UND Aufloesung ODER Codec, kein reiner Season-Ordner), wird dieser Ordnername
direkt verwendet (note "folder-as-is"). Greift NUR ohne Quell-Episode-Token -> Mega-Direct
(mit Quell-Token) bleibt no-target. Aufloesung ODER Codec (nicht nur Aufloesung) deckt
DVDRip/XviD ohne 720p ab (Advisor-Punkt). Bonus/Sample werden vorher gefiltert.
Verifiziert: tsc 6, 682 Tests gruen (+3: Kreuzfahrt real, DVDRip-nur-Codec, Mega-Direct-bleibt-
no-target), Build gruen. Advisor + reproduzierter Diagnose-Test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report (aus Desktop-Rename-Log): castle.s08e02.german.dl.720p.web.h264-idtv_int.mkv im
sauberen Episoden-Ordner "Castle.S08E02.GERMAN.DL.720p.WEB.H264-idTV_iNT" (Paket "scn2-cstl7")
wurde zu "scn2-cstl7.S08E02.mkv" VERSCHLIMMBESSERT (guter Quellname -> obfuskierter Paketname).
Ursache (vorbestehend, nicht durch v1.7.178): hasSceneGroupSuffix erkannte die Scene-Gruppe
"-idTV_iNT" nicht (SCENE_GROUP_SUFFIX_RE + Fallback verbieten Unterstriche). Der saubere
Episoden-Ordner wurde dadurch als Nicht-Scene-Ordner verworfen, und die Namensherleitung fiel
auf den obfuskierten Paket-Ordner "scn2-cstl7" zurueck -> "scn2-cstl7.S08E02".
Fix: hasSceneGroupSuffix nutzt jetzt zusaetzlich extractFlexibleSceneGroupSuffix (existierte
bereits, war aber nicht verdrahtet), das Unterstrich-Gruppen korrekt erkennt (splittet auf "_",
validiert jeden Teil). Der saubere Ordner wird akzeptiert -> idealer Name
"Castle.S08E02.GERMAN.DL.720p.WEB.H264-idTV_iNT". Mein v1.7.178-Folder-Token-Guard schuetzt
generische Paketordner (Mega-Direct) weiterhin.
Verifiziert: tsc 6, 679 Tests gruen (+1 Charakterisierung fuer den idTV_iNT-Fall), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>