Commit Graph

309 Commits

Author SHA1 Message Date
Sucukdeluxe
c417ebb57f Auto-Rename Race-Fix: parallele Scans pro Package serialisieren
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.
2026-04-22 01:38:26 +02:00
Sucukdeluxe
834da04b45 Auto-Rename Folder-Override: nur bei OBFUSKIERTEM Source-Filename
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.
2026-04-22 01:29:32 +02:00
Sucukdeluxe
19c31caab5 Auto-Rename: zwei Mismatch-Bugs gefixt (obfuskierter File-Token + weak Folder)
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.
2026-04-22 00:45:49 +02:00
Sucukdeluxe
75036edbd1 DLC-Import Hang gefixt: kein sync-FS Log-I/O mehr pro Link
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.
2026-04-21 21:36:19 +02:00
Sucukdeluxe
25aa48fe99 Account-rotation logging + transient cooldown fixes
- 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
2026-04-19 23:03:22 +02:00
Sucukdeluxe
cdec0029fe Debrid-Link stability: stop double-blocking, shorter transport cooldown, max-wins
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>
2026-04-19 22:11:35 +02:00
Sucukdeluxe
c3590f08fc Fix stale-state when account credentials change at runtime
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>
2026-04-19 21:59:28 +02:00
Sucukdeluxe
4d1f3c3fdc Performance: hash-based IPC state diffing (the big one)
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>
2026-04-19 14:10:48 +02:00
Sucukdeluxe
67fe689e28 Performance: cache cloneSettings() between snapshots (400ms TTL)
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>
2026-04-19 13:57:15 +02:00
Sucukdeluxe
d71dd3af0b Performance: visiblePackages dep fix + ItemRow date memo + small Object.keys cuts
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>
2026-04-19 13:46:57 +02:00
Sucukdeluxe
5b4ad99923 Performance: cloneSession shallow refs + scheduler 1-pass + speed obj alloc
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>
2026-04-19 13:30:42 +02:00
Sucukdeluxe
3c9894c7b0 Performance: ItemRow extraction + scheduler single-pass + selectedIds memo fix
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>
2026-04-19 13:07:42 +02:00
Sucukdeluxe
bece2f3e85 Performance: prune long-lived caches, hoist regexes, idle chart redraws
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>
2026-04-19 12:32:38 +02:00
Sucukdeluxe
6e936cd5bc Bonus dir detection: normalize separators (Making.Of, Behind.The.Scenes)
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>
2026-04-14 15:03:02 +02:00
Sucukdeluxe
1dfb486145 Auto-rename safety net: never strip valid SxxExx episode token
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>
2026-04-14 11:45:41 +02:00
Sucukdeluxe
6713771144 Skip bonus/extras content in MKV collection and auto-rename
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>
2026-04-14 11:28:44 +02:00
Sucukdeluxe
49efebd001 Extend hybrid companion stem matching to all archive types
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>
2026-04-04 21:28:06 +02:00
Sucukdeluxe
711147fc10 Fix companion files stuck at extraction labels in hybrid mode
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>
2026-04-04 20:41:38 +02:00
Sucukdeluxe
021401e3b6 Mark companion metadata files (.sfv) as extracted during hybrid extraction
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>
2026-04-04 20:21:16 +02:00
Sucukdeluxe
9d611bd749 Accept small metadata files (.sfv, .nfo, .nzb) without retry loops
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>
2026-04-04 20:07:26 +02:00
Sucukdeluxe
650dafb535 Fix support bundle export freeze and resume prealloc recovery 2026-03-29 03:25:58 +02:00
Sucukdeluxe
653e756010 Harden download integrity, extraction safety, and update security 2026-03-28 16:27:21 +01:00
Sucukdeluxe
a1d72b6dbc Fix resume tail corruption after terminated streams 2026-03-28 02:30:30 +01:00
Sucukdeluxe
5aeab9ecad Prevent queue loss during app updates
- 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>
2026-03-26 19:34:48 +01:00
Sucukdeluxe
52bafed0b2 Add archive deobfuscation for hoster-mangled filenames
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>
2026-03-26 13:28:40 +01:00
Sucukdeluxe
38179881f5 Fix Debrid-Link key rotation cascade failure, case-sensitive rename, and sample filter
- 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>
2026-03-26 13:04:42 +01:00
Sucukdeluxe
45310f0bf7 Fix daily-log freezes and revert unrelated queue scope change
- Rewrite daily-log from synchronous fs.writeSync to async buffered
  writes (500ms flush interval), matching the main logger's pattern.
  The sync writes blocked the event loop on every log line.
- Revert the stop() queue scope change from v1.7.114 which was
  intended for a different project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 10:03:35 +01:00
Sucukdeluxe
c215fdd658 Preserve selected-only run scope across stop/start cycles
When startItems() was used with a subset of items (e.g. 2000 of 6020),
stopping and restarting would pick up ALL 6020 queued items instead of
just the original 2000. Now stop() marks items outside the run set as
"Gestoppt" so they are not automatically included in the next start().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:29:01 +01:00
Sucukdeluxe
180397f10a Revert post-MKV-move auto-rename that corrupted other packages
The post-MKV-move rename pass added in v1.7.107 ran on the shared
mkvLibraryDir (Entpackt/), causing files from OTHER packages to be
renamed to the current package's name. For example, Orange.Is.The.New.Black
files were renamed to Ted.S02E13...SAUERKRAUT.mkv.

