When a Mega-Web account's unrestrict aborts because the shared unrestrict
timeout fired while it was running, give that account a 2-min cooldown
(only if it actually ran >=8s, so a quick user-cancel does not cool it
down). The download-manager retry then skips the cooled-down account and
rotates to the next one, instead of hammering the same account every 60s.
- debrid.ts: handle the abort in the rotation catch before classifyAccountFailure
- rotation log event TIMEOUT_COOLDOWN (+ renderer label) replaces the misleading
red "fataler Fehler" for this case
- RD_MEGA_ABORT_MIN_RUN_MS env override for the run-length threshold
- 2 regression tests (cooldown set -> next call rotates; quick abort -> no cooldown)
- New video-processor.ts: ffmpeg/ffprobe remux that keeps only the German
audio track (by language tag, with safe fallbacks) and strips the ".DL."
marker from the filename
- Runs after extraction in both the deferred and hybrid post-process paths,
inside the per-package file-op chain; abortable, disk-space checked,
mtime-preserving, atomic temp->replace so the original is never lost
- System ffmpeg via PATH / RD_FFMPEG_BIN; toggle + track-mode select in settings
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.
User-Entscheidung: ein Mega-Debrid-Account am Tageslimit soll bis zum Programm-Neustart
uebersprungen werden, nicht alle 20s/2min neu getestet.
Ground Truth (Support-Bundle gegrept): der limitierte Account liefert im Web-Pfad NIE eine
unterscheidbare Meldung — "Kein Server" = 0 Treffer, "Antwort leer" = 20.861. Tageslimit und
transienter Blip sind auf Message-Ebene nicht trennbar (generate() findet ohne processDebrid-
Code keinen Code -> return null -> "Antwort leer"). Ein Trigger auf "Kein Server" waere toter Code.
Loesung (Verhaltens-Signal statt Wortlaut):
- megaDebridEmptyResponseStreaks zaehlt aufeinanderfolgende "Antwort leer"/"Kein Server"-
Treffer je Account; ab 3 wird der Account bis Neustart geparkt (until=MAX_SAFE_INTEGER,
nur In-Memory -> Neustart loescht). Erfolg/anderer Fehler setzt zurueck.
- classifyAccountFailure markiert beide Signale als limitSignal (Symmetrie: ein einzelner
evtl. transienter Treffer parkt NICHT, behaelt kurzen Cooldown).
- Skip-Branch: "uebersprungen (bis Neustart gesperrt)", traegt nicht zu earliestCooldownUntil
bei (kein absurder Retry-Timer); Post-Loop wirft klare Endmeldung wenn alle geparkt.
- generate() surfacet "Kein Server" zusaetzlich als Page-Error (falls es doch im HTML steht).
- UI: Rotations-Verlauf zeigt "bis Neustart gesperrt".
Verifiziert: tsc 9 (Baseline), 655 Tests + 5 neue (inkl. Wiring-E2E der eine echte leere
Antwort durch unrestrictWithAccounts->classify->catch->Park treibt), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beim "Hinzufuegen" eines Mega-Debrid-Accounts im Bearbeiten-Dialog wird der Account
jetzt sofort einzeln geprueft (connectUser) — Login-Gueltigkeit + Premium-Restlaufzeit
erscheinen direkt als Badge, ohne den Tab schliessen und "Alle pruefen" klicken zu
muessen. Waehrend der Pruefung zeigt das Badge "Pruefe…".
Neue Einzel-Check-IPC (Spiegel von checkDebridAccounts): CHECK_MEGA_DEBRID_ACCOUNT
-> app-controller.checkSingleMegaDebridAccount(login, password) baut den Account-Entry
(id via getMegaDebridAccountId), ruft die bestehende checkMegaDebridAccount() und merged
das Ergebnis via applyDebridAccountStatuses -> Snapshot -> Badge (gleicher Pfad wie
"Alle pruefen"). Funktioniert fuer den noch nicht gespeicherten Draft-Account, weil
applyDebridAccountStatuses merged (kein Pruning) + emitState.
Kern-Check-Logik unveraendert + weiterhin durch account-check.test.ts gedeckt.
643 Tests gruen, tsc 9 (unveraendert), Build sauber. GUI compile-/build-verifiziert,
im laufenden Electron noch nicht click-getestet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend war bereits vorhanden (megaDebridDisabledAccountIds + Rotation-Skip +
Storage-Normalisierung); es fehlte nur das UI. Spiegelt das Debrid-Link-Muster:
im Account-Bearbeiten-Dialog bekommt jeder Mega-Account einen Aktivieren/
Deaktivieren-Toggle (+ "Deaktiviert"-Badge). Der Disabled-Zustand wird im Dialog-
Draft gehalten (megaDisabledIds) und beim Speichern via applyAccountDialogToSettings
in megaDebridDisabledAccountIds uebernommen (gefiltert auf vorhandene Accounts).
Kein Live-Persist mitten im Dialog -> kohaerent mit dem draft-then-Save-Modell.
Wirkt OHNE Neustart: DebridService.unrestrictLink liest this.settings live
(setSettings propagiert die Liste), unrestrictWithAccounts ueberspringt deaktivierte
Accounts (gleicher Mechanismus wie Daily-Limit/Cooldown-Skip).
Test: "skips a manually disabled Mega-Debrid account" — acc1 disabled -> acc2 loest
auf (beweist den ID-Seam getMegaDebridAccountId). 643 Tests gruen, tsc 9, Build sauber.
GUI-Toggle compile-/build-verifiziert, im laufenden Electron noch nicht click-getestet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements per-item / per-package hash-based diffing for the IPC state-update
channel. This is the architecturally biggest performance win — for queues
with thousands of items where most are idle between emits, this can cut
IPC payload size by 80-95%.
How it works:
1. New `getSnapshotForEmit()` method computes a compact hash per item and
per package covering the visible/mutable fields. On each emit it includes
only items/packages whose hash changed since the last emit, plus a list
of removed IDs. Every 30 seconds a full resync is sent for safety.
2. A new `payloadKind: "full" | "delta"` field on UiSnapshot signals the
format. `removedItemIds` and `removedPackageIds` lists carry deletions.
3. The renderer maintains a `masterSnapshotRef` and merges incoming deltas:
spreads new items over master items, deletes the removed-IDs, then sets
the merged snapshot as React state. Full payloads replace the master
entirely (initial sync + 30s resync).
4. The existing direct `getSnapshot()` API used by app-controller, debug-server,
and link-export is unchanged — they still get a full snapshot. Only the
"state" emit channel uses delta encoding.
Trade-offs accepted:
- Hash computation cost: ~13 string concats per item per emit. With 5000
items at 700ms intervals that's ~7100 hash ops/sec — well under 1ms total.
- The 30s full resync ensures any drift bug self-heals within 30s without
user-visible glitch.
- Server keeps two extra Maps (item/package hash tracking).
Items / packages that are completely idle between emits add ZERO bytes to
the IPC payload now, instead of ~450 bytes per item. For a normal queue of
5000 items where ~30 are actively downloading, payload drops from ~3.6 MB
to ~30 KB per emit — a 100x reduction.
Tests: 140/140 download-manager + 133/133 storage+auto-rename green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Major optimizations to reduce UI lag with large queues (5000+ items):
1. ItemRow extracted to its own memoized component (renderer)
Previously every package re-render mapped all its items inline,
producing N×M re-renders per state update. Now each item-row only
re-renders when ITS specific data changes, with custom equality on
the visible fields (status, progress, speed, fullStatus, etc.).
Also adds stable useCallback handlers per item.
2. PackageCard stats consolidated into single useMemo (renderer)
Replaces 5 separate filter()/some() + 2 reduce() calls (O(7N)) with
one O(N) pass collecting all aggregates (done/failed/cancelled/
extracted/extracting/activeProgress/extractingProgress).
3. selectedIds memo comparator fixed (renderer)
Custom equality now checks if selection state changed for items in
THIS package only. Previously any selection anywhere broke memo on
all 200+ visible PackageCards.
4. Scheduler single-pass queue presence (main)
New getQueuePresence() returns hasImmediate + hasDelayed in one
iteration. Replaces hasQueuedItems() + hasDelayedQueuedItems() that
each scanned packages independently. Saves one full O(n) iteration
per scheduler tick.
No functional changes. All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three low-risk optimizations that reduce CPU/memory footprint without
changing user-visible behavior:
1. Periodic cleanup of unbounded module-level Maps (24/7 stability):
- debridLinkKeyCooldowns, debridLinkKeyCooldownDetails,
debridLinkKeyRuntimeStatuses (debrid.ts)
- megaDebridAccountCooldowns (debrid.ts)
- allDebridHostInfoCache (download-manager.ts)
- All pruned every 10 min via existing resetStaleRetryState() with a
1h grace window for debugging
- Without this, modules accumulated entries indefinitely over days
of continuous server operation
2. Hoist regex literals in resolveArchiveItemsFromList() to module scope.
Avoids 5 RegExp constructions per call. The function is called per
archive set during extraction discovery — adds up on packages with
many archives.
3. BandwidthChart: skip the 250ms redraw interval while the session is
stopped or paused. The chart renders once on state change to show the
latest history, then sleeps until downloads resume. Saves 4 canvas
redraws per second when idle (renderer CPU).
No functional changes. All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Replace the raw login:password textarea with a proper form-based UI:
- Individual Login + Password input fields with "Hinzufügen" button
- List of configured accounts with masked logins and "Entfernen" button
- Duplicate login detection
- Storage format unchanged (megaCredentials stays login:password pairs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple Mega-Debrid accounts can now be configured as login:password
pairs (one per line). When an account hits Fair-Use limits or errors,
the next account is tried automatically.
- New parser module mega-debrid-accounts.ts (parse, ID generation,
masking, serialization)
- Per-account daily limits, usage tracking, enable/disable
- Account rotation with per-mode cooldowns (API failures don't
block Web attempts)
- Backward compatible: existing single megaLogin/megaPassword
is auto-migrated to the new format
- UI: textarea for credentials, account list with masked logins
Follows the existing Debrid-Link multi-key pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Items incorrectly marked as "completed" by the old 50% recovery threshold
persist in the session file across updates. On startup, check all completed
items: if the file on disk is smaller than expected totalBytes, reset to
"queued" so it gets re-downloaded. Also reset items whose files are missing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backup redesign (JDownloader 2 style):
- AES-256-GCM encrypted .mdd format with fixed app key
- All credentials exported (no more ***-masking), works on any PC
- Includes settings, session AND history in backup
- Backward-compatible: auto-detects legacy JSON backups
- Normalize history entries on import
- Added debridLinkApiKeys, linkSnappy credentials to sensitive keys
Hide extracted items:
- New setting: hide completed/extracted items from package item list
- Items still count in progress stats (done/total), only hidden in UI
- Default: enabled
- Toggle in settings: "Entpackte Items in Paketliste ausblenden"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multiple accounts from the same provider are now merged:
"Debrid-Link (#3), Debrid-Link (#4)" becomes "Debrid-Link (#3 + #4)"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Daily traffic limits:
- Per-provider daily download limit (configurable in GB per provider)
- Per Debrid-Link API key daily limit (individual limits per key)
- Usage tracking with automatic daily reset at midnight
- Provider is skipped when daily limit reached, falls back to next provider
- Reset button per provider and per Debrid-Link key in account settings
- Hoster routing skips daily-limited providers gracefully
Debrid-Link multi-key improvements:
- Keys now display with labels (#1, #2...) and masked tokens in account list
- Option to show detailed per-key view with individual usage stats
- Keys that hit their daily limit are automatically skipped
- providerAccountId/providerAccountLabel stored per download item
Auto-sort packages by progress:
- Active packages automatically sorted to top during downloads
- Sorted by completion ratio, then downloaded bytes
- Toggle in settings (autoSortPackagesByProgress)
UI polish:
- Package column headers: flatter, more transparent design
- LinkSnappy mode label: "Login" renamed to "Web"
- Account list: new toggle for detailed Debrid-Link key display
- Account usage stats section with warning styling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All ? placeholders in App.tsx replaced with correct UTF-8 umlauts
(ä, ö, ü, ß). File is now properly encoded as UTF-8.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>