Kernbug (User-Log, v1.7.168): "Unrestrict Timeout nach 60s" — Account 1 hing die
volle Zeit, acc2/acc3 wurden NIE versucht. Ursache: die gesamte Account-Rotation
lief unter EINEM geteilten ~60s-Signal (download-manager wickelt den ganzen
unrestrictLink in getUnrestrictTimeoutMs()); haengt acc1 bis es feuert, bricht die
ganze Rotation ab.
Fix (debrid.ts): jeder Account/Key bekommt im Rotations-Loop sein EIGENES Timeout
(PER_ACCOUNT_ATTEMPT_TIMEOUT_MS=25s, env RD_PER_ACCOUNT_TIMEOUT_MS, clamp 8-45s) via
AbortController + AbortSignal.any([global, attempt]). Catch: globaler signal.aborted
-> throw (Rotation stoppen); nur attemptController.signal.aborted -> 30s-Cooldown +
naechster Account. Ueber die Retry-Zyklen werden mit den Cooldowns alle Accounts erreicht.
Test: "aborts Mega web unrestrict when caller signal is cancelled" pruefte vorher
Objekt-Identitaet (.toBe(controller.signal)); der Per-Account-Timeout wrappt das Signal
aber zwingend (AbortSignal.any), die gereichte Instanz ist daher absichtlich nicht mehr
identisch. Umgestellt auf VERHALTEN: gereichtes Signal ist eine AbortSignal-Instanz und
propagiert das Caller-Cancel (aborted=true).
Recovered aus dem reset-weggesetzten Commit ae3ee1f (der andere Chat committete den Fix,
der Test brach, er resettete + hing). 641 Tests gruen, tsc unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User sah im Item-Log nur "Link-Umwandlung gestartet" -> "Unrestrict Timeout
60s" -> "erneut Versuch 1/inf", aber nie welcher Account/Key wann probiert
wurde. Die Rotation lief nur in account-rotation.log + Panel.
Jetzt: AsyncLocalStorage-Item-Sink (parallel-sicher bei 8 gleichzeitigen
Unrestricts) leitet JEDEN Rotations-Event in das Log des betroffenen Items:
"Account-Rotation: Mega-Debrid Web - Account 1 (xy) wird versucht / fehl-
geschlagen (Timeout) -> Account 2". Damit ist im Item-Log direkt sichtbar,
ob acc2/acc3 ueberhaupt erreicht werden -> dient auch als Diagnose fuer den
vermuteten Timeout-Bug (kommt separat, falls das Log Stillstand zeigt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Folge-Fund zu 18eada9 (Opus-Verifikation des deferFreshFiles-Konzepts):
18eada9 schloss den "frische Datei landet mit Original-Scene-Namen in der
Library"-Bug nur fuer den Hybrid-Pfad (deferFreshFiles=true + Mehrfach-Paesse).
Der finale Deferred-Pass blieb betroffen.
Root Cause (verifiziert via failing Test gegen HEAD):
- runDeferredPostExtraction macht Rename -> Collect (deferFreshFiles=false). Ist
eine Datei beim Deferred-Rename noch "frisch" (juenger als fileStabilizeMinAgeMs,
prod=2000ms) -- v.a. eine eben per Nested-Extraction geschriebene Datei --
ueberspringt der Frische-Gate sie, und der Collect moved sie mit Original-
Scene-Namen in die Library. collectMkvFilesToLibrary benennt selbst nicht um
(buildUniqueFlattenTargetPath, nur Flatten).
- Im Deferred-FINAL-Pass gibt es keinen concurrent Extractor-Write mehr
(Extraktion inkl. Nested ist awaited) -- der Frische-Gate ist dort ein False
Positive. Pre-existierender Gap (Frische-Skip aelter als 18eada9), auch
v1.7.162 betroffen.
Fix (minimal): treatFilesAsStable-Param durch autoRenameExtractedVideoFiles(Impl).
Der Deferred-Final-Pass ruft mit treatFilesAsStable=true -> Frische-Gate umgangen
-> alle Dateien werden umbenannt, bevor der Collect sie sammelt. Hybrid-Pfad
unangetastet (nutzt ...Impl mit Default false -> Frische-Skip bleibt aktiv).
Regressionstest: frische Datei im Deferred-Pass landet UMBENANNT in der Library.
623 Tests gruen, tsc unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das Bundle packte bisher alle logs-Unterordner rekursiv (addDirectoryIfExists)
→ tausende Per-Item-Logs → 200+ MB (User-Bundle: 4273 Item-Logs, 214 MB).
Zum Verschicken/Analysieren unhandlich.
Neue addRecentDirectoryFiles(): packt nur Dateien mit mtime in den letzten
8h. Package-Logs und Item-Logs nutzen das 8h-Fenster; Session-Logs (wenige
Dateien) weiterhin komplett. Haupt-Logs (rd_downloader.log, rename.log,
audit.log, session.log, trace.log) waren schon per addFileIfExists einzeln
gepackt und bleiben unveraendert. Live-Logs der aktiven Queue (laufende
Session) ebenfalls komplett.
Ergebnis: Bundle enthaelt alles fuer aktuelle Fehler + Rename-Probleme,
aber kein Bloat durch tausende alte Item-Logs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Report (verifiziert via Support-Bundle): pl3x-24hours.s01e07,
tmsf-burnnotice-s05e11-repack, -s05e15 landeten mit Original-Scene-Namen in der
Library statt umbenannt. Andere Episoden derselben Pakete (formatidentisch)
wurden korrekt umbenannt → kein Format-Problem, sondern Timing-Race.
Root Cause (aus Log-Timeline):
1. autoRenameExtractedVideoFilesImpl erfasste `now` EINMAL am Scan-Start. Bei
Hybrid-Extraktion werden weitere Dateien WÄHREND des Scans geschrieben →
deren mtime > now → negatives ageMs → der "Clock-Skew = stabil"-Zweig wertete
sie faelschlich als stabil → Rename mitten im Extractor-Write → EBUSY → 200ms-
Retry deferred.
2. Der MKV-Collect hatte KEINEN Frische-Skip und moved die Datei im Retry-Fenster
mit Original-Namen, bevor der Rename-Retry feuerte.
3. Rename + Collect liefen als zwei separate chainPackageFileOp-Ketten →
ueberlappende Hybrid-Runden konnten einen Collect zwischen Rename und Collect
einer anderen Runde einschieben.
Fix (3 Teile, scoped auf extractDir des Pakets — kein Shared-Library-Scan, nicht
das v1.7.107-Antipattern):
1. `now` wird PRO DATEI erfasst → frisch-geschriebene Dateien korrekt als "frisch"
erkannt und deferred (statt EBUSY-Rename mitten im Write).
2. collectMkvFilesToLibrary bekommt deferFreshFiles-Param: im Hybrid-Pfad werden
frische Dateien (juenger als fileStabilizeMinAgeMs) uebersprungen statt unbenannt
gemoved. Der finale Deferred-Pass (deferFreshFiles=false) sammelt sie nach
Stabilisierung ein (Safety-Net).
3. Hybrid-Pfad: Rename (Impl-Variante, kein Self-Chain) + Collect in EINER
chainPackageFileOp-Kette → atomar, kein Interleaving ueberlappender Runden.
Deferred-Pfad unangetastet (dort keine concurrent Extraktion). Regressionstest:
frische Datei wird im Hybrid-Collect deferred, vom finalen Pass gesammelt.
622 Tests gruen, tsc-Fehlerzahl unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Report: ZIP scheitert mit wrong_password -> Passwort zur Liste hinzufügen
+ Settings speichern -> "Jetzt entpacken" scheitert WIEDER -> erst nach
App-Neustart klappt "Jetzt entpacken".
Analyse (eigenständig + zweiter Analyse-Agent, gesamte TS+Java-Kette):
Renderer-Save -> updateSettings (fingerprint enthält archivePasswordList) ->
setSettings (this.settings aktualisiert) -> extractNow -> runPackagePostProcessing
-> extractPackageArchives (passwordList: this.settings.archivePasswordList) ->
archivePasswordCandidates (enthält neues PW) -> Daemon bekommt Passwörter PRO
REQUEST. Java-Daemon probiert alle PW frisch, kein Per-Archiv-Cache; zip4j/
sevenzipjbinding ohne relevanten static State. Alle Pfade propagieren die neue
Liste korrekt — statisch ist KEIN Bug auffindbar.
Verifikation per Ausschluss: die EINZIGE zustandsbehaftete Komponente, die ein
App-Neustart zurücksetzt und ein Settings-Save NICHT, ist der langlebige
JVM-Daemon-Prozess (+ der In-Memory Learned-Password-Cache). Der User bestätigt
empirisch, dass ein Neustart (= frischer Daemon) es fixt.
Fix: neue resetExtractorCachesForPasswordChange() repliziert den Neustart-Effekt
am Extractor-Subsystem — bei Änderung der Passwortliste in setSettings wird der
Learned-Password-Cache geleert und der idle JVM-Daemon heruntergefahren, sodass
die nächste Extraktion frisch mit der neuen Liste startet. Beschäftigter Daemon
wird nicht abgebrochen (laufende Extraktion bleibt unangetastet).
Diagnostik: setSettings loggt jetzt PW-Anzahl + Reset-Resultat. Zusammen mit den
bestehenden "Archiv-Passwortliste: passwordCount=N"-Logs lässt sich bei erneutem
Auftreten in 30s unterscheiden, ob das PW beim Extractor ankommt (H2, Fix greift)
oder nicht (H1, dann TS-upstream). Best-bet per Ausschluss + Logs zur finalen
Bestätigung beim nächsten Repro.
621 Tests grün, tsc-Fehlerzahl unverändert (9 pre-existing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Problem: nach importBackup hielt der Manager weiter die STALE In-Memory-Session
(Import schrieb nur auf Disk). blockAllPersistence wurde gesetzt um Überschreiben
zu verhindern, aber nie zurückgesetzt → ignorierte der User die manuelle
"Bitte neustarten"-Aufforderung und arbeitete weiter, ging bei hartem Crash
alles verloren (stille Persistenz-Blockade).
In-Memory-Reload verworfen: aborted activeTasks settlen ASYNC und greifen in
ihren finally-Blöcken auf this.session.items[id] zu — ein Session-Swap würde
dagegen racen. Sicherer Reload bräuchte async-Refactor (alle Tasks awaiten).
Lösung: Auto-Relaunch. Nach restored===true startet main.ts die App automatisch
neu (1.5s Delay für Toast). Der frische Prozess lädt die restored Session sauber
über den bewährten Startup-Pfad — null Stale-State-Risiko. Main-getrieben (nicht
Renderer), damit ein Renderer-Fehler den Restart nicht verhindern kann.
skipShutdownPersist/blockAllPersistence schützen weiterhin das kurze Fenster +
den Quit (prepareForShutdown:5680 überspringt Persistenz sauber, fasst die stale
Session nicht an). Nach Relaunch: frischer Prozess, Flags zurückgesetzt — Footgun weg.
621 Tests grün, tsc-Fehlerzahl unverändert (9 pre-existing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aus der Bug-Analyse (3 Subagents): die Deferred-Post-Processing-Pipeline war
nur halb ins Abbruch-/Lifecycle-Management integriert — gleiche Ecke wie der
v1.7.156-Datenverlust.
H1: abortPostProcessing (globaler Stop/Shutdown/clearAll/external) bricht jetzt
auch packageDeferredPostProcessAbortControllers + die neue Hybrid-Map ab.
Vorher rasten MKV-Move/Cleanup/Rename gegen den synchronen Shutdown-Save.
H2: Hybrid-Post-Extract (Rename+MKV-Collect) lief als komplett ungetracktes
detached Promise. Jetzt in packageHybridPostProcessControllers (Set/Package)
registriert — SYNCHRON vor dem Promise, mit shouldAbort an beide Aufrufe.
Bewusst SEPARAT von der Deferred-Map, sonst würde runDeferredPostExtraction's
replace-Logik die laufende Hybrid-Arbeit selbst killen (Advisor-Fund).
Cancel/Reset/Stop stoppt jetzt laufende Hybrid-Verschiebungen.
M1: hasAnyDeferredPostProcessPending() — Scheduler-Abschluss + finishRun-Clear
gaten darauf. Run endet/Summary feuert nicht mehr während im Hintergrund
noch Dateien verschoben werden; Run-State wird nicht mehr mittendrin geleert.
H3: validateDownloadedFileCompletion akzeptierte 0-Byte bei source=stream-end
(kein Content-Length, keine Provider-Größe) als "fertig". Jetzt ok:false
-> bestehender download_underflow-Retry-Pfad. Verhindert leere Datei = komplett.
N1: toter (unerreichbarer) Disk-Fallback-Block in findReadyArchiveSets +
verwaiste pendingItemStatus-Map entfernt (verhaltensneutral).
Bewusst übersprungen: M2 (blockAllPersistence — vorgeschlagener Reset wäre
unsicher, In-Memory-Session ist nach Import stale) und M3 (cancelPendingAsyncSaves
— Generation-Guard schützt Korrektheit bereits). Siehe tasks/todo.md.
8 neue Tests (tests/download-completion.test.ts) inkl. H3-Regression. 621 Tests grün.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KRITISCHER Datenverlust-Fix (Regression aus v1.7.154):
collectMkvFilesToLibrary lief seit v1.7.154 mit einem Cleanup-Loop ueber
BEIDE Source-Dirs (extractDir + outputDir). cleanupNonMkvResidualFiles
loescht alle Nicht-Video-Dateien — auf dem outputDir traf das auch die
RAR-Archive. Bei Multi-Archive-Set-Paketen (z.B. S01 + S02 RARs im selben
outputDir) wurde nach dem Extrahieren von S01 die MKV-Collection getriggert,
die dann die noch nicht entpackten S02-RAR-Parts als "Restdateien" loeschte.
Folge: S02 ging verloren (missing_file beim spaeteren Extract).
Fix: Destruktiver Cleanup (Restdateien + leere Ordner) laeuft jetzt NUR
noch auf dem cleanupDir:
- autoExtract=true -> extractDir (entpackter Inhalt, fertig verarbeitet)
- autoExtract=false -> outputDir (kein Extract, finaler Inhalt)
Der outputDir wird bei autoExtract=true nie hier aufgeraeumt — das macht
die separate Archive-Cleanup-Pipeline mit Extraktions-Guards.
Das MKV-Scannen beider Dirs (v1.7.154 Mega-Direct-.mkv) bleibt erhalten,
nur der Cleanup ist eingegrenzt.
Regressionstest verifiziert: 2 RAR-Sets im outputDir, S01-MKVs in
extractDir -> collectMkvFilesToLibrary darf S02-RARs nicht loeschen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UX: Beim Hinzufuegen von mega.nz Links wurden bisher nur die opaken
URL-Fragmente angezeigt ("pZl1wBRQ" etc.). Echte Filenames kamen erst
beim Mega-Debrid Unrestrict-Call, d.h. unmittelbar vor Download-Start.
Fix: Neuer src/main/mega-public-api.ts holt Filename + Groesse direkt
von Mega's Public API (g.api.mega.co.nz/cs) ohne Mega-Debrid-Quota
anzufassen. AES-128-CBC Decryption der Attribute mit dem Key aus
dem URL-Fragment.
resolveFilenames (debrid.ts) ruft den neuen Resolver fuer alle
erkannten mega.nz Links auf (concurrency 4). Auf Fehler/Rate-Limit
fallback auf den bestehenden Unrestrict-Pfad.
19 neue Tests fuer URL-Parser, AES-Decryption, Mocked-Fetch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug: Direct .mkv Downloads (z.B. von Mega-Debrid bei mega.nz, die
KEIN Archiv liefern) blieben mit autoExtract=true im outputDir liegen
und kamen nie in die MKV-Library. collectMkvFilesToLibrary scannte
binary nur extractDir wenn autoExtract aktiv war.
Fix: Beide Source-Dirs scannen, dedupe by basename (extractDir wins),
Safety-Check + Existenz-Check pro Dir. Cleanup-Loop läuft auch pro Dir.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Aus dem live Rename-Log + Library-Screenshot:
(1) .nfo Files landeten in der MKV-Library
moveCompanionFiles() hatte ".nfo" in der Extensions-Liste — sollte
nur fuer Subtitles gemoved werden. .nfo gehoert nicht in die
Library. Aus dem Set entfernt; renameCompanionFiles laesst .nfo
weiterhin mit-umbenennen (im Extract-Dir, harmlos), aber MKV-Move
bringt sie nicht mehr in die Library.
(2) Vollstaendige Scene-Namen wurden auf "Show.SxxExx.mkv" gekuerzt
buildSafeAutoRenameTargetPath hatte ein 247-Zeichen Total-Path-Cap,
das vollstaendige Scene-Releases wie
"Dr.House.S04E02.Der.Stoff.aus.dem.die.Heldin.ist.GERMAN.5.1.DL.AC3.720p.BDRiP.x264-TvR.mkv"
abgelehnt hat → Fallback aktiviert → "Dr.House.S04E02.mkv".
Die ABER renamePathWithExdevFallback wraps eh ueber
toWindowsLongPathIfNeeded (\?\ Prefix), und der Endpfad nach
MKV-Move ist viel kuerzer (Library-Dir-Prefix). Cap war
ueberhaupt nicht noetig und hat aktiv schoene Namen abgesaegt.
Cap entfernt; nur das 255-char NTFS Filename-Limit bleibt.
Test: neuer Test "mkv-move moves SUBTITLES to library but NOT .nfo
metadata files". 592/592 Tests gruen.
Im naechsten Schritt: Fix-Skript fuer den User damit existierende
Library-Files (mit .nfo + zu kurzen Namen) korrigiert werden ohne
Re-Download.
Independent code review fand drei echte Probleme an v1.7.151:
(a) File-stability check bei Clock-Skew rueckwaerts:
negative ageMs (mtime in der Zukunft, z.B. NTP-Korrektur, VM-Resume)
wurde von "ageMs < 2000" als "frisch" interpretiert → Datei stuck
bis Clock aufschliesst. Fix: ageMs >= 0 zusaetzlich pruefen — negativ
= "definitiv stabil".
(c) Suffix-Loop koennte Source-File als Resolved-Target waehlen:
wenn Source schon "<base>.2.mkv" heisst und das Original "<base>.mkv"
anderswo existiert, koennte die .2/.3-Loop sich selbst auswaehlen.
Fix: pathKey-Vergleich gegen sourcePath im Loop, springt weiter.
(f) xX-Format matched x264/x265/x266 Codec-Tokens:
"5x265.x265.mkv" wurde als S05E265 interpretiert.
"Movie.x264-GROUP.mkv" konnte phantome Episode triggern.
Fix: zweite Number-Group auf \d{1,2} (max 99) gecapped + negativer
Lookahead [\dx] dahinter. 3-stellige xX-Episoden (sehr selten) gehen
verloren — moderne SxxEnnn deckt das ab. Schutz gegen alle gaengigen
Codecs (x264/265/266, h264/265) und Aspect-Ratios (1920x1080).
Tests: neue assertions fuer x264/x265/aspect-ratio + 10x99 vs 10x100.
591/591 gruen.
v1.7.149 hat den Race ZWISCHEN parallelen Auto-Rename-Scans gefixt
(autoRenameInFlight). Aber es gab noch einen anderen Race:
- Hybrid-Pfad (Z.10952-66) feuert "fire-and-forget" rename->mkvMove
- Deferred-Post-Process-Pfad (Z.11672/11748) feuert "awaited" rename + mkvMove
Beide Pipes koennen GLEICHZEITIG fuer dasselbe Package laufen. Innerhalb
einer Pipe ist rename->mkvMove sequentiell, aber Pipe A's mkvMove kann
WAEHREND Pipe B's rename starten (nachdem die Rename-Serialisierung von
v1.7.149 Pipe B entsperrt hat). Resultat: Pipe A bewegt File X aus
extractDir, Pipe B's rename versucht File X umzubenennen → ENOENT, oder
File landet mit altem Hoster-Namen in der Library.
Fix: autoRenameInFlight wird zu packageFileOpChain generalisiert. Helper
chainPackageFileOp(pkgId, fn) chained beliebige file-mutierende Ops auf
das vorherige Promise. autoRenameExtractedVideoFiles benutzt es intern,
und beide collectMkvFilesToLibrary-Aufrufstellen werden jetzt explizit
durch denselben Chain geroutet.
Effekt: pro Package laeuft maximal eine post-process Operation (rename
ODER mkvMove) zu jeder Zeit, egal welche Pipe sie triggert.
Tests:
- "serializes rename and mkvMove across hybrid + deferred pipes":
4 chainPackageFileOp-Calls fuer dasselbe Package, max-concurrent == 1,
Reihenfolge erhalten, Slot nach letztem Op geleert.
- "chainPackageFileOp recovers from a failed op": Fehler im ersten Op
bricht die Chain nicht — nachfolgende Ops laufen normal weiter.
584/584 Tests gruen.
Im Production-Log:
21:30:33.957Z Auto-Rename Scan gestartet | videoFiles=25
21:30:33.992Z Auto-Rename Scan gestartet | videoFiles=25 (35ms spaeter, gleiches pkg!)
21:30:33.994Z Auto-Rename durchgefuehrt | E24.B (Scan 1)
21:30:34.009Z Auto-Rename uebersprungen: Ziel existiert (Scan 2 sieht renamed file)
21:30:34.029Z Auto-Rename durchgefuehrt | E24.A (Scan 1)
21:30:34.056Z Auto-Rename fehlgeschlagen | ENOENT (Scan 2 versucht renamed file)
Ursache: hybrid-extract feuert nach JEDEM erfolgreichen Archive einen
fire-and-forget autoRename (Z.10915), und der deferred Post-Process-Pfad
ruft am Ende nochmal autoRename auf (Z.11630). Bei einem Multi-Archive-
Package (25 Episoden) ueberlappen sich 2+ Scans auf demselben Fileset.
Ergebnis: "Ziel existiert"-Warnungen + ENOENT-Fehler beim Rename.
Manchmal blieben einzelne Files unbenannt durchrutschen (Scan 2 sieht
File X, will renamen, aber Scan 1 hat es schon weg-renamed).
Fix: pro Package via Promise-Chaining serialisieren. Neue Map
autoRenameInFlight haelt das laufende Scan-Promise pro packageId. Der
neue Wrapper kettet jeden weiteren Aufruf an das vorherige Promise an
— so laeuft maximal ein Scan zur Zeit pro Package, der naechste startet
erst wenn der vorherige fertig ist (und sieht damit den korrekten
Disk-State).
Test: zwei parallele autoRenameExtractedVideoFiles-Aufrufe fuer dasselbe
Package mit 3 obfuskierten Files. Beide resolven sauber, Summe der
Renames == 3, alle 3 Folders enthalten am Ende den korrekten Folder-
Namen statt Hoster-Obfuskation. 582/582 Tests gruen.
Regression in v1.7.147: der Folder-Override (parentEpisodeToken
ueberschreibt sourceEpisodeToken bei Mismatch) ist zu aggressiv. Bei
sauberen Scene-Releases die zufaellig im falschen Folder liegen wuerde
das den Episodennamen FALSCH umschreiben.
Beispiel aus Production-Log:
Folder: The.Royals.S01E08.Der.Grosse.stuerzt.German.DL.720p.BluRay.x264-J4F
File: the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv
Beide haben sauberes Scene-Format. Datei sagt klar S01E09 — Folder ist
falsch beschriftet (Hoster-Fehler). Die Datei zu S01E08 umbenennen
waere Daten-Korruption.
Fix: neue Helper-Funktion looksLikeObfuscatedSceneFileName() prueft ob
ein Filename Scene-Marker hat (720p/1080p, german/english, bluray/web/
hdtv, x264/x265, ac3/aac/dts) ODER 5+ Punkte als Scene-Struktur. Wenn
2+ Marker oder 5+ Punkte → KEIN Override (Source ist authoritativ).
Wenn weniger → Source ist obfuskiert, Folder gewinnt.
Beispiele aus Production:
- "awa-diethundermans02e16hd.mkv" (0 Marker, 0 Punkte) → obfuskiert,
Folder Die.Thundermans.S02E01... gewinnt → korrekt umbenannt
- "the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv"
(5 Marker) → sauber, Source bleibt → Skip statt Falsch-Rename
- "Desperate.Housewives.S01E01.German.Synced.DL.720p.WEB-DL.AC3.h264.mkv"
(5+ Marker) → sauber, kein Override
4 neue Unit-Tests fuer looksLikeObfuscatedSceneFileName, 581/581 gruen.
Bug 1 - Obfuskierter Datei-Token vs Folder:
Hoster verteilen Episoden in EPISODEN-Ordner ABER scrambeln den
Datei-Token zur Anti-Piracy:
Folder: Die.Thundermans.S02E01.Der.Thunder.Van.GERMAN...
File: awa-diethundermans02e16hd.mkv (Datei sagt E16, Folder E01)
Bisher hat der Episode-Token-Mismatch-Check uebersprungen → Datei
behaelt obfuskierten Namen → MKV-Move kopiert Garbage-Namen ins
Library-Verzeichnis.
Fix: wenn der unmittelbare Eltern-Ordner explizit denselben SxxExx-
Token wie unser computed targetBaseName traegt (also ein Per-Episode
Scene-Folder ist, NICHT der Extract-Root), wird der Folder-Token als
authoritativ behandelt — Scene-Releases benennen Episoden-Folder
deterministisch korrekt, der Datei-Name ist die Obfuskation.
Bug 2 - Weak Folder ueberschreibt perfekten Source-Filename:
Source: Desperate.Housewives.S01E01.German.Synced.DL.720p.WEB-DL.AC3.h264
Folder-Kette: ["S01 Complete", "Desperate.Housewives.S01.Synced..."]
Auto-Rename hat den unmittelbaren Parent "S01 Complete" gewaehlt und
daraus "S01E01 Complete" gebaut — kompletter Verlust des Series-Namens.
Fix: neue Helper-Funktion hasMeaningfulSeriesPrefix() prueft, ob ein
Name mindestens 3 alphabetische Zeichen VOR dem Season-Token hat.
Wenn (a) Source einen Episode-Token hat, (b) Source einen Series-Prefix
hat, (c) Computed Target KEINEN Series-Prefix hat und (d) Target weniger
als die Haelfte der Source-Laenge ist → Source behalten, Rename
ueberspringen. Renaming wuerde Information ZERSTOEREN.
Tests: 3 neue Unit-Tests fuer hasMeaningfulSeriesPrefix decken die
relevanten Faelle ab (echte Series-Namen vs generische Season-Labels
vs. Folder ohne Season-Token). 577/577 Tests gruen.
Symptom: Nutzer zieht DLC mit vielen Paketen rein, App haengt 1-2 min.
Ursache: addPackages() rief logPackageForItem() pro Link auf. Jede
dieser Calls triggerte ~10 synchrone FS-Operationen:
- ensurePackageLog: mkdirSync + existsSync
- ensureItemLog: mkdirSync + existsSync + writeFileSync (first-time)
+ 2× appendFileSync (first-time header)
- logPackage + writeItemLogEvent (appendFileSync, batched)
Bei einer DLC mit 60 Paketen × 25 Links = 1500 Items → ~9.000-15.000
sync FS-Calls. Auf langsamen Disks / Netzwerk-Shares: 60-120 Sekunden
Event-Loop-Blockade. UI eingefroren.
Fix: per-Item-Logs waehrend Bulk-Add nicht mehr initialisieren. Sie
werden lazy beim ersten echten Lifecycle-Event (Download-Start, Fehler)
angelegt. Stattdessen EINE zusammengefasste "Links registriert (N)"
Zeile ins Package-Log pro Paket — bei >50 Links mit gekuerzter
Vorschau (erste 20 + "+N more") damit die Log-Zeile nicht riesig wird.
Neuer Test "bulk-adds large DLC containers without initializing per-item
logs" verifiziert: 1500 Items werden in <5s hinzugefuegt (lokal unter
300ms), keine Item-Log-Dateien entstehen, pro Paket existiert genau ein
Package-Log.
Laeuft einmal beim App-Start und warnt klar im Log, wenn etwas auffaellt
— BEVOR der Nutzer mitten im Download stolpert. Blockiert den Start
nicht, schreibt nur in rd_downloader.log + audit-log.
Pruefungen:
- Download-Ziel-Ordner fehlt / nicht beschreibbar / nicht konfiguriert
- Runtime-Ordner (%APPDATA%/runtime) fehlt oder nicht beschreibbar
- Wenig Festplattenplatz im Download-Ordner (< 5 GB)
- Kein einziger Debrid-Provider konfiguriert → Downloads koennen nicht
funktionieren
- State-Datei > 50 MB (alte abgeschlossene Pakete sollten geprunt werden)
Listet zudem als INFO alle aktiv konfigurierten Provider auf, damit aus
dem Startup-Log klar ist was aktiv ist (Mega-Debrid X Accounts,
Debrid-Link X Keys, etc.).
Reine Funktion runStartupHealthCheck() → HealthCheckReport, 6 Unit-Tests
decken die wichtigsten Pfade ab. Wiring in AppController-Constructor ist
in try/catch — falls der Check selbst abstuerzt, stoert das den Start
nicht.
fileNotAvailable, disabledServerHost, notFreeHost, serverNotAllowed,
freeServerOverload, maintenanceHost, noServerHost sind LINK- oder
HOST-level Fehler, nicht Key-level. Der Key antwortet ganz normal und
sagt nur "diesen Link kann ich aktuell nicht verarbeiten".
Vorher wurde trotzdem der Runtime-Status auf "error" gesetzt — sah in
der UI aus als waere der Key kaputt und hat die Rotations-Heuristiken
irritiert.
Fix: bei failure.category === "skip" den Runtime-Status in Ruhe lassen.
Der Key bleibt "ready" (bzw. was er vorher war). Invalid bleibt
"invalid", alle anderen fehlerhaften Antworten bleiben "error".
Test: Key 1 gibt fileNotAvailable zurueck → Key 2 erfolgreich. Key 1
darf danach NICHT "error" sein (per neuem Test-Helper
getDebridLinkKeyRuntimeStateForTests).
Previously, when a Debrid-Link key returned maxDataHost or maxLinkHost
("you've used up YOUR per-host quota for this hoster on this key"), the
WHOLE key got a 2-min key-wide cooldown — blocking it for all hosters
even though it was only exhausted for that one host.
Now those errors apply a per-(key, host) cooldown instead:
- Key 1 hits maxDataHost on rapidgator → only (Key 1, rapidgator) is
blocked. Key 1 stays usable for uploaded.net etc. in the same rotation
- Same key + same host on subsequent attempts: skipped with explicit
"Host-Cooldown rapidgator bis HH:MM:SS" log line
- Key-wide quotas (maxLink, maxData) still apply key-wide as before
Implementation:
- DEBRID_LINK_QUOTA_ERRORS split into key-wide vs host-only sets
- New debridLinkKeyHostCooldowns map keyed by `${keyId}|${hoster}`
- setDebridLinkKeyHostCooldownState mirrors max-wins / strong-category
semantics of the per-key version, falls back to key-wide cooldown when
the hoster can't be parsed (safer than thrashing)
- Key runtime status stays "ready" on host-only failures — only this
(key, host) is blocked, the key is still healthy for other hosters
- Reset/prune helpers (resetDebridLinkRuntimeStateForTests,
pruneDebridLinkRuntimeStateForKeys, pruneExpiredDebridLinkRuntimeState)
clear the new map too
- New rotation log event SKIP_HOST_COOLDOWN
Test: 2 keys, key1 hits maxDataHost on rapidgator → key2 succeeds.
Second rapidgator request: key1 SKIPPED via host-cooldown.
Third request to uploaded.net: key1 tried again and succeeds.
- New dedicated account-rotation.log (audit-style) so multi-account/key
rotation flow is visible without rd_downloader.log noise
- Mega-Debrid: always show "Account X/Y (masked@login)" label even with
one account, and log a clear "TESTE Account fuer Link-Generierung..."
line BEFORE every network call so the user sees which account is in
play even if the call hangs
- Mega-Debrid: on FAILED, log which account will be tried next
(skipping disabled/limited/cooldown ones), so the rotation is auditable
- Mega-Web "Antwort leer" / empty body now uses 20s cooldown instead of
120s — empty responses are typically transient server hiccups, not
real failures (caused healthy accounts to be unfairly blocked)
- Generic transport/unknown errors: 30s cooldown instead of 120s
- Global stall watchdog no longer counts items in "validating" status —
per-item validating watchdog already handles them and gives the
multi-account rotation enough time. Without this fix the global
watchdog could abort the unrestrict mid-rotation (e.g. account 3 of 3
still being tested) just because no download bytes had arrived
- Same logging treatment applied to Debrid-Link key rotation
User reported Debrid-Link "often jumps into provider-cooldown" and feels
unstable. Root cause was four cooperating bugs that turned isolated key-level
failures into provider-wide multi-minute outages.
Fix 1: Skip provider-wide circuit breaker for ALL Debrid-Link errors
(download-manager.ts ~8689)
Previously only the explicit `debrid_link_cooldown:` sentinel was bypassed;
every other Debrid-Link error (terminal failures, timeouts, parse errors)
still went through recordProviderFailure() + applyProviderBusyBackoff(),
applying a provider-wide cooldown ON TOP of the per-key cooldown debrid.ts
already managed. Now any error message containing "debrid-link" or where
the failure key is "debridlink" skips the provider-level circuit breaker
entirely. Per-key cooldowns alone are the right granularity.
Fix 2: Transport errors get a short 15s cooldown, not 2 min
(debrid.ts ~2684)
A single network timeout / ECONNRESET was parking the key for 2 full
minutes. With 9 keys all of which might experience the same transient
issue at different moments, this could cascade into all keys cooling
down for 2 min each. Now isolated transport hiccups get 15s while real
API/server problems still get the full 2 min.
Fix 3: HTTP 200 with success:false (no error code) is now temporary, not fatal
(debrid.ts ~2691)
Previously these went through to the fallthrough "fatal: true" which
permanently failed the item. Now they get a 30s temporary cooldown and
the item retries on the next key.
Fix 4: setDebridLinkKeyCooldownState is max-wins under concurrent calls
(debrid.ts ~135)
When 8 parallel items all hit the same key with floodDetected, each
computes its own cooldown duration and calls setDebrid LinkKeyCooldown
State. Without max-wins, the LAST setter could shorten the cooldown
(e.g. one item read a 1h Retry-After header, another defaulted to 2 min;
the 2 min would then overwrite the 1h). Now the longer cooldown wins,
with rate_limit/quota/invalid categories also winning over plain
temporary regardless of duration.
Tests: 201/201 (debrid + download-manager) green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reported user bug: "When I add an account, remove it, add a new one, the
new account is only really used after restart." Multiple cache layers were
not invalidated when settings changed, causing the system to keep using
stale state until the app was restarted.
Three layers of caching needed invalidation:
1. DebridService cached client instances (debrid.ts ~3067)
The cached DebridLinkClient / LinkSnappyClient / DdownloadClient hold
internal state (session cookies, auth tokens, parsed key lists). Without
explicit invalidation when credentials change, the OLD client instance
keeps serving requests until apiKeysRaw / login+password no longer match
the cache key - which doesn't always trigger because the cache key may
incidentally match (e.g. user removes and re-adds the same key).
Fix: setSettings() now compares previous vs next credentials per provider
and explicitly clears the cache when they differ.
2. MegaDebridClient.cachedApiTokens (debrid.ts ~1533)
Static module-level token cache keyed by login (lowercase). When a user
changes the password for an existing login (same login, new password),
the cached token was kept for up to 20 minutes and would only get cleared
after the API returned 401/403.
Fix: Two new static methods - pruneCachedTokensNotIn() removes entries
whose login is no longer in the active list, and clearCachedApiToken()
force-clears a specific login. Both called from setSettings() based on
diff between previous and next account list.
3. Module-level Debrid-Link cooldown maps (debrid.ts ~41-51)
debridLinkKeyCooldowns / debridLinkKeyCooldownDetails / runtime statuses
are keyed by API key ID (FNV-1a hash of the key). When a key was put
into cooldown then removed from settings then re-added later, the old
cooldown entry would still block it.
Fix: New pruneDebridLinkRuntimeStateForKeys() function called from
setSettings() removes cooldown entries for keys no longer in the active
set.
4. providerFailures circuit-breaker map (download-manager.ts ~1990)
Per-provider failure tracking with cooldownUntil. When a user removes a
failing account and adds a new one of the same provider, the cooldown
would carry over. Now setSettings() compares per-provider credentials
and clears matching providerFailures entries when they change.
Reproduction (now fixed):
1. Add Debrid-Link key A
2. Trigger a failure (e.g. invalid link) so A goes into cooldown
3. Remove A, add B in settings
4. Try a download immediately - previously the cooldown for A or the
cached client with A's state could still prevent B from being used.
After this fix B is used immediately.
Tests: 201/201 (debrid + download-manager) green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements per-item / per-package hash-based diffing for the IPC state-update
channel. This is the architecturally biggest performance win — for queues
with thousands of items where most are idle between emits, this can cut
IPC payload size by 80-95%.
How it works:
1. New `getSnapshotForEmit()` method computes a compact hash per item and
per package covering the visible/mutable fields. On each emit it includes
only items/packages whose hash changed since the last emit, plus a list
of removed IDs. Every 30 seconds a full resync is sent for safety.
2. A new `payloadKind: "full" | "delta"` field on UiSnapshot signals the
format. `removedItemIds` and `removedPackageIds` lists carry deletions.
3. The renderer maintains a `masterSnapshotRef` and merges incoming deltas:
spreads new items over master items, deletes the removed-IDs, then sets
the merged snapshot as React state. Full payloads replace the master
entirely (initial sync + 30s resync).
4. The existing direct `getSnapshot()` API used by app-controller, debug-server,
and link-export is unchanged — they still get a full snapshot. Only the
"state" emit channel uses delta encoding.
Trade-offs accepted:
- Hash computation cost: ~13 string concats per item per emit. With 5000
items at 700ms intervals that's ~7100 hash ops/sec — well under 1ms total.
- The 30s full resync ensures any drift bug self-heals within 30s without
user-visible glitch.
- Server keeps two extra Maps (item/package hash tracking).
Items / packages that are completely idle between emits add ZERO bytes to
the IPC payload now, instead of ~450 bytes per item. For a normal queue of
5000 items where ~30 are actively downloading, payload drops from ~3.6 MB
to ~30 KB per emit — a 100x reduction.
Tests: 140/140 download-manager + 133/133 storage+auto-rename green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cloneSettings() copies 85+ fields including 6 nested usage Maps and the
bandwidth schedule array. With ~700ms emit interval most snapshot ticks
clone identical settings. Add a time-based cache (400ms TTL) so most
snapshots reuse the previous clone.
Settings are mutated in-place by hot paths (provider usage tracking, debrid
key counters), so a reference check wouldn't catch changes — but the 400ms
TTL window is short enough that user-visible setting changes still appear
within one render cycle. replaceSettings() explicitly invalidates the
cache for immediate visibility on user setting changes.
The 4 architecturally-invasive items I considered for this round —
recordSpeed batching (already 120ms-bucketed, false alarm), full IPC state
diffing (392 mutation sites, too risky), list virtualization (variable
package heights make it complex), and per-channel settings/stats split
(invasive type changes) — are deferred. This caching change captures most
of the practical settings-clone savings without the architectural risk.
All 140 download-manager tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five more low-risk hot-path optimizations:
1. visiblePackages no longer re-runs the sort callback on every item update.
The sort is only meaningful when running && autoSort && >1 packages, so we
pass null as items dep otherwise. Previously fired the full O(N) sort
pass on every progress tick even when it would have returned the input
array unchanged.
2. ItemRow memoizes formattedCreatedAt + displayStatus + statusTitle so a
row that re-renders because of progress/speed changes no longer pays for
formatDateTime() and computeDisplayedItemStatus() twice (title+body).
3. resetSessionTotalsIfQueueEmpty: removed redundant Object.keys() check.
itemCount + packageOrder.length cover the same condition without
allocating two intermediate arrays.
4. markQueuedAsReconnectWait: replaced Object.keys() array allocation with
for-in iteration when runItemIds is empty. Saves a 5000-element string
array allocation every 900ms during reconnect.
5. columnOrderJson useEffect dep: replaced JSON.stringify with cached
join() + useMemo. Stringifying a 7-element settings array on every
render was ~2-3ms per render for nothing.
All 140 download-manager tests green. Renderer + main both build clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three deeper optimizations focused on hot allocations:
1. cloneSession(): items/packages references shared instead of per-item
shallow clone. The IPC layer runs structuredClone() in the same tick
so the renderer always gets an isolated copy; in-process consumers
read snapshots synchronously without mutating. Eliminates ~5000
object allocations per emit on a 5000-item queue.
2. findNextQueuedItem(): single-pass priority scan instead of 3 separate
passes (high → normal → low). Returns immediately on high-priority
match; collects best normal/low candidate while iterating. Saves up
to 2x O(n) iterations per scheduler tick.
3. packageSpeedBps: direct loop assembly instead of
Object.fromEntries([...Map].map(...)) (3 allocs per entry → 1).
Idle case now returns a stable EMPTY_PACKAGE_SPEED_BPS reference
so the renderer's useMemo on it doesn't recompute on every snapshot
while the queue is paused/stopped.
All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major optimizations to reduce UI lag with large queues (5000+ items):
1. ItemRow extracted to its own memoized component (renderer)
Previously every package re-render mapped all its items inline,
producing N×M re-renders per state update. Now each item-row only
re-renders when ITS specific data changes, with custom equality on
the visible fields (status, progress, speed, fullStatus, etc.).
Also adds stable useCallback handlers per item.
2. PackageCard stats consolidated into single useMemo (renderer)
Replaces 5 separate filter()/some() + 2 reduce() calls (O(7N)) with
one O(N) pass collecting all aggregates (done/failed/cancelled/
extracted/extracting/activeProgress/extractingProgress).
3. selectedIds memo comparator fixed (renderer)
Custom equality now checks if selection state changed for items in
THIS package only. Previously any selection anywhere broke memo on
all 200+ visible PackageCards.
4. Scheduler single-pass queue presence (main)
New getQueuePresence() returns hasImmediate + hasDelayed in one
iteration. Replaces hasQueuedItems() + hasDelayedQueuedItems() that
each scanned packages independently. Saves one full O(n) iteration
per scheduler tick.
No functional changes. All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three low-risk optimizations that reduce CPU/memory footprint without
changing user-visible behavior:
1. Periodic cleanup of unbounded module-level Maps (24/7 stability):
- debridLinkKeyCooldowns, debridLinkKeyCooldownDetails,
debridLinkKeyRuntimeStatuses (debrid.ts)
- megaDebridAccountCooldowns (debrid.ts)
- allDebridHostInfoCache (download-manager.ts)
- All pruned every 10 min via existing resetStaleRetryState() with a
1h grace window for debugging
- Without this, modules accumulated entries indefinitely over days
of continuous server operation
2. Hoist regex literals in resolveArchiveItemsFromList() to module scope.
Avoids 5 RegExp constructions per call. The function is called per
archive set during extraction discovery — adds up on packages with
many archives.
3. BandwidthChart: skip the 250ms redraw interval while the session is
stopped or paused. The chart renders once on state change to show the
latest history, then sleeps until downloads resume. Saves 4 canvas
redraws per second when idle (renderer CPU).
No functional changes. All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three improvements for users running on servers where the Windows account
is not "Administrator" or where the environment is headless (Service, RDP-
disconnected, no interactive desktop):
1. readSettingsFile / readSessionFile: distinguish ENOENT (normal first run)
from EACCES/EPERM (permission problem). The latter logs an explicit
message including the current Windows username so the user can spot
misconfigured ACLs immediately.
2. ensureBaseDir: log EACCES/EPERM with the current username before re-
throwing. Previously the error bubbled up without any hint why the
AppData directory creation failed.
3. createTray: log a warning when Tray creation fails (typical on Windows
Service / headless servers / RDP disconnected sessions). Previously the
error was silently swallowed and minimize-to-tray would just not work
without explanation.
These errors were silently swallowed before, making it impossible to
diagnose problems on servers with restricted user accounts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The v1.7.130 BONUS_DIR_PATTERNS used substring matching with space-separated
patterns like "making of" and "behind the scenes", but real-world subfolder
names use dot/dash/underscore separators (e.g. "Breaking.Bad.S05.Making.Of").
These were NOT detected as bonus dirs, causing the safety net in v1.7.131 to
apply the source filename's episode token to the package name, producing
mislabeled bonus files like "Breaking.Bad.S05E10.GERMAN.BluRay.720p.TSCC".
Fix: normalize folder segments by stripping all separators ([._-\s]+) before
matching against BONUS_DIR_NORMALIZED_PATTERNS. "Breaking.Bad.S05.Making.Of"
normalizes to "breakingbads05makingof" which matches "makingof".
Also extend BONUS_FILENAME_RE with "inside-e\d+" and "making-of-e\d+" to
catch more filename variants from Breaking Bad BluRay extras.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Real-world scenario from user logs: package "Drei.Meter.ueber.dem.Himmel.
S01GERMAN.DL.720P.WEB.X264-WAYNE" (note malformed S01GERMAN with no
separator) caused the auto-renamer to strip the source's S01E01..S01E08
episode tokens because SCENE_SEASON_ONLY_RE doesn't match a season followed
by an immediate letter (no separator).
Result: all 8 episodes in the season pack collapsed to the same target name
and collided in the MKV library with (2)(3)(4)(5)(6)(7)(8) suffixes.
Fix: After buildAutoRenameBaseNameFromFoldersWithOptions, check if the
source filename has a valid episode token. If yes:
1. If target has NO episode token: try to insert it via regex replacement
(Sxx<garbage> -> SxxExx.<garbage>), then via applyEpisodeTokenToFolderName.
If both fail, skip the rename entirely (preserve source name).
2. If target has a DIFFERENT episode token: skip the rename (mislabel risk).
This guard is the last line of defense against the helper's regex
limitations on malformed package names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bonus content (Featurettes, Behind-The-Scenes, Making-Of, Deleted Scenes,
etc.) was being moved into the flat MKV library with generic names like
"Schrotflinte.mkv" or "White.House.mkv", losing all show context. Auto-rename
also touched these files and would mislabel them with episode tokens.
Real-world impact: 397 bonus files from Breaking Bad S03/S04/S05 BluRay
extras subdirectories landed in the user's main library with nonsense names.
Fix:
- Add isInsideBonusDir() that walks the path from file to package root,
checking each directory segment for bonus indicators (Extras, Bonus,
Featurettes, Specials, Behind-The-Scenes, Making-Of, Deleted-Scenes, etc.)
- Add BONUS_FILENAME_RE to catch bonus indicators in filenames (making-of-e02,
deleted-scene, alternate-ending, gag-reel, behind-the-scenes, etc.)
- Auto-rename: skip files matching either pattern
- MKV collection: skip files matching either pattern, log skipped count
Bonus files now stay in the package output directory with their original
names; only the actual episodes get moved to the flat library.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The stem extraction regex for matching companion metadata files (.sfv,
.nfo) to their archives only handled RAR patterns. Now also covers
ZIP, 7z, tar, generic splits, and recovery volumes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When all archive parts were already extracted in a prior hybrid round and
a companion file (.sfv, .nfo, .md5) downloads later, result.extracted is
0 so the companion's status was never updated from "Entpacken - Ausstehend"
or "Entpacken - Warten auf Parts".
Now companion metadata files are explicitly marked as "Entpackt (Metadaten)"
when no archives need extraction and no failures occurred, preventing them
from blocking package completion indefinitely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
collectArchiveCleanupTargets() now includes companion files (.sfv, .nfo,
.md5, .sha1, .sha256, .crc, .srr) that share the same base stem as the
archive parts. Previously these were left as orphans after archive cleanup.
Applies to all archive types: multipart RAR, single RAR, ZIP, split ZIP,
7z, and split 7z.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SFV files belong to the same archive set as the RAR parts but were not
included in hybridFileNames, causing them to stay stuck on "Entpacken -
Ausstehend" after the RAR parts were successfully extracted. This blocked
package completion in hybrid extraction mode.
Fix: collect archive base stems from extracted parts and match companion
metadata files (.sfv, .nfo, etc.) by stem, adding them to hybridFileNames
so they get marked as "Entpackt" together with their archive parts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SFV checksum verification files are legitimately tiny (~128 bytes) but were
rejected by the "suspicious small download" detection, causing infinite
"Direktlink erneuern" retry loops that blocked package extraction.
- Add KNOWN_SMALL_FILE_RE for .sfv, .nfo, .nzb, .md5, .sha1, .sha256, .crc,
.txt, .url, .lnk, .srr file extensions
- Skip suspicious-small-download rejection for known small files when they
match their expected size (or have no size expectation)
- Skip tiny-download error detection for known small metadata files
- Add test: verifies .sfv file downloads without retries and completes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add safeJsonReplacer to all JSON.stringify calls in storage.ts to prevent
NaN/Infinity values from corrupting state files and causing queue loss
- Fix LinkSnappy and 1Fichier retry loops: use sleepWithSignal() instead of
sleep() so abort signals are respected during retry delays
- Fix Debrid-Link polling: replace raw setTimeout with sleepWithSignal() so
URL generation polling can be cancelled
- Fix Mega-Debrid doConnectApi: clear token cache on 401/403 responses
instead of caching invalid credentials for 20 minutes
- Add logging when normalizeLoadedSession removes orphaned items so data
loss during startup is visible in logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Increase quit timeout from 900ms to 5000ms to ensure pending saves complete
- Add persistNowSync() called before update install to flush queue to disk
- Remove blockAllPersistence from shutdown save condition — shutdown must
always persist to prevent data loss across restarts
- Add temp file recovery as last resort when both primary and backup
session files are corrupted
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some hosters/debrid services obfuscate downloaded archive filenames by
mutating characters and changing extensions (e.g. .part06.rar → .part06.mov,
star_crossed → star_crossfed). This breaks extraction since the extractor
relies on filename patterns to discover archive parts.
New deobfuscateArchiveFiles() method runs after download, before extraction:
- Reads magic bytes of non-archive files via detectArchiveSignature()
- If RAR/7z/ZIP signature found: corrects the extension
- Uses correctly-named sibling .rar files as reference to reconstruct
the full correct filename including part number
- Updates item.fileName and item.targetPath after rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- notDebrid (host-level) no longer burns all keys: stops rotation immediately
with 5min cooldown instead of cycling through all 9 keys pointlessly
- Remove double provider-blockade: debrid_link_cooldown no longer stacks
recordProviderFailure + applyProviderBusyBackoff on top of key cooldowns
- Detect timeout cascades: 2+ consecutive transport failures trigger 3min
cooldown instead of burning remaining keys
- Case-sensitive rename: files with different casing (e.g. lowercase scene
names) now get properly renamed instead of being skipped as "already matching"
- Extended sample filter: detect -s.mkv suffix and \Sample\ subdirectories
in auto-rename (already worked in MKV-move)
- Add key status display with state pills in Debrid-Link key stats popup
- Add parseDebridLinkTerminalFailure for fast-fail on exhausted keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add polling loop (5x 2s) in resolveDownloaderEntry when /add returns
no downloadUrl — Debrid-Link sometimes needs seconds to generate links
- Classify missing/expired downloadUrl as temporary instead of fatal so
key rotation kicks in before giving up
- Change notDebrid from fatal to temporary — "host may be down" is
transient, all keys should be tried before failing
- Raise parseRetryAfterMs cap from 2min to 1h — floodDetected mandates
"retry after 1 hour" per API docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>