release: ship v2.0.23 queue and account reliability fixes
Normalize RapidGator aliases, stabilize active queue ordering, preserve user-controlled package expansion, and align compact queue status presentation. Separate Mega-Debrid API and Web credential pools, migrate legacy credentials and disabled states safely, refresh live account availability without restart, honor disabled credential persistence, and invalidate Web sessions without allowing stale in-flight, retry, or queued logins to restore old cookies. Expand regression coverage for account isolation, legacy migration, session races, live scheduler refresh, update notes, and responsive queue behavior.
This commit is contained in:
@@ -21,8 +21,10 @@ import {
|
||||
StartConflictEntry,
|
||||
StartConflictResolutionResult,
|
||||
UiSnapshot, DebridAccountStatus } from "../shared/types";
|
||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||
import { isMegaDebridTransientResolveFailure, germanMegaDebridResolveReason } from "../shared/mega-debrid-errors";
|
||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||
import { extractHosterFromUrl } from "../shared/hoster";
|
||||
import { isMegaDebridTransientResolveFailure, germanMegaDebridResolveReason } from "../shared/mega-debrid-errors";
|
||||
import { getMegaDebridAccountsForMode } from "../shared/mega-debrid-accounts";
|
||||
import {
|
||||
addDebridLinkApiKeyDailyUsageBytes,
|
||||
addDebridLinkApiKeyTotalUsageBytes,
|
||||
@@ -470,15 +472,9 @@ function isArchiveLikePath(filePath: string): boolean {
|
||||
return /\.(?:part\d+\.rar|rar|r\d{2,3}|zip(?:\.\d+)?|z\d{1,3}|7z(?:\.\d+)?)$/i.test(lower);
|
||||
}
|
||||
|
||||
function extractHosterKey(link: string): string {
|
||||
try {
|
||||
const host = new URL(link).hostname.replace(/^www\./, "").toLowerCase();
|
||||
const parts = host.split(".");
|
||||
return parts.length >= 2 ? parts[parts.length - 2] : host;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function extractHosterKey(link: string): string {
|
||||
return extractHosterFromUrl(link);
|
||||
}
|
||||
|
||||
function isLargeBinaryLikePath(filePath: string): boolean {
|
||||
const lower = path.basename(String(filePath || "")).toLowerCase();
|
||||
@@ -2175,7 +2171,26 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
public setSettings(next: AppSettings, opts?: { suppressRetroactiveCleanup?: boolean; settingsOnlyImport?: boolean }): void {
|
||||
const previous = this.settings;
|
||||
const previous = this.settings;
|
||||
const previousMegaPool = (["api", "web"] as const)
|
||||
.flatMap((mode) => getAvailableMegaDebridAccounts(previous, mode).map((account) => `${mode}:${account.id}:${account.password}`))
|
||||
.sort()
|
||||
.join("\n");
|
||||
const nextMegaAccounts = (["api", "web"] as const)
|
||||
.flatMap((mode) => getAvailableMegaDebridAccounts(next, mode));
|
||||
const nextMegaPool = (["api", "web"] as const)
|
||||
.flatMap((mode) => getAvailableMegaDebridAccounts(next, mode).map((account) => `${mode}:${account.id}:${account.password}`))
|
||||
.sort()
|
||||
.join("\n");
|
||||
const previousMegaWebCredentials = getMegaDebridAccountsForMode(previous, "web")
|
||||
.map((account) => `${account.id}:${account.password}`)
|
||||
.sort()
|
||||
.join("\n");
|
||||
const nextMegaWebCredentials = getMegaDebridAccountsForMode(next, "web")
|
||||
.map((account) => `${account.id}:${account.password}`)
|
||||
.sort()
|
||||
.join("\n");
|
||||
const megaPoolChanged = previousMegaPool !== nextMegaPool && nextMegaAccounts.length > 0;
|
||||
next.totalDownloadedAllTime = Math.max(next.totalDownloadedAllTime || 0, this.settings.totalDownloadedAllTime || 0);
|
||||
next.totalCompletedFilesAllTime = Math.max(next.totalCompletedFilesAllTime || 0, this.settings.totalCompletedFilesAllTime || 0);
|
||||
const now = nowMs();
|
||||
@@ -2184,8 +2199,11 @@ export class DownloadManager extends EventEmitter {
|
||||
this.invalidateSettingsSnapshotCache();
|
||||
this.runtimePersistedTotalMs = this.settings.totalRuntimeAllTimeMs || 0;
|
||||
this.runtimePersistedAt = now;
|
||||
this.ensureProviderDailyUsageFresh(nowMs());
|
||||
this.debridService.setSettings(next);
|
||||
this.ensureProviderDailyUsageFresh(nowMs());
|
||||
if (previousMegaWebCredentials !== nextMegaWebCredentials) {
|
||||
this.invalidateMegaSessionFn?.();
|
||||
}
|
||||
this.debridService.setSettings(next);
|
||||
this.allDebridHostInfoCache.clear();
|
||||
|
||||
const prevOrder = JSON.stringify(previous.providerOrder ?? []);
|
||||
@@ -2218,7 +2236,11 @@ export class DownloadManager extends EventEmitter {
|
||||
{ prev: previous.debridLinkApiKeys || "", next: next.debridLinkApiKeys || "", providers: ["debridlink"] },
|
||||
{ prev: previous.linkSnappyLogin + "|" + previous.linkSnappyPassword, next: next.linkSnappyLogin + "|" + next.linkSnappyPassword, providers: ["linksnappy"] },
|
||||
{ prev: previous.ddownloadLogin + "|" + previous.ddownloadPassword, next: next.ddownloadLogin + "|" + next.ddownloadPassword, providers: ["ddownload"] },
|
||||
{ prev: previous.megaCredentials + "|" + previous.megaPassword, next: next.megaCredentials + "|" + next.megaPassword, providers: ["megadebrid", "megadebrid-api", "megadebrid-web"] }
|
||||
{
|
||||
prev: `${previous.megaDebridApiCredentials}|${previous.megaDebridWebCredentials}|${previous.megaDebridApiEnabled}|${previous.megaDebridWebEnabled}`,
|
||||
next: `${next.megaDebridApiCredentials}|${next.megaDebridWebCredentials}|${next.megaDebridApiEnabled}|${next.megaDebridWebEnabled}`,
|
||||
providers: ["megadebrid", "megadebrid-api", "megadebrid-web"]
|
||||
}
|
||||
];
|
||||
let clearedProviderFailures = 0;
|
||||
for (const change of credChanges) {
|
||||
@@ -2232,9 +2254,13 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clearedProviderFailures > 0) {
|
||||
logger.info(`Settings-Update: ${clearedProviderFailures} Provider-Failure(s) gecleart wegen geaenderter Credentials`);
|
||||
}
|
||||
if (clearedProviderFailures > 0) {
|
||||
logger.info(`Settings-Update: ${clearedProviderFailures} Provider-Failure(s) gecleart wegen geaenderter Credentials`);
|
||||
}
|
||||
|
||||
if (!opts?.settingsOnlyImport && megaPoolChanged) {
|
||||
this.releaseMegaDebridResetParks();
|
||||
}
|
||||
|
||||
if (!opts?.settingsOnlyImport) {
|
||||
this.resolveExistingQueuedOpaqueFilenames();
|
||||
@@ -2243,8 +2269,43 @@ export class DownloadManager extends EventEmitter {
|
||||
this.applyRetroactiveCleanupPolicy();
|
||||
}
|
||||
}
|
||||
this.emitState();
|
||||
}
|
||||
this.emitState();
|
||||
}
|
||||
|
||||
private releaseMegaDebridResetParks(): number {
|
||||
const activeItemIds = new Set([...this.activeTasks.values()].map((task) => task.itemId));
|
||||
const affectedPackageIds = new Set<string>();
|
||||
let released = 0;
|
||||
for (const item of Object.values(this.session.items)) {
|
||||
if (activeItemIds.has(item.id)) continue;
|
||||
if (item.status !== "queued" && item.status !== "reconnect_wait") continue;
|
||||
if (!/Mega-Debrid bis Tagesreset gesperrt/i.test(item.fullStatus || "")) continue;
|
||||
this.retryAfterByItem.delete(item.id);
|
||||
this.retryStateByItem.delete(item.id);
|
||||
item.status = "queued";
|
||||
item.fullStatus = "Wartet";
|
||||
item.lastError = "";
|
||||
item.provider = null;
|
||||
item.providerLabel = undefined;
|
||||
item.providerAccountId = undefined;
|
||||
item.providerAccountLabel = undefined;
|
||||
item.updatedAt = nowMs();
|
||||
affectedPackageIds.add(item.packageId);
|
||||
released += 1;
|
||||
}
|
||||
for (const packageId of affectedPackageIds) {
|
||||
const pkg = this.session.packages[packageId];
|
||||
if (pkg) this.refreshPackageStatus(pkg);
|
||||
}
|
||||
if (released > 0) {
|
||||
logger.info(`Settings-Update: ${released} Mega-Debrid-Tagesreset-Warteitem(s) wegen geaendertem Account-Pool freigegeben`);
|
||||
this.persistSoon();
|
||||
if (this.session.running) {
|
||||
void this.ensureScheduler().catch((error) => logger.error(`Scheduler nach Account-Update fehlgeschlagen: ${compactErrorText(error)}`));
|
||||
}
|
||||
}
|
||||
return released;
|
||||
}
|
||||
|
||||
public getSettings(): AppSettings {
|
||||
return this.settings;
|
||||
@@ -8019,12 +8080,12 @@ export class DownloadManager extends EventEmitter {
|
||||
if (effectiveProvider === "realdebrid") {
|
||||
return Boolean(this.settings.realDebridUseWebLogin || this.settings.token.trim());
|
||||
}
|
||||
if (effectiveProvider === "megadebrid-api") {
|
||||
const hasMegaCreds = Boolean(this.settings.megaCredentials.trim() || (this.settings.megaLogin.trim() && this.settings.megaPassword.trim()));
|
||||
return Boolean(hasMegaCreds && (resolveMegaDebridProvider(this.settings, "megadebrid") === "megadebrid-api" || this.settings.megaDebridApiEnabled));
|
||||
}
|
||||
if (effectiveProvider === "megadebrid-web") {
|
||||
const hasMegaCreds = Boolean(this.settings.megaCredentials.trim() || (this.settings.megaLogin.trim() && this.settings.megaPassword.trim()));
|
||||
if (effectiveProvider === "megadebrid-api") {
|
||||
const hasMegaCreds = getMegaDebridAccountsForMode(this.settings, "api").length > 0;
|
||||
return Boolean(hasMegaCreds && (resolveMegaDebridProvider(this.settings, "megadebrid") === "megadebrid-api" || this.settings.megaDebridApiEnabled));
|
||||
}
|
||||
if (effectiveProvider === "megadebrid-web") {
|
||||
const hasMegaCreds = getMegaDebridAccountsForMode(this.settings, "web").length > 0;
|
||||
return Boolean(hasMegaCreds && (resolveMegaDebridProvider(this.settings, "megadebrid") === "megadebrid-web" || this.settings.megaDebridWebEnabled));
|
||||
}
|
||||
if (effectiveProvider === "bestdebrid") {
|
||||
@@ -8209,7 +8270,7 @@ export class DownloadManager extends EventEmitter {
|
||||
private getSerializedValidatingLimit(provider: DebridProvider | null): number {
|
||||
if (provider === "megadebrid-web" || provider === "megadebrid-api") {
|
||||
const mode = provider === "megadebrid-web" ? "web" : "api";
|
||||
const usableAccounts = getAvailableMegaDebridAccounts(this.settings)
|
||||
const usableAccounts = getAvailableMegaDebridAccounts(this.settings, mode)
|
||||
.filter((account) => !getMegaDebridAccountCooldownState(`${account.id}:${mode}`))
|
||||
.length;
|
||||
return Math.max(1, usableAccounts);
|
||||
@@ -8793,9 +8854,11 @@ export class DownloadManager extends EventEmitter {
|
||||
genericRetries: Number(active.genericErrorRetries || 0),
|
||||
freshRetryUsed: Boolean(active.freshRetryUsed),
|
||||
resumeHardResetUsed: Boolean(active.resumeHardResetUsed)
|
||||
});
|
||||
this.retryAfterByItem.set(item.id, nowMs() + waitMs);
|
||||
}
|
||||
});
|
||||
this.retryAfterByItem.set(item.id, nowMs() + waitMs);
|
||||
const pkg = this.session.packages[item.packageId];
|
||||
if (pkg) this.refreshPackageStatus(pkg);
|
||||
}
|
||||
|
||||
private scheduleHttp416Retry(
|
||||
item: DownloadItem,
|
||||
|
||||
Reference in New Issue
Block a user