Commit Graph

359 Commits

Author SHA1 Message Date
Sucukdeluxe
ba144f323b Fix: Statistik-Doppelzaehlung bei Retry + Debrid-Link Key-Cooldown bei Abbruch (Nutzer-Nachforderung Audit)
BYTE-DROP-RETRY-1 (MED): Integritaets-/Zu-klein-/Tiny-Neuversuche zaehlten die
volle Dateigroesse pro Versuch erneut in die Byte-Statistik. Ursache: die 3
rm-dann-frisch-Sites (Integrity-Fail 8979, too-small 9020, tiny 10486) riefen
dropItemContribution() auf, das den itemContributedBytes-Eintrag loescht, den die
einzige Reconciliation (9991, writeMode 'w') zum Subtrahieren braucht -> Subtraktion
tot -> Re-Download addiert erneut. Fix (Mechanismus a): dropItemContribution an
diesen 3 Sites entfernt, der Eintrag ueberlebt, 9991 subtrahiert ihn korrekt
(selbst-korrigierend nach writeMode). Zusaetzlich am selben Punkt
totalDownloadedAllTime subtrahiert (wurde nie subtrahiert -> doppelte bei JEDEM
frischen Re-Download). BEWUSST ausgeklammert: recordProviderDownloadedBytes/
providerDailyUsageBytes, da diese isProviderDailyLimited (= Verhalten) steuern und
nicht provider-keyed sind. Reine Telemetrie-Korrektur, kein Slot/Admission betroffen.

DL-1 (LOW): Ein abort-ohne-timeout (User-Cancel) setzte am DebridLink-Rotations-
Catch via classifyKeyFailure einen 15s-Key-Cooldown (und konnte ueber Keys zu einer
providerweiten Kaskade fuehren). Fix: Mega-Gate (2072) am Catch (2789) gespiegelt -
abort-ohne-timeout + elapsedMs < getMegaDebridAbortMinRunMs() -> kein Cooldown;
ran-long-enough -> 120s (Retry rotiert); throw bailt die Rotation.

DL-CONCURRENCY-PILEUP (MED): untersucht -> bereits strukturell geloest
(getSerializedValidatingLimit = nutzbare Accounts + shouldDelayStartForItem 8526;
MW-1 haelt das Limit hoch). Kein Eingriff (Nutzer-Entscheidung).

Je rot-bewiesener Test (per Temp-Revert, nicht-vakuum; BYTE-DROP beide Beine
einzeln). Volle Suite 893 gruen, tsc unveraendert (6 vorbestehende Fehler).
2026-06-17 20:23:28 +02:00
Sucukdeluxe
85c8d6bdf9 Fix: Mega-Cooldown vor Tagesreset-Park bevorzugen + Token-Truncation + Rewind-Fail-Haertung (Synthese-Pass)
Capstone-Synthese/Regressions-Pass ueber den kumulativen Audit-Diff (v1.7.212..HEAD)
hat 3 Fix-INTERAKTIONEN gefunden — der Rest komponiert sauber.

C1 (MED): Sind BEIDE Mega-Modi aktiv (API + Web), aggregiert die Provider-Kette bei
Fehlschlag beide Token in einen String: `mega_debrid_reset_park:LONG` (API bis
Tagesreset geparkt) + `mega_debrid_cooldown:30000` (Web nur kurz im Cooldown). Der
Manager pruefte reset_park VOR cooldown → das Item wurde ~24h geparkt, obwohl der
Web-Modus in ~30s wieder verfuegbar gewesen waere. Fix: den Cooldown-Zweig (kuerzerer
Delay) VOR dem reset_park-Zweig pruefen; der erholbarere Pfad gewinnt.

C2 (LOW): Das reset_park-Token konnte von compactErrorText (220-Zeichen-Cap)
abgeschnitten werden, wenn Mega nicht der Lead-Provider war und ein vorheriger
Provider eine lange Fehlermeldung lieferte → Park wurde still uebersprungen und Mega
am Tageslimit weiter gehaemmert. Fix: beide Mega-Token aus der UNGEKUERZTEN
error.message parsen statt aus dem gekuerzten Anzeige-String.

RANGE1-truncate-fail (LOW, Haertung + Doku-Korrektur): Der v1.7.219-Commit behauptete,
das Setzen von downloadedBytes ZUERST rette den Fall eines fehlgeschlagenen finalen
Rewind-truncate ueber den Re-Entry-prealloc-reconcile. Das stimmt nicht — bei
truncate-Fehler finalisiert tryFinalizeItemFromDisk die exakt-langen Garbage-Datei VOR
dem Re-Entry (net-neutral vs pre-audit, KEINE Regression, aber der Claim war falsch).
Haertung: bei fehlgeschlagenem finalem Rewind die Teil-Datei verwerfen
(rmSync + downloadedBytes=0) → Finalize lehnt ab → sauberer Re-Download. Best-effort
(faellt der rm auch, bleibt es net-neutral) — macht den 219-Claim jetzt wahr.

