From 09ea7803f33ef70852659bdda64406cead61b8a8 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 19 Aug 2026 19:09:07 +0200 Subject: [PATCH] fix(accounts): restore Real-Debrid start availability Keep concrete account pools authoritative when fully disabled and clear stale transient cooldown state when an account is explicitly re-enabled, while preserving configured daily limits and usage. --- CHANGELOG.md | 8 +++++ package-lock.json | 4 +-- package.json | 2 +- src/main/debrid.ts | 9 ++++++ src/main/download-manager.ts | 21 +++++++++++-- tests/download-manager.test.ts | 55 +++++++++++++++++++++++++++++++++- 6 files changed, 92 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d586ef3..1b26d35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to Multi-Debrid Downloader are documented in this file. ## [Unreleased] +## [2.0.45] - 2026-08-19 + +### Account availability + +- Kept a configured Real-Debrid account pool authoritative when every account is disabled instead of reviving legacy browser-login fields. +- Released stale in-memory Real-Debrid cooldown and sticky state when an account is explicitly re-enabled so the Start action becomes available without restarting the application. +- Preserved configured per-account daily limits and usage while refreshing only transient runtime state. + ## [2.0.44] - 2026-08-17 ### Live updates diff --git a/package-lock.json b/package-lock.json index a2154de..d9d67a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-debrid-downloader", - "version": "2.0.44", + "version": "2.0.45", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-debrid-downloader", - "version": "2.0.44", + "version": "2.0.45", "license": "MIT", "dependencies": { "adm-zip": "0.6.0", diff --git a/package.json b/package.json index 81388d8..753c0bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-debrid-downloader", - "version": "2.0.44", + "version": "2.0.45", "description": "Desktop downloader", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/debrid.ts b/src/main/debrid.ts index e28ab08..ed6e452 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -714,6 +714,15 @@ export function pruneRealDebridRuntimeStateForAccounts(activeAccountIds: Set [account.id, account])); + const previousRealDebridProviderDisabled = (previous.disabledProviders || []).includes("realdebrid"); + const nextRealDebridProviderDisabled = (next.disabledProviders || []).includes("realdebrid"); + const reenabledRealDebridAccountIds = getRealDebridAccounts(next) + .filter((account) => account.enabled && ( + previousRealDebridAccounts.get(account.id)?.enabled === false + || (previousRealDebridProviderDisabled && !nextRealDebridProviderDisabled) + )) + .map((account) => account.id); const previousMegaPool = (["api", "web"] as const) .flatMap((mode) => getAvailableMegaDebridAccounts(previous, mode).map((account) => `${mode}:${account.id}:${account.password}`)) .sort() @@ -2300,7 +2309,12 @@ export class DownloadManager extends EventEmitter { this.invalidateMegaSessionFn?.(); } this.debridService.setSettings(next); - this.allDebridHostInfoCache.clear(); + for (const accountId of reenabledRealDebridAccountIds) { + if (releaseRealDebridAccountCooldown(accountId)) { + logger.info(`Settings-Update: Real-Debrid-Cooldown für reaktivierten Account freigegeben (${accountId})`); + } + } + this.allDebridHostInfoCache.clear(); const prevOrder = JSON.stringify(previous.providerOrder ?? []); const nextOrder = JSON.stringify(next.providerOrder ?? []); @@ -8260,7 +8274,8 @@ export class DownloadManager extends EventEmitter { return false; } if (effectiveProvider === "realdebrid") { - return getRealDebridAccounts(this.settings).some((account) => account.enabled) + const configuredAccounts = getRealDebridAccounts(this.settings); + return configuredAccounts.length > 0 ? getAvailableRealDebridAccounts(this.settings).length > 0 : Boolean(this.settings.realDebridUseWebLogin || this.settings.token.trim()); } diff --git a/tests/download-manager.test.ts b/tests/download-manager.test.ts index 9557c71..b71e5d8 100644 --- a/tests/download-manager.test.ts +++ b/tests/download-manager.test.ts @@ -21,7 +21,7 @@ import { serializeRealDebridApiAccounts } from "../src/shared/real-debrid-accoun import { getRenameLogPath, initRenameLog, shutdownRenameLog } from "../src/main/rename-log"; import { UnrestrictedLink } from "../src/main/realdebrid"; import { resetVideoToolingCache } from "../src/main/video-processor"; -import type { HistoryEntry, PackageEntry } from "../src/shared/types"; +import type { AppSettings, HistoryEntry, PackageEntry } from "../src/shared/types"; const tempDirs: string[] = []; const originalFetch = globalThis.fetch; @@ -636,6 +636,59 @@ describe("download start account gate", () => { expect(disabledMegaManager.getSnapshot().canStart).toBe(false); }); + it("does not revive the legacy Real-Debrid login when every concrete pool account is disabled", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-disabled-pool-gate-")); + tempDirs.push(root); + const firstId = "rdw_first"; + const secondId = "rdw_second"; + const manager = new DownloadManager( + { + ...defaultSettings(), + realDebridUseWebLogin: true, + realDebridWebAccountIds: [firstId, secondId], + realDebridDisabledAccountIds: [firstId, secondId], + providerOrder: ["realdebrid"] + }, + emptySession(), + createStoragePaths(path.join(root, "state")) + ); + + expect(manager.getSnapshot().canStart).toBe(false); + await expect(manager.start()).rejects.toThrow("Kein aktiver Download-Account verfügbar"); + }); + + it("makes an explicitly re-enabled Real-Debrid account immediately usable after an old cooldown", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-reenabled-cooldown-gate-")); + tempDirs.push(root); + const accountId = "rdw_reenabled"; + const disabledSettings: AppSettings = { + ...defaultSettings(), + realDebridUseWebLogin: true, + realDebridWebAccountIds: [accountId], + realDebridDisabledAccountIds: [accountId], + disabledProviders: ["realdebrid"], + providerOrder: ["realdebrid"] + }; + const manager = new DownloadManager( + disabledSettings, + emptySession(), + createStoragePaths(path.join(root, "state")) + ); + primeRealDebridRuntimeCooldownForTests(accountId, 60_000, "Vorheriger Accountfehler"); + + expect(manager.getSnapshot().canStart).toBe(false); + expect(getProviderRuntimeSnapshot().realDebrid.accounts.find((entry) => entry.accountId === accountId)?.cooldown).not.toBeNull(); + + manager.setSettings({ + ...disabledSettings, + realDebridDisabledAccountIds: [], + disabledProviders: [] + }); + + expect(manager.getSnapshot().canStart).toBe(true); + expect(getProviderRuntimeSnapshot().realDebrid.accounts.find((entry) => entry.accountId === accountId)?.cooldown ?? null).toBeNull(); + }); + it("allows start when an active account is available", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-active-account-gate-")); tempDirs.push(root);