Remove the post-MKV-move rename entirely. The original hybrid race
condition (1 file per season not renamed) is far less damaging than
cross-package corruption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:58:07 +01:00
Sucukdeluxe
d91621bd6d Handle SxxSxx scene typo in episode token extraction
Scene releases occasionally use SxxSxx (e.g. s05s01) instead of
SxxExx — the second S is a typo for E. Add a fallback regex to
detect this pattern and correctly interpret it as S05E01.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 18:03:45 +01:00
Sucukdeluxe
79c178eb0d Skip sample files during auto-rename to prevent (2) MKV duplicates
Sample files like wayne-sample.mkv were renamed by auto-rename which
stripped the -sample suffix. After rename they were indistinguishable
from the main MKV, causing MKV collection to create (2) copies
(e.g. Messiah.Superstar.S01E01...WAYNE (2).mkv at 17 MB alongside
the real 470 MB episode).

Auto-rename now skips files with a "sample" token in their name,
matching the same detection used by MKV collection's sample filter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:06:41 +01:00
Sucukdeluxe
87c097c822 Fix auto-rename race in hybrid extraction missing MKV files
During hybrid extraction, files can finish extracting between the
auto-rename scan and MKV-move, causing them to be moved to the MKV
library dir with their original scene names (e.g. awa-diethundermans03e21hd.mkv).

Add a post-MKV-move auto-rename pass on the MKV library directory to
catch these stragglers and rename them to the proper folder-based name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:53:51 +01:00
Sucukdeluxe
7b764be769 Fix auto-rename episode range folders producing duplicate MKV names
Folder names with episode ranges like S01E01-E08 (common in season
packs from debrid servers) were returned unchanged as target name,
causing all episodes to get the same filename and producing (2)(3)(4)
suffixes during MKV collection.

- Detect episode ranges (S01E01-E08, S01E01-08) in folder names and
  replace them with the source file's specific episode token
- Extend applyEpisodeTokenToFolderName regex to match and replace
  full episode ranges instead of only single episode tokens

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:25:10 +01:00
Sucukdeluxe
78fef627bb Fix auto-rename episode pollution and deferred nested extraction abort
- Fix episode-token pollution: packageExtraCandidates included ALL item
  filenames, causing resolveEpisodeTokenForAutoRename to pick up episode
  tokens from unrelated files (e.g. S01E07 from 4sf-...-s01e07 applied
  to all hrs-...-101/102/103 files). This also caused (2)(3) MKV
  suffixes when multiple files were renamed to the same wrong episode.
  Now only the package name (outputDir) is used as extra candidate.
- Fix deferred nested extraction missing abort signal: the nested
  extractPackageArchives call in runDeferredPostExtraction did not
  receive deferredController.signal, making it unabortable on
  stop/cancel/restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:20:47 +01:00
Sucukdeluxe
30a5832498 Fix auto-rename mixed scene group suffixes 2026-03-11 20:05:12 +01:00
Sucukdeluxe
99455eca94 Fix auto-rename raw episode folder selection 2026-03-11 14:17:51 +01:00
Sucukdeluxe
d9170f4167 Refactor: Extractor in 18 Sektionen reorganisiert 2026-03-10 23:47:02 +01:00
Sucukdeluxe
1a0f49b29c Rebuild download completion verification 2026-03-10 20:22:19 +01:00
Sucukdeluxe
83640b8f1f Honor configured parallel extraction slots 2026-03-10 20:08:43 +01:00
Sucukdeluxe
fbae8a1496 Fix cleanup after partial extraction failures 2026-03-10 19:57:26 +01:00
Sucukdeluxe
a054eface5 Improve extraction failure diagnostics 2026-03-10 19:14:21 +01:00
Sucukdeluxe
722fe071cc Harden Debrid-Link completion recovery 2026-03-10 18:27:26 +01:00
Sucukdeluxe
c1a4d8037f Fix Debrid-Link retry recovery 2026-03-10 18:20:19 +01:00
Sucukdeluxe
0a724aed71 Fix BSOD MEMORY_MANAGEMENT on low-RAM servers
- Dynamically compute JVM -Xmx based on system RAM instead of hardcoded 32g
- Reduce peak memory during session loading (inline JSON string, skip redundant clone)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 14:19:49 +01:00
Sucukdeluxe
6c7b1bb088 Keep failed packages out of package cleanup 2026-03-10 12:30:43 +01:00
Sucukdeluxe
17e947fc6b Harden type safety and recovery guards 2026-03-10 05:54:19 +01:00
Sucukdeluxe
17604910b5 Harden startup recovery and stats edge cases 2026-03-10 01:50:16 +01:00
Sucukdeluxe
6d7b3686dc Add AVI video-library support and startup recovery fixes 2026-03-10 00:43:51 +01:00
Sucukdeluxe
b41bb0aeb5 Fix deferred cleanup after MKV move 2026-03-09 23:48:28 +01:00
Sucukdeluxe
ecb5df0a31 Fix startup duplicate archive recovery 2026-03-09 20:38:23 +01:00