Rot-bewiesen: Single-Pass-processItem-Test mit Aggregat beider Mega-Token → retryAfter
< 60s (Cooldown) und fullStatus "Cooldown" statt "Tagesreset"; mit deaktiviertem
Cooldown-Zweig (alte Reihenfolge) ~24h-Park = rot. Volle Suite 886 gruen, tsc=6.
2026-06-17 12:51:29 +02:00
Sucukdeluxe
eacd0c9d81 Fix: stille Mid-File-Korruption beim Resume nach Verbindungsabbruch (known-total) + Rewind-Haertung (Runde-8-Audit)
RANGE-1 (HIGH, adversarisch 3/3): Wenn ein Debrid-Server beim Socket-Abbruch
einen Garbage-Block in den Body schreibt UND das im LETZTEN inneren Versuch
passiert, wurde der Tail nicht zurueckgespult: der `attempt < maxAttempts`-Guard
verhindert das Vormerken, resumeRewindBytesNextAttempt ist funktions-lokal und
ueberlebt den downloadToFile-Re-Entry nicht, und der Outer-Handler nimmt fuer
terminated-class den generic-retry-Zweig (loescht die Teil-Datei NICHT). Beim
Fresh-Link-Resume wurden die echten Bytes NACH dem Garbage angehaengt → eine
Datei mit exakt korrekter Laenge, die die reine Laengen-Pruefung
(validateDownloadedFileCompletion) besteht; bei manifestlosen .mkv/.mp4 ohne
Pruefsumme fiel die Korruption nie auf.

Fix: am Exhaustion-Punkt vor dem Throw den verdaechtigen Tail zurueckspulen
(truncate auf written - RESUME_REWIND_BYTES, downloadedBytes ZUERST gesetzt,
damit der binary-Re-Entry-prealloc-reconcile auch bei fehlgeschlagenem truncate
greift). GEGATED auf bekannte Groesse (totalBytes != null && > 0): dann ist
rewound size < totalBytes = minBytes, sodass tryFinalizeItemFromDisk die
gekuerzte Datei ablehnt und der Re-Entry den Garbage sauber ueberschreibt.

Ehrliche Einordnung: behoben fuer Medien mit bekannter Groesse (Debrid liefert
fast immer fileSize). Der seltene Fall OHNE Groessenangabe behaelt eine separate,
schon vorher bestehende stille Korruption unter dem dokumentierten
Size-only-Validation-Blindspot — durch dieses Rueckspulen nicht loesbar (kein
Laengensignal; nur ein Hard-Reset wuerde helfen, groesserer separater Eingriff).
Bewusst NICHT angefasst, um keinen vierten Bug einzubauen.

REWIND-TRUNCATE-FAIL (MED, 3/3): Das `finally` setzte
resumeRewindBytesNextAttempt=0 unbedingt — auch wenn die Rewind-truncate warf
(transienter win32-EBUSY/AV-Lock) → Garbage-Tail blieb, Flag gecleart, nie
erneut versucht. Fix: Reset nur noch im Success-Branch, sodass ein
fehlgeschlagenes Rueckspulen im naechsten Versuch erneut probiert wird.

PREALLOC-ZEROS-ACCEPTED (MED, schwaechste) dokumentiert nicht gefixt: dominante
Medien/Archive sind immun (footprint-threshold 0); narrow non-binary-Conjunction.

Advisor vor Implementierung konsultiert (HIGH-Hot-Path); Check A (totalBytes-null
finalisiert die gekuerzte Datei) am echten Code verifiziert und der Fix
entsprechend auf known-total gegated.

Rot-bewiesen: Cross-Call-Integrationstest (final-attempt Garbage-Inject + Drop →
Exhaustion → queueRetry → 2. downloadToFile via Fresh-Link → byte-genaue
Inhaltsgleichheit). Ohne den Fix ist die Laengen-Assertion gruen und die
Inhalts-Assertion rot (exakt-laengen-Korruption) — genau die RANGE-1-Signatur.
Volle Suite 885 gruen, tsc unveraendert (6).
2026-06-17 12:16:51 +02:00
Sucukdeluxe
d2a1b831a0 Fix: festes Retry-Limit wird jetzt hart eingehalten — kein Endlos-Shelve-Loop mehr (Runde-6-Audit)
Bei einem FESTEN retryLimit (>=5) sind alle drei Per-Klasse-Retry-Caps
(stall/unrestrict/generic) gleich dem Limit. Die beiden "15-Fehler"-Shelve-Zweige
(download-manager.ts stall-Pfad + error-Pfad) feuern aber auf der hartkodierten
Summe >=15 OBERHALB der Per-Klasse-Terminal-Fail-Pruefungen und HALBIEREN danach
alle drei Zaehler. Dadurch wurden die Per-Klasse-Caps nie gleichzeitig
ueberschritten: der Eintrag erreichte nie status="failed", schliff endlos im
90s-Takt, item.retries wuchs weit ueber das konfigurierte Limit, der
Download-Slot blieb dauerhaft belegt, und das wiederholte
providerFailures.delete besiegte immer wieder den Provider-Circuit-Breaker →
Hoster-Hammering. resetStaleRetryState konnte nicht eingreifen, weil der
<=90s-Re-Admit item.updatedAt frisch haelt (nie 10min stale).

Der Auslieferungs-Default retryLimit=0 (= unendlich) ist NICHT betroffen — dort
ist der 15-Fehler-Shelve der gewollte Dauer-Park-Backstop und es gibt kein
endliches Budget zu verletzen.

Fix: in BEIDEN Shelve-Zweigen vor dem queueRetry eine harte Obergrenze —
`if (configuredRetryLimit > 0 && item.retries >= configuredRetryLimit)` failt den
Eintrag terminal (status="failed", recordRunOutcome, retryStateByItem.delete)
statt ihn neu zu queuen. Der ∞-Modus (retryLimit<=0) ueberspringt die Grenze und
bleibt unveraendert.

Runde-6-Audit (adversarisch verifiziert) hat ausserdem den deferred-LOW-Cluster
abgeschlossen: #10 HTTP416-shared-counter, #11 fresh-retry-preempt und #13
queue-wait→elapsedMs sind BENIGN; #12 war genau dieser Bug.

Rot-bewiesen: Single-Pass-Integrationstest seedet genericErrorRetries=15 vor und
injiziert einen generischen Fehler bei retryLimit=5 → ohne Fix requeued der
Shelve (status bleibt nicht "failed"), mit Fix wird terminal gefailt. Volle Suite
882 gruen, tsc unveraendert.
2026-06-17 10:58:18 +02:00
Sucukdeluxe
f1e35f5f41 Fix: Mega "bis Tagesreset gesperrt" parkt das Paket bis zum Reset statt es den ganzen Tag alle 2 min neu zu versuchen
Wenn ALLE Mega-Debrid-Accounts wegen wiederholt leerer/Server-loser Antworten
bis zum Tagesreset geparkt waren (in-memory untilRestart-Park aus Runde 3),
warf unrestrictWithAccounts einen reinen Klartext-Fehler ohne Maschinen-Token.
Im Manager-Catch fiel dieser durch parseMegaDebridCooldownRetry (kein
mega_debrid_cooldown:-Praefix) und isMegaDebridTransientResolveFailure und
landete im generischen isUnrestrictFailure-Zweig — nur weil der Text
"mega-debrid" enthaelt. Folge: das Item wurde den ganzen Tag etwa alle zwei
Minuten neu versucht (120s-Cap) und fuetterte dabei recordProviderFailure den
Provider-Circuit-Breaker, statt einmal bis zum Tagesreset zu parken. Bei
Standard-Einstellungen (retryLimit=0=unendlich) heilte es sich zwar um
Mitternacht selbst (kein Stranding), war aber unnoetige Log-Flut und Churn und
unterlief genau die untilRestart-Park-Absicht aus Runde 3.

Cross-Layer-Synthese-Pass (adversarisch verifiziert) hat parallel die
Advisor-Hypothese eines Key-Mismatch (Manager cool't aufgeloesten
"megadebrid-api", Fallback-Suche liest rohen "megadebrid") WIDERLEGT:
normalizeProviderOrder speichert immer den aufgeloesten Key, also faellt
Write/Check/Clear/Read auf denselben Key — der Routing-Fix (986fbab) ist fuer
den Mega-Fall nachweislich sicher.

Fix (konservativ, spiegelt den bereits korrekten mega_debrid_cooldown-Zweig):
- debrid.ts emittiert beim untilRestart-Park jetzt den Token
  mega_debrid_reset_park:<msBisReset>: (Delay aus megaDebridDailyParkExpiry,
  deckt sich exakt mit dem in-memory Park-Ablauf).
- download-manager.ts: neue reine parseMegaDebridResetPark (kein 15min-Clamp,
  26h-Cap) + Catch-Branch VOR der Cooldown-Klassifikation, der bis zum
  Tagesreset queued OHNE recordProviderFailure (ein geplanter Park ist kein
  Provider-Fehler).
- Token enthaelt weiter "mega_debrid" → jeder Fall-through landet schlimmstenfalls
  im heutigen Verhalten (sichere Untergrenze).

Tests: 5 neue (parseMegaDebridResetPark: parst/embedded/kein-15min-Clamp/Cap+Junk,
plus cooldown-Parser ignoriert den neuen Token) + die all-parked debrid-Assertion
prueft jetzt den Token. Volle Suite 881 gruen, tsc unveraendert.
2026-06-17 10:16:16 +02:00
Sucukdeluxe
986fbab04f Fix: Provider-Cooldown-Fallback fuehrt die Kette jetzt wirklich mit dem Ersatz-Provider an (Routing-Slice)
Wenn ein Provider in den Manager-Cooldown laeuft (>=20 Fehler in Folge) und
auto-Fallback aktiv ist, berechnete der Manager zwar einen Ersatz-Provider
(findFallbackProviderNotInCooldown), warf ihn dann aber weg: der unrestrictLink-
Aufruf bekam keinen Hint, also baute debrid.ts die Provider-Kette frisch aus
providerOrder und fuehrte erneut mit dem ausgebremsten Provider 1 an — ein meist
in den 60s-Timeout laufender, verschwendeter Versuch, bevor ueberhaupt der
gesunde Provider drankam.

Jetzt wird der Ersatz-Provider als bevorzugter Lead durchgereicht. Die Kette
wird UMSORTIERT, nicht beschnitten: der ausgebremste Provider bleibt als letzter
Notnagel in der Reihenfolge, sodass kein Link gestrandet werden kann. Sind ALLE
Provider im Cooldown, erreicht der Aufruf unrestrictLink ohnehin nicht (der
else-Zweig queued einen Retry). Greift damit nur, wenn ein Provider bereits
nachweislich degradiert ist — strikt besser, wenn aktiv, und ohne Timeout-,
Cancel- oder Budget-Vertrag anzufassen.

- debrid.ts: reine leadProviderChainWith(order, preferred) (export) + optionaler
  4. Param preferredLeadProvider an unrestrictLink; Reorder direkt nach Aufbau
  der Reihenfolge.
- download-manager.ts: Ersatz-Provider in preferredLeadProvider festhalten und
  an den unrestrictLink-Aufruf durchreichen.
- Tests: Integration (ohne Hint fuehrt realdebrid, mit Hint fuehrt debridlink) +
  drei reine Helper-Tests fuer die No-Stranding-Invariante.

Zwei verbleibende HIGH-Punkte sind bewusst NICHT autonom geaendert, sondern als
Produkt-/UI-Entscheidung in tasks/entscheidungen-offen.md vorgelegt (globaler
60s-Failover-Timeout; gespiegelter Mega API/Web-Schalter).
2026-06-17 06:23:44 +02:00
Sucukdeluxe
c8ea2f6765 Fix: Startup-Dedup ersetzt keine gute kanonische Datei mehr durch ein kleineres Duplikat
Beim Start gleicht das Tool Duplikat-benannte Dateien ("Name (1).ext") gegen die
kanonische Datei ("Name.ext") ab. Im Zweig "Duplikat rangiert hoeher und das
kanonische Item ist nicht 'completed'" wurde bisher bedingungslos
fs.rmSync(kanonisch) + fs.renameSync(Duplikat → kanonisch) ausgefuehrt — ohne die
echten Dateigroessen zu vergleichen. Der Rang mischt den persistierten STATUS mit
der Disk-Existenz; nach einem Absturz kann der Status von der Realitaet abweichen.
War die kanonische Datei auf der Platte tatsaechlich vollstaendig/gut, ihr Item
aber z.B. "failed"/"queued", waehrend das Duplikat-Item ein veraltetes
"completed" ueber einer kleineren/partiellen Datei trug, wurde die gute
kanonische Datei geloescht und durch das schlechtere Duplikat ersetzt —
unwiederbringlicher Verlust der besseren Datei. Zusatzrisiko: rmSync erfolgreich,
renameSync wirft EXDEV → kanonisch weg, nichts installiert.

Fix:
- Size-Guard: Ist die kanonische Datei >= dem Duplikat, wird sie behalten und das
  (kleinere/gleich grosse) Duplikat verworfen. Eine kleinere Datei kann eine
  groessere nie ueberschreiben.
- Sichere Reihenfolge fuer den echten Austausch (kanonisch kleiner): kanonisch →
  ".dedupbak" umbenennen, dann Duplikat → kanonisch; bei Fehler (EXDEV o.ae.)
  wird die kanonische Datei aus dem Backup wiederhergestellt. Kein Loss-Fenster
  mehr. Erst nach Erfolg wird ".dedupbak" entfernt.

Test: kanonisch 1024 B (gut) + Duplikat 256 B (partiell), primaeres Item "failed"
(Duplikat rangiert hoeher) → die kanonische Datei bleibt mit 1024 B erhalten.
Ohne den Size-Guard wird sie durch die 256-B-Datei ersetzt (rot bewiesen). Volle
download-manager-Suite (168) gruen, tsc unveraendert 6.
2026-06-17 05:13:40 +02:00
Sucukdeluxe
2b639b7267 Fix: Teildatei wird beim Entfernen eines laufenden Downloads gelöscht (kein Datei-Waise)
Wurde ein gerade herunterladendes Item entfernt (removeItem), brach der Abbruch
zwar den Stream ab, aber die angefangene Teildatei blieb für immer auf der
Platte: removeItem löscht das Item synchron aus der Session, und der danach
laufende processItem-catch stieg in der allerersten Zeile (Session-Mitglied-
schaftsprüfung `session.items[id] !== item`) per return aus — also VOR dem
Cancel-Zweig, der die geclaimte Teildatei via fs.rmSync entfernt. Folge:
verwaiste Bytes, die kein Item mehr referenziert, plus beim erneuten Hinzufügen
desselben Links eine stille Duplikat-Benennung "Name (1)".

Fix: Im catch wird vor dem frühen return — wenn das Item nicht mehr in der
Session ist und der Abbruchgrund "cancel" war — die geclaimte Teildatei
(claimedTargetPathByItem bzw. item.targetPath) gelöscht. Das passiert im catch,
also NACH dem Schließen des Streams (kein Race mit dem noch offenen Write-
Stream). releaseTargetPath/dropItemContribution laufen wie bisher (finally bzw.
removeItem). Eng auf cancel+entferntes-Item begrenzt → keine Auswirkung auf
normale/laufende Items.

Test: held-Connection hält den Download aktiv, removeItem mitten im Stream; die
Teildatei ist danach weg. Ohne den Fix bleibt sie liegen (rot bewiesen, Timeout).
2026-06-17 05:02:46 +02:00
Sucukdeluxe
2646cba1c7 Fix: Stille Datei-Korruption auf Windows bei vorab-allozierten Downloads (Null-Bytes am Ende)
Auf Windows wird die Zieldatei vor dem Download auf die erwartete Groesse
vorab-alloziert (zero-extend via fd.truncate(item.totalBytes)) und dann ueber
einen r+/start:0-Stream beschrieben — dieser ueberschreibt ab Offset 0, kuerzt
die Datei aber nie. item.totalBytes ist dabei die Provider-Metadaten-Groesse
(knownTotal), nicht zwingend die echte content-length.

Lieferte der Server real weniger Bytes als vorab-alloziert (content-length kleiner
als die Provider-Angabe, oder Verbindungsabbruch mit sauberem EOF), blieb der
Schwanz der Datei mit Null-Bytes gefuellt. Die anschliessende stat-Korrektur hat
`written` dann FAELSCHLICH auf die On-Disk-Groesse (= Alloc-Groesse inkl.
Null-Padding) HOCHgesetzt. Dadurch wurde sowohl die Underflow-Pruefung als auch
das nachgelagerte Pre-alloc-Truncate ausgehebelt: Das Item galt als zu 100%
fertig, obwohl ein grosser Teil der Datei Nullen waren — echte Datentraeger-
Korruption, exakt auf der Plattform des Nutzers (win32).

Fix: Neue reine Funktion reconcileFinalizedSize() entscheidet die Korrektur.
Fuer vorab-allozierte Dateien wird `written` nur noch nach UNTEN korrigiert
(echter Kurz-Write), niemals nach oben auf das Null-Padding. Damit greifen die
bestehende Underflow-Validierung und das Pre-alloc-Truncate wieder wie vorgesehen:
- Voll heruntergeladen (content-length erreicht): Null-Padding wird abgeschnitten,
  Datei ist korrekt.
- Echt zu kurz: wird korrekt als Underflow erkannt und neu versucht.

Verhaltensaenderung (bewusst): Wenn die Provider-Metadaten-Groesse die echte
Dateigroesse UEBERSCHAETZT (Provider sagt 1 GB, echte Datei ist 300 MB und
vollstaendig), galt der Download bisher still als fertig (mit Null-Padding-
Korruption). Jetzt wird er als Underflow gewertet und neu versucht. Sichtbarer
Fehlschlag ist einem still korrupten "fertigen" File vorzuziehen. Die
ALLOCATION_UNIT_SIZE-Toleranz deckt kleine Abweichungen weiterhin ab. (Falls je
ein "wiederholt denselben Link" gemeldet wird, fuehrt die Spur hierher.)

Test: reconcileFinalizedSize-Matrix (Pre-alloc+Padding bleibt echte Byte-Zahl,
Pre-alloc+Kurz-Write schrumpft, Nicht-Pre-alloc beidseitig, Invalid-stat,
No-op-Gleichstand) — der Padding-Fall ist rot ohne den Fix.
2026-06-17 04:48:11 +02:00
Sucukdeluxe
3bd6e3b23e Härtung Download/Rotation (Audit-Batch 1): Scheduler-Freeze, Cooldown-Respekt, Daily-Reset, Kategorisierung
Aus einem adversarisch verifizierten Multi-Agent-Audit (14 confirmed/11 refuted):

#1 HIGH Scheduler-Freeze: findNextQueuedItem hatte keinen activeTasks-Guard.
Wird ein Item zurueckgesetzt/ueberschrieben, waehrend sein alter Task noch in
einem nicht-abbrechbaren await parkt (z.B. Integritaets-Check), liefert
findNextQueuedItem dasselbe Item, startItem lehnt es ab ohne activeTasks zu
verkleinern → der SYNCHRONE Admission-Loop dreht endlos → Event-Loop friert
permanent ein. Fix: `if (this.activeTasks.has(itemId)) continue;`. Repro-Test
(ohne Fix haengt sogar der vitest-Timeout — Freeze bewiesen).

#2/#3 MED mega_debrid_cooldown:<ms> wurde verworfen: kein Parser (nur das
debrid_link-Analogon) → Item lief in den generischen 5s-Exponential-Backoff
und fragte cooled Accounts im Sekundentakt erneut an. Neuer
parseMegaDebridCooldownRetry (nimmt das frueheste Cooldown-Ende ueber alle
Accounts) + Handler VOR transient/generic → Item wartet die echte Cooldown-Zeit.

#7/#8 MED Mega per-Account Tages-Usage wurde am Tageswechsel nie zurueckgesetzt
(ensureProviderDailyUsageFresh ruecksetzte nur provider/debrid-link), und weil
es den Tagesschluessel zuerst weiterstellte, lief auch der Reset in
addMegaDebridAccountDailyUsageBytes ins Leere → Accounts blieben den ganzen Tag
faelschlich "am Limit" und schrumpften das (neue) Pro-Account-Umwandlungslimit.
Fix: megaDebridAccountDailyUsageBytes im Tageswechsel mit zuruecksetzen.

#14 LOW classifyAccountFailure: rate_limit-Branch vor quota (quota matchte
"limit" in "rate limit" → Fehl-Kategorisierung).

852/852 gruen, tsc unveraendert (6). #4 (empty→until-restart) bewusst deferred.
2026-06-17 04:22:20 +02:00
Sucukdeluxe
73004f3864 Fix: Selbst-Lahmlegung der Mega-Debrid-API beheben (1-Token-pro-Account + falscher 60-min-Cooldown)
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.
2026-06-17 03:30:04 +02:00
Sucukdeluxe
5309f8dd69 Diagnose: Vollstaendiges Conversion-Trace-Logging (conversion.log) fuer haengende/langsame Link-Umwandlung
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.
2026-06-17 02:52:44 +02:00
Sucukdeluxe
876483da51 Fix: R2-Web-Limit zaehlt nur Accounts ohne laufenden Web-Cooldown
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.
2026-06-17 02:16:22 +02:00
Sucukdeluxe
6d4da02f92 Fix: Mega-Debrid-Umwandlung robust + parallel — schneller Retry, alle Accounts, sichtbarer Grund
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.
2026-06-17 02:07:25 +02:00
Sucukdeluxe
db59b59053 Fix: Mega-Debrid "Fichier supprime" ist transient, nicht tot -- korrigiert v1.7.210
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.
2026-06-17 00:45:09 +02:00
Sucukdeluxe
938b84392d Fix: Tote Mega-Debrid-Links (frz. "Fichier supprimé") vergiften nicht mehr die ganze Queue
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.
2026-06-17 00:23:50 +02:00
Sucukdeluxe
cbb3fce7ef Fix: Hybrid-Race liess .DL.+Doppeltonspur auf einzelnen Episoden zurueck
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.
2026-06-10 15:53:47 +02:00
Sucukdeluxe
3fb9e85ba2 Fix: Run-Lebenszyklus — Stop-Summary, Scheduler-Race, ehrliche Run-Ende-Meldung, Boot-Re-Arm
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.
2026-06-10 00:34:31 +02:00
Sucukdeluxe
8acb22d3af Fix: Dedup-Lifecycle der Benachrichtigungen — Recovery/Retry/Trailing-Pakete
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.
2026-06-10 00:28:50 +02:00
Sucukdeluxe
99a24592c5 Fix: Paket-Endstatus + Webhook gingen verloren, wenn das letzte Item fehlschlug
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.
2026-06-10 00:22:49 +02:00
Sucukdeluxe
8ab87bda51 Feature: Discord-Ping in Benachrichtigungen (optionale Erwaehnung)
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).
2026-06-09 21:52:47 +02:00
Sucukdeluxe
41cf36890b Benachrichtigungen: ntfy ersetzt durch Discord-Webhooks
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).
2026-06-09 21:40:39 +02:00
Sucukdeluxe
e753ea1296 Feature: Push-Benachrichtigungen (ntfy/Webhook) bei Paket fertig/fehlgeschlagen + Run-Ende
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.
2026-06-09 20:50:58 +02:00
Sucukdeluxe
2a1a55401e Feature: Tonspur-Ergebnis sichtbar am Paket (audioStripSummary)
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).
2026-06-09 20:42:22 +02:00
Sucukdeluxe
dc05b51083 Fix: Settings-only-Backup-Import wischte Live-Queue + Zaehler (B/I)
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).
2026-06-08 22:51:16 +02:00
Sucukdeluxe
3c33b988c3 Fix: Post-Process-Identity-Guard (J) + Remux-Temp nie ins Library sammeln (Q)
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.)
2026-06-08 22:46:14 +02:00
Sucukdeluxe
2b93f47d3a German-audio step: mislabeled-tag fallback, full logging, shorter temp
- 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
2026-06-08 14:50:34 +02:00
Sucukdeluxe
77661389f3 Add "keep only German audio" post-extract step for .DL. files
- 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
2026-06-07 21:17:26 +02:00
Sucukdeluxe
468df99142 Add extended diagnostics logging
- Electron crash handlers (render-process-gone, child-process-gone,
  unresponsive/responsive, process warnings) with a circuit-breaker
  auto-reload for renderer crashes
- Renderer error capture (window.onerror, unhandledrejection, React
  ErrorBoundary) forwarded to the main log via a one-way IPC channel
- Memory-pressure heartbeat measured against the V8 heap_size_limit
- Gated DEBUG log level (RD_DEBUG) and an in-memory ring of recent
  WARN/ERROR lines, exposed via the /errors endpoint and support bundle
- Disk-error classification (ENOSPC etc.) on download failures and
  integrity-check pass/fail logging
2026-06-07 17:00:06 +02:00
Sucukdeluxe
3ed3877ac9 chore: remove all source code comments and internal artifacts
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.
2026-06-06 04:53:54 +02:00
Sucukdeluxe
07b034440b Fix: bereits sauber benannte Folgen werden vom Collect nicht mehr verkrueppelt (Miniserien)
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.
2026-06-06 02:51:46 +02:00
Sucukdeluxe
339c46bdd2 Fix: Folgen in vollstaendigem Episoden-Ordner OHNE -GROUP-Suffix werden umbenannt
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).
2026-06-05 17:54:02 +02:00
Sucukdeluxe
afba79cdfd Fix: Folgen mit Bonus-Wort im Titel bleiben nicht mehr in "Downloader Fertig" liegen
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.
2026-06-04 23:05:50 +02:00
Sucukdeluxe
dc271e08ff Renaming: vollstaendigen Episoden-Ordner als Namen nutzen, wenn Quelle keinen SxxExx-Token hat
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>
2026-06-04 02:32:29 +02:00
Sucukdeluxe
5349554b01 Renaming: Scene-Gruppen mit Unterstrich erkennen (-idTV_iNT) — kein Verschlimmbessern zum Paketnamen
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>
2026-06-03 11:55:42 +02:00
Sucukdeluxe
288a0762a6 Renaming 100%: collect leitet sauberen Namen selbst ab (gemeinsame Entscheidungsfunktion + Wurzel-Schutz)
User-Report (aus dem Desktop-Rename-Log): 17 Dateien landeten ROH in der Library
("tvarchiv...s07e12-720.mkv", "4sf-...s04e01.mkv") — Auto-Rename hatte sie verpasst, der
MKV-Collect schob sie mit dem rohen Scene-Namen weg.

Root Cause 1: Auto-Rename und collectMkvFilesToLibrary sind entkoppelte Scans. Auto-Rename
benennt nur present-and-stable Dateien in extractDir um; eine verpasste Datei (verpasster
Zyklus ODER lag in "Downloader Unfertig" ausserhalb extractDir) wurde von collect roh
weggeschoben (collect behielt blind den Basename).
Root Cause 2: decideAutoRenameBaseName fabrizierte Namen fuer token-lose generische Ordner
("Mega-Direct-Pack" -> "Mega-Direct-Pack.S01E01") wegen eines hasSceneGroupSuffix-Falsch-
Positivs auf "-Pack" — derselbe latente Bug haette Auto-Rename getroffen.

Fix:
- Namens-Entscheidung in EINE pure Funktion extrahiert: decideAutoRenameBaseName (Single
  Source of Truth fuer Auto-Rename UND Collect — koennen nicht mehr divergieren).
- Wurzel-Schutz darin: Rename nur, wenn ein folderCandidate einen echten Season-/Episode-
  Token traegt (kein Fabrizieren aus token-losen Ordnern). Fixt beide Pfade.
- collectMkvFilesToLibrary leitet den sauberen Namen via dieser Funktion ab (gegated auf
  autoRename4sf4sj — respektiert die Umbenenn-Einstellung), inkl. Companion-Untertitel und
  Dedup gegen den sauberen Namen. mkvFiles traegt jetzt sourceRoot fuer die Ordner-Herleitung.
- Auto-Rename-Loop nutzt jetzt die gemeinsame Funktion (behebt nebenbei 2 latente
  use-before-declaration/TDZ-Fehler an resolveRenameItem).
- Latenter Bug: Casing-Zaehler renamedCount -> renamed (war undeklariert -> ReferenceError,
  vom catch verschluckt -> Casing-Korrekturen wurden still verworfen).

Verifiziert: tsc 6 (von 9 — 3 latente Fehler nebenbei behoben), 678 Tests + 9 neue (7
Charakterisierung der Entscheidung + 2 Collect-Integration: raw->clean + Companion/.srt folgt
+ Datei ausserhalb extractDir), Build gruen. Adversarialer Review-Workflow (4 Linsen) + Advisor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 01:09:21 +02:00
Sucukdeluxe
8d03ca124f Update-Neustart: laufende Downloads als queued parken statt als "Gestoppt" haengenzubleiben
Beim Update parkte installUpdate() aktive Downloads via stop() -> deren Abbruch-
Continuation markierte die Items "cancelled"/"Gestoppt". autoResumeOnStart nimmt
nach dem Neustart aber nur "queued"/"reconnect_wait" auf, also liefen die gerade
ladenden Downloads nach dem Update nicht weiter (timing-abhaengig: "manchmal").
Jetzt: stop({parkForRestart:true}) bricht aktive Tasks mit Grund "shutdown" ab,
sodass sie als "queued" re-queued werden (wie bei normalem App-Shutdown). Das
schliesst zugleich den einzigen plausiblen Loesch-Pfad (all-cancelled-Pakete sind
ueber applyRetroactiveCleanupPolicy entfernbar). Stop-Button-Verhalten unveraendert.

Zusaetzliche Robustheit in storage.ts (enge Blast-Radien, nicht die Hauptursache):
- async-Save-Clobber: eine gequeuete, veraltete Payload konnte einen neueren
  Sync-Save (persistNowSync/prepareForShutdown) ueberschreiben; Generation wird
  jetzt zum Snapshot-Zeitpunkt erfasst und durch die Queue getragen.
- loadSession gab leer zurueck (und ignorierte ein gefuelltes .bak), wenn die
  Primaerdatei fehlte; faellt jetzt auf die Backup/Temp-Recovery zurueck.

Regressionstests: tests/update-restart-resume.test.ts (echter Live-Download ->
Park -> Reload = queued, plus Charakterisierung plain stop() -> cancelled) und
tests/session-restart-loss.test.ts (Clobber + Backup-Fallback). Volle Suite gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 05:19:54 +02:00
Sucukdeluxe
251c41ca6c Renaming-Logging: lueckenloses Desktop-Protokoll pro Sitzung + Post-Rename-Verifikation
User-Goal: bei kuenftigen Renaming-Problemen eine vollstaendige, sofort auffindbare Uebersicht —
JEDER Umbenenn-/Verschiebevorgang protokolliert UND danach verifiziert (liegt die Datei wirklich
unter dem Zielnamen? Quelle weg? richtige Schreibweise?).

- NEU desktop-rename-log.ts: pro Sitzung <Desktop>/Downloader-Log/rename-session_<ts>.txt; Ordner
  selbstheilend (mkdir recursive vor jedem Write -> auch nach Loeschung zur Laufzeit sofort wieder
  da). Synchroner Append, Schreibfehler verschluckt (bricht nie einen Download).
- verifyRename (sync) + verifyRenameAsync (Hot-Path): prueft Ziel-Existenz, echten On-Disk-Namen
  (case-genau via readdir), Quell-Abwesenheit; Level INFO/WARN/ERROR. Nutzt denselben \?\-Long-
  Path-Prefix wie der echte Rename (sonst falsche Urteile auf langen Scene-Pfaden).
- download-manager: renamePathWithExdevFallback = verifizierter Wrapper um die unveraenderte
  Raw-Logik (deckt alle Media-Renames ab) + 3 Sync-Sites (startup-Dedup, Deobfuskation, Suffix-Fix)
  via logVerifiedRenameSync; logRenameProcess spiegelt ins Desktop-Log.
- app-controller init/shutdown (getPath("desktop") gegen Startup-Crash abgesichert); support-bundle
  packt das Log mit ein.

Adversarialer Review-Workflow (4 Linsen) fand + behoben: Long-Path-Verify-Bug (falsches OK
maskiert halb-fertigen Move), readdir-Fehler-False-OK, sync-I/O im Hot-Path, getPath-Guard,
Test-Temp-Cleanup. tsc 9 (Baseline), 663 Tests (+7 neue), Build gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:14:55 +02:00
Sucukdeluxe
dd31bee8b1 Rotation: jeden Account-Versuch ins ITEM-Log schreiben (Sichtbarkeit)
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>
2026-05-30 22:50:49 +02:00
Sucukdeluxe
3977184fd4 Account-Rotation: Login/Premium-Badges + Live-Rotations-Panel + "Alle pruefen"
- Pro Mega-Debrid-Account UND Debrid-Link-Key im Bearbeiten-Dialog: Badge mit
  Login-Gueltigkeit + Premium-Restlaufzeit (connectUser vip_end / account/infos premiumLeft)
- "Alle pruefen"-Button oben rechts; prueft alle Accounts (Concurrency-Cap 4),
  Ergebnis persistiert (debridAccountStatuses), ueberlebt Neustart
- Rotations-Verlauf-Panel: zeigt live welcher Account/Key versucht wurde + warum
  gewechselt (Ring-Buffer -> Snapshot -> UI), statt nur "Link-Umwandlung erneut"
- Bug A: Mega-Debrid Per-Account-Verbrauch wurde nie erfasst (Heute/Insgesamt immer 0)
- Bug B: isProviderConfigured erkannte reine megaCredentials-Multi-Config nicht
- Neu: account-check.ts (standalone), CHECK_DEBRID_ACCOUNTS IPC, 13 Tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:19:23 +02:00
Sucukdeluxe
35622445da Fix: Deferred-Final-Pass benennt frische Dateien vor dem Collect um
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>
2026-05-28 22:16:01 +02:00
Sucukdeluxe
18eada963f Fix: Hybrid-Rename-Race — 1-2 Dateien pro Staffel blieben unbenannt
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>
2026-05-28 17:44:19 +02:00
Sucukdeluxe
08372f99cb Fix: neu hinzugefügtes Archiv-Passwort greift jetzt ohne App-Neustart
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>
2026-05-24 19:07:22 +02:00
Sucukdeluxe
7d52d5a495 Deferred-Post-Processing Lifecycle härten (H1/H2/M1) + 0-Byte-Fix (H3) + Dead Code (N1)
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>
2026-05-23 16:39:34 +02:00
Sucukdeluxe
ceda9817f8 v1.7.156 HOTFIX: MKV-Collection loescht keine pending Archive im outputDir mehr
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>
2026-05-23 15:12:00 +02:00
Sucukdeluxe
7ba8dd07b9 v1.7.154 collectMkvFilesToLibrary scannt jetzt extractDir UND outputDir
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>
2026-05-23 00:43:43 +02:00
Sucukdeluxe
6fcad8bc6c v1.7.153 zwei Production-Regressionen aus v1.7.151 gefixt
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.
2026-04-22 17:22:14 +02:00
Sucukdeluxe
7f7bcf8ab2 v1.7.151 Review-Findings nachgepflegt: 3 Edge-Cases entschaerft
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.
2026-04-22 02:25:10 +02:00
Sucukdeluxe
709a93b405 Auto-Rename Hardening: 9 weitere Bugs aus 10-Agent-Audit gefixt
B) Symlink-Following + Library-Cross-Risk verhindert
   - collectFilesByExtensions skippt jetzt Symbolic Links / Junctions
     (entry.isSymbolicLink) — der v1.7.107-Korruptions-Vektor kann nicht
     mehr ueber Reparse-Points zurueckkehren
   - autoRenameExtractedVideoFiles bricht ab wenn extractDir mit
     mkvLibraryDir ueberlappt (in beide Richtungen) → keine Cross-
     Package-Korruption durch fehlerhafte User-Konfig
   - collectMkvFilesToLibrary mit gleichem Schutz fuer sourceDir<->targetDir

C) Long-Path Silent Skip behoben
   - buildSafeAutoRenameTargetPath prueft jetzt zusaetzlich Gesamtpfad-
     Laenge (247 chars conservative Windows-Limit), nicht nur Datei-
     Namen-Laenge. Fallback zu kuerzerem Pfad greift jetzt zuverlaessig

D) Hybrid-Extract Partial-Write Race entschaerft
   - Files mit mtime juenger als 2s werden uebersprungen (im naechsten
     Scan re-evaluiert). Verhindert Rename auf gerade-noch-gschriebene
     MKVs waehrend Hybrid-Extract parallel arbeitet
   - Konfigurierbar via fileStabilizeMinAgeMs (Tests: VITEST=true => 0)

E) Retry-Logik fuer transiente Rename-Fehler
   - renamePathWithExdevFallback retried jetzt EBUSY/EACCES/EPERM/EEXIST
     mit 200/500/1000ms Backoff. Antivirus, Indexer, OneDrive, offene
     Player-Locks → automatisch geheilt statt permanent geskippt

F) Subtitle/.nfo Companion-Files werden mit-umbenannt UND mit-verschoben
   - Neue Helper renameCompanionFiles + moveCompanionFiles erkennen Subs
     (.srt/.ass/.ssa/.sub/.idx/.vtt/.smi) und Metadaten (.nfo) am Basis-
     Namen-Match. Auch Sprach-Tags wie .de.srt bleiben erhalten
   - Mediaplayer kann Subs nach Library-Move wieder automatisch laden

G) Sample-Token False-Positive entschaerft
   - Dateien die sampleTokenRe matchen bekommen Size-Check: nur als Sample
     behandelt wenn ≤150 MB. Series mit "Sample" im Titel (z.B.
     "Sample.Squad.S01E01.mkv") werden jetzt korrekt umbenannt
   - Sample-Subfolder-Detection bleibt unveraendert (eindeutig)

H) UNC + Casing-only Rename: jetzt via renamePathWithExdevFallback
   - Casing-Rename benutzt jetzt den gleichen Helper, bekommt automatisch
     toWindowsLongPathIfNeeded und Retry-Logik

I) Multi-MKV in selbem Folder: numerischer Suffix statt Skip
   - Wenn Ziel existiert: probiert .2, .3, ... bis .99 bevor aufgegeben.
     A/B-Parts oder alternate-Audio-Files in selbem Folder werden jetzt
     korrekt mit Suffix differenziert statt 2./3. File silent zu droppen

J) Episode-Token Coverage: xX-Format hinzugefuegt
   - Neuer SCENE_EPISODE_X_RE erkennt 1x01, 10x100, etc. (aeltere
     Scene-Releases). Quality-Tokens wie 1080p werden NICHT falsch
     als 1080xX matched (kein zweiter Number-Group)

Tests:
- Symlink-Guard: extractDir==mkvLibraryDir → 0 renamed, File unangetastet
- Companion: .srt/.de.srt/.nfo bei Rename mitbenannt
- Multi-MKV-Collision: 2 Files → suffix .2 statt skip
- Episode-Token: 1x01/10x100 erkannt, 1080p nicht falsch matched

589/589 Tests gruen.
2026-04-22 02:17:11 +02:00
Sucukdeluxe
36ff1c5a86 Cross-Pipe Race-Fix: Rename + MKV-Move teilen jetzt einen per-Package Lock
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.
2026-04-22 01:54:35 +02:00