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.
This commit is contained in:
parent
6d4da02f92
commit
876483da51
@ -52,7 +52,7 @@ function releaseTlsSkip(): void {
|
|||||||
}
|
}
|
||||||
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
|
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
|
||||||
import { planDownloadCompletion, validateDownloadedFileCompletion } from "./download-completion";
|
import { planDownloadCompletion, validateDownloadedFileCompletion } from "./download-completion";
|
||||||
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo, getAvailableDebridLinkApiKeys, getAvailableMegaDebridAccounts, pruneExpiredDebridLinkRuntimeState, pruneExpiredMegaDebridRuntimeState } from "./debrid";
|
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo, getAvailableDebridLinkApiKeys, getAvailableMegaDebridAccounts, getMegaDebridAccountCooldownState, pruneExpiredDebridLinkRuntimeState, pruneExpiredMegaDebridRuntimeState } from "./debrid";
|
||||||
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, detectArchiveSignature, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree, resetExtractorCachesForPasswordChange, type ExtractArchiveFailureInfo } from "./extractor";
|
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, detectArchiveSignature, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree, resetExtractorCachesForPasswordChange, type ExtractArchiveFailureInfo } from "./extractor";
|
||||||
import { validateFileAgainstManifest } from "./integrity";
|
import { validateFileAgainstManifest } from "./integrity";
|
||||||
import { classifyDiskError } from "./fs-error";
|
import { classifyDiskError } from "./fs-error";
|
||||||
@ -7981,7 +7981,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
private getSerializedValidatingLimit(provider: DebridProvider | null): number {
|
private getSerializedValidatingLimit(provider: DebridProvider | null): number {
|
||||||
if (provider === "megadebrid-web") {
|
if (provider === "megadebrid-web") {
|
||||||
return Math.max(1, getAvailableMegaDebridAccounts(this.settings).length);
|
const usableAccounts = getAvailableMegaDebridAccounts(this.settings)
|
||||||
|
.filter((account) => !getMegaDebridAccountCooldownState(`${account.id}:web`))
|
||||||
|
.length;
|
||||||
|
return Math.max(1, usableAccounts);
|
||||||
}
|
}
|
||||||
return Number.MAX_SAFE_INTEGER;
|
return Number.MAX_SAFE_INTEGER;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits";
|
|||||||
import { getItemLogPath, initItemLogs, shutdownItemLogs } from "../src/main/item-log";
|
import { getItemLogPath, initItemLogs, shutdownItemLogs } from "../src/main/item-log";
|
||||||
import { initPackageLogs, shutdownPackageLogs } from "../src/main/package-log";
|
import { initPackageLogs, shutdownPackageLogs } from "../src/main/package-log";
|
||||||
import { createStoragePaths, emptySession } from "../src/main/storage";
|
import { createStoragePaths, emptySession } from "../src/main/storage";
|
||||||
import { primeDebridLinkRuntimeCooldownForTests, resetDebridLinkRuntimeStateForTests } from "../src/main/debrid";
|
import { primeDebridLinkRuntimeCooldownForTests, resetDebridLinkRuntimeStateForTests, primeMegaDebridRuntimeCooldownForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid";
|
||||||
|
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
||||||
import { getRenameLogPath, initRenameLog, shutdownRenameLog } from "../src/main/rename-log";
|
import { getRenameLogPath, initRenameLog, shutdownRenameLog } from "../src/main/rename-log";
|
||||||
import { UnrestrictedLink } from "../src/main/realdebrid";
|
import { UnrestrictedLink } from "../src/main/realdebrid";
|
||||||
|
|
||||||
@ -155,6 +156,7 @@ async function removeDirWithRetries(dir: string): Promise<void> {
|
|||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
resetDebridLinkRuntimeStateForTests();
|
resetDebridLinkRuntimeStateForTests();
|
||||||
|
resetMegaDebridRuntimeStateForTests();
|
||||||
shutdownItemLogs();
|
shutdownItemLogs();
|
||||||
shutdownPackageLogs();
|
shutdownPackageLogs();
|
||||||
shutdownRenameLog();
|
shutdownRenameLog();
|
||||||
@ -6642,6 +6644,83 @@ describe("download manager", () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not over-admit Mega-Debrid Web validations when an account is in cooldown", async () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
|
||||||
|
let unrestrictCalls = 0;
|
||||||
|
const pendingRejectors = new Set<(error: Error) => void>();
|
||||||
|
|
||||||
|
primeMegaDebridRuntimeCooldownForTests(`${getMegaDebridAccountId("mega-user-b")}:web`, 120000);
|
||||||
|
|
||||||
|
const manager = new DownloadManager(
|
||||||
|
{
|
||||||
|
...defaultSettings(),
|
||||||
|
megaCredentials: "mega-user-a:pass-a\nmega-user-b:pass-b",
|
||||||
|
megaDebridWebEnabled: true,
|
||||||
|
megaDebridApiEnabled: false,
|
||||||
|
megaDebridPreferApi: false,
|
||||||
|
providerOrder: [],
|
||||||
|
providerPrimary: "megadebrid",
|
||||||
|
providerSecondary: "none",
|
||||||
|
providerTertiary: "none",
|
||||||
|
outputDir: path.join(root, "downloads"),
|
||||||
|
extractDir: path.join(root, "extract"),
|
||||||
|
autoExtract: false,
|
||||||
|
autoReconnect: false,
|
||||||
|
enableIntegrityCheck: false,
|
||||||
|
maxParallel: 4
|
||||||
|
},
|
||||||
|
emptySession(),
|
||||||
|
createStoragePaths(path.join(root, "state")),
|
||||||
|
{
|
||||||
|
megaWebUnrestrict: vi.fn(async (_link: string, signal?: AbortSignal) => {
|
||||||
|
unrestrictCalls += 1;
|
||||||
|
return await new Promise<UnrestrictedLink | null>((resolve, reject) => {
|
||||||
|
const rejector = (error: Error): void => {
|
||||||
|
signal?.removeEventListener("abort", onAbort);
|
||||||
|
pendingRejectors.delete(rejector);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
const onAbort = (): void => {
|
||||||
|
rejector(new Error("aborted:test-mega-web"));
|
||||||
|
};
|
||||||
|
if (signal?.aborted) {
|
||||||
|
onAbort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
signal?.addEventListener("abort", onAbort, { once: true });
|
||||||
|
pendingRejectors.add(rejector);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.addPackages([{
|
||||||
|
name: "mega-web-cooldown",
|
||||||
|
links: [
|
||||||
|
"https://rapidgator.net/file/mega-web-1.part1.rar.html",
|
||||||
|
"https://rapidgator.net/file/mega-web-2.part2.rar.html",
|
||||||
|
"https://rapidgator.net/file/mega-web-3.part3.rar.html"
|
||||||
|
]
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await manager.start();
|
||||||
|
await waitFor(() => unrestrictCalls === 1, 10000);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||||
|
|
||||||
|
const items = Object.values(manager.getSnapshot().session.items);
|
||||||
|
expect(items.filter((item) => item.status === "validating")).toHaveLength(1);
|
||||||
|
expect(items.filter((item) => item.status === "queued")).toHaveLength(2);
|
||||||
|
expect(unrestrictCalls).toBe(1);
|
||||||
|
|
||||||
|
manager.stop();
|
||||||
|
for (const reject of Array.from(pendingRejectors)) {
|
||||||
|
reject(new Error("aborted:test-mega-web"));
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||||
|
});
|
||||||
|
|
||||||
it("shows the same AllDebrid countdown for all immediately free slots", async () => {
|
it("shows the same AllDebrid countdown for all immediately free slots", async () => {
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
tempDirs.push(root);
|
tempDirs.push(root);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user