Compare commits
No commits in common. "39c587c8b79aefe18553458b12d50e1f96a68ef4" and "6ae9f5d7252be42f571f781b7a26c0711819e270" have entirely different histories.
39c587c8b7
...
6ae9f5d725
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.220",
|
"version": "1.7.219",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -1885,9 +1885,6 @@ class MegaDebridClient {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
if (/queue.?timeout/i.test(lastError)) {
|
|
||||||
throw new Error(lastError.replace(/^Error:\s*/i, ""));
|
|
||||||
}
|
|
||||||
throw new Error("aborted:debrid");
|
throw new Error("aborted:debrid");
|
||||||
}
|
}
|
||||||
if (web?.directUrl) {
|
if (web?.directUrl) {
|
||||||
|
|||||||
@ -9436,25 +9436,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const megaRawError = error instanceof Error ? String(error.message || "") : String(error || "");
|
const megaResetPark = parseMegaDebridResetPark(errorText);
|
||||||
const megaCooldownRetry = parseMegaDebridCooldownRetry(megaRawError);
|
|
||||||
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
|
||||||
active.unrestrictRetries += 1;
|
|
||||||
item.retries += 1;
|
|
||||||
logger.warn(`Mega-Debrid Account-Cooldown: item=${item.fileName || item.id}, retry=${active.unrestrictRetries}/${retryDisplayLimit}, delay=${megaCooldownRetry.delayMs}ms, link=${item.url.slice(0, 80)}`);
|
|
||||||
this.queueRetry(
|
|
||||||
item,
|
|
||||||
active,
|
|
||||||
megaCooldownRetry.delayMs,
|
|
||||||
`Mega-Debrid Cooldown, neuer Versuch in ${Math.ceil(megaCooldownRetry.delayMs / 1000)}s`
|
|
||||||
);
|
|
||||||
item.lastError = megaCooldownRetry.detail || errorText;
|
|
||||||
this.persistSoon();
|
|
||||||
this.emitState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const megaResetPark = parseMegaDebridResetPark(megaRawError);
|
|
||||||
if (megaResetPark && active.unrestrictRetries < maxUnrestrictRetries) {
|
if (megaResetPark && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||||
active.unrestrictRetries += 1;
|
active.unrestrictRetries += 1;
|
||||||
item.retries += 1;
|
item.retries += 1;
|
||||||
@ -9471,6 +9453,23 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const megaCooldownRetry = parseMegaDebridCooldownRetry(errorText);
|
||||||
|
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||||
|
active.unrestrictRetries += 1;
|
||||||
|
item.retries += 1;
|
||||||
|
logger.warn(`Mega-Debrid Account-Cooldown: item=${item.fileName || item.id}, retry=${active.unrestrictRetries}/${retryDisplayLimit}, delay=${megaCooldownRetry.delayMs}ms, link=${item.url.slice(0, 80)}`);
|
||||||
|
this.queueRetry(
|
||||||
|
item,
|
||||||
|
active,
|
||||||
|
megaCooldownRetry.delayMs,
|
||||||
|
`Mega-Debrid Cooldown, neuer Versuch in ${Math.ceil(megaCooldownRetry.delayMs / 1000)}s`
|
||||||
|
);
|
||||||
|
item.lastError = megaCooldownRetry.detail || errorText;
|
||||||
|
this.persistSoon();
|
||||||
|
this.emitState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isMegaDebridTransientResolveFailure(errorText) && active.unrestrictRetries < maxUnrestrictRetries) {
|
if (isMegaDebridTransientResolveFailure(errorText) && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||||
active.unrestrictRetries += 1;
|
active.unrestrictRetries += 1;
|
||||||
item.retries += 1;
|
item.retries += 1;
|
||||||
@ -10608,12 +10607,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
rewindTarget
|
rewindTarget
|
||||||
});
|
});
|
||||||
} catch (rewindError) {
|
} catch (rewindError) {
|
||||||
try {
|
logAttemptEvent("WARN", "Resume-Schutz: finales Rueckspulen fehlgeschlagen", {
|
||||||
fs.rmSync(effectiveTargetPath, { force: true });
|
|
||||||
item.downloadedBytes = 0;
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
logAttemptEvent("WARN", "Resume-Schutz: finales Rueckspulen fehlgeschlagen, Teil-Datei verworfen", {
|
|
||||||
attempt,
|
attempt,
|
||||||
written,
|
written,
|
||||||
rewindTarget,
|
rewindTarget,
|
||||||
|
|||||||
@ -177,12 +177,12 @@ async function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal, abortErrorFactory: () => Error = abortError): Promise<T> {
|
async function raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
||||||
if (!signal) {
|
if (!signal) {
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw abortErrorFactory();
|
throw abortError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
@ -194,7 +194,7 @@ async function raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal, abort
|
|||||||
}
|
}
|
||||||
settled = true;
|
settled = true;
|
||||||
signal.removeEventListener("abort", onAbort);
|
signal.removeEventListener("abort", onAbort);
|
||||||
reject(abortErrorFactory());
|
reject(abortError());
|
||||||
};
|
};
|
||||||
|
|
||||||
signal.addEventListener("abort", onAbort, { once: true });
|
signal.addEventListener("abort", onAbort, { once: true });
|
||||||
@ -284,7 +284,6 @@ export class MegaWebFallback {
|
|||||||
private async runExclusive<T>(job: () => Promise<T>, key: string, signal?: AbortSignal): Promise<T> {
|
private async runExclusive<T>(job: () => Promise<T>, key: string, signal?: AbortSignal): Promise<T> {
|
||||||
const queuedAt = Date.now();
|
const queuedAt = Date.now();
|
||||||
const QUEUE_WAIT_TIMEOUT_MS = 90000;
|
const QUEUE_WAIT_TIMEOUT_MS = 90000;
|
||||||
let workStarted = false;
|
|
||||||
const guardedJob = async (): Promise<T> => {
|
const guardedJob = async (): Promise<T> => {
|
||||||
throwIfAborted(signal);
|
throwIfAborted(signal);
|
||||||
const waited = Date.now() - queuedAt;
|
const waited = Date.now() - queuedAt;
|
||||||
@ -292,7 +291,6 @@ export class MegaWebFallback {
|
|||||||
traceConversionPhase({ phase: "web-queue", provider: "megadebrid-web", queueWaitMs: waited, outcome: "queue-timeout", detail: `${Math.floor(waited / 1000)}s in Web-Queue gewartet` });
|
traceConversionPhase({ phase: "web-queue", provider: "megadebrid-web", queueWaitMs: waited, outcome: "queue-timeout", detail: `${Math.floor(waited / 1000)}s in Web-Queue gewartet` });
|
||||||
throw new Error(`Mega-Web Queue-Timeout (${Math.floor(waited / 1000)}s gewartet)`);
|
throw new Error(`Mega-Web Queue-Timeout (${Math.floor(waited / 1000)}s gewartet)`);
|
||||||
}
|
}
|
||||||
workStarted = true;
|
|
||||||
const workStartedAt = Date.now();
|
const workStartedAt = Date.now();
|
||||||
try {
|
try {
|
||||||
const result = await job();
|
const result = await job();
|
||||||
@ -306,11 +304,7 @@ export class MegaWebFallback {
|
|||||||
const prev = this.queues.get(key) ?? Promise.resolve();
|
const prev = this.queues.get(key) ?? Promise.resolve();
|
||||||
const run = prev.then(guardedJob, guardedJob);
|
const run = prev.then(guardedJob, guardedJob);
|
||||||
this.queues.set(key, run.then(() => undefined, () => undefined));
|
this.queues.set(key, run.then(() => undefined, () => undefined));
|
||||||
return raceWithAbort(run, signal, () =>
|
return raceWithAbort(run, signal);
|
||||||
workStarted
|
|
||||||
? abortError()
|
|
||||||
: new Error(`Mega-Web Queue-Timeout (abgebrochen nach ${Math.floor((Date.now() - queuedAt) / 1000)}s Wartezeit, Account war belegt)`)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async login(login: string, password: string, signal?: AbortSignal): Promise<string> {
|
private async login(login: string, password: string, signal?: AbortSignal): Promise<string> {
|
||||||
|
|||||||
@ -627,24 +627,12 @@ function normalizeAudioStripSummary(raw: unknown): AudioStripSummary | undefined
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateLegacyMegaEnableFlags(parsed: AppSettings): AppSettings {
|
|
||||||
if (parsed.megaDebridApiEnabled !== undefined || parsed.megaDebridWebEnabled !== undefined) {
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
const hasMegaCreds = Boolean(asText(parsed.megaLogin) && asText(parsed.megaPassword));
|
|
||||||
if (!hasMegaCreds) {
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
const preferApi = parsed.megaDebridPreferApi !== undefined ? Boolean(parsed.megaDebridPreferApi) : true;
|
|
||||||
return { ...parsed, megaDebridApiEnabled: preferApi, megaDebridWebEnabled: !preferApi };
|
|
||||||
}
|
|
||||||
|
|
||||||
function readSettingsFile(filePath: string): AppSettings | null {
|
function readSettingsFile(filePath: string): AppSettings | null {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as AppSettings;
|
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as AppSettings;
|
||||||
const merged = normalizeSettings({
|
const merged = normalizeSettings({
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
...migrateLegacyMegaEnableFlags(parsed)
|
...parsed
|
||||||
});
|
});
|
||||||
return sanitizeCredentialPersistence(merged);
|
return sanitizeCredentialPersistence(merged);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,125 +1,8 @@
|
|||||||
# Autonomer Audit-Loop — Download/Fehler/Rotation (Goal 2026-06-17, 8h)
|
# Autonomer Audit-Loop — Download/Fehler/Rotation (Goal 2026-06-17, 8h)
|
||||||
|
|
||||||
## GOAL-ANPASSUNG (Nutzer, nach Runde 8 + Synthese-Start): noch Runde 9 + 10, dann Goal BEENDEN.
|
|
||||||
Plan: Synthese-Pass (wrexz7mdf, Capstone R1-8) auswerten → Runde 9 (Provider-spezifische
|
|
||||||
Unrestrict-/Rotations-Pfade: Mega-Web-Fallback Session/Single-Flight, AllDebrid Host-Cooldown/
|
|
||||||
Rapidgator-Backoff, DebridLink-Key-Rotation, Passwort-Cache-Race) → Runde 10 (Concurrency/Locking +
|
|
||||||
Settings/Persistenz-Integritaet: Hybrid-Race, targetPath-Claim/Release-Races, Settings-Migration,
|
|
||||||
Backup/Restore, account-check) → Abschlussbericht + Ende. Fixes je rot-bewiesen, buendeln zu v1.7.220 falls HIGH/MED.
|
|
||||||
|
|
||||||
Disziplin: erst BELEGEN (Code-Zitat + konkretes Szenario), dann adversarisch verifizieren,
|
Disziplin: erst BELEGEN (Code-Zitat + konkretes Szenario), dann adversarisch verifizieren,
|
||||||
dann TDD-Fix. Kein Blind-Fix. Tests gruen + tsc=6 nach jeder Runde. Periodisch releasen.
|
dann TDD-Fix. Kein Blind-Fix. Tests gruen + tsc=6 nach jeder Runde. Periodisch releasen.
|
||||||
|
|
||||||
## Runde 9 (Provider-spezifische Unrestrict-/Rotations-Pfade) — Mega-Web-Single-Flight + DebridLink-Key + Concurrency
|
|
||||||
Fokus: Mega-Web-Fallback Session/Single-Flight-Queue, DebridLink-Key-Rotation, In-Flight-Verteilung.
|
|
||||||
- **CONFIRMED HIGH (GEFIXT) MW-1 Selbst-Cooldown eines GESUNDEN, nur in der Queue wartenden Web-Accounts:**
|
|
||||||
Der Caller-Timeout (`DEFAULT_UNRESTRICT_TIMEOUT_MS`=60s) feuert, waehrend die zweite Umwandlung eines
|
|
||||||
Accounts noch SERIELL in der Mega-Web-Single-Flight-Queue (90s `QUEUE_WAIT_TIMEOUT_MS`) auf den laufenden
|
|
||||||
Vorgaenger wartet — also bevor ueberhaupt echte Arbeit begann. `raceWithAbort` warf bisher unbedingt
|
|
||||||
`aborted:mega-web`; `unrestrictViaWeb` (debrid.ts:1887) flachte JEDEN signal-aborted-Fall zu `aborted:debrid`
|
|
||||||
ab; die Rotation (debrid.ts:2072) wertete das via `/aborted/i && !/timeout/i` → `ranLongEnough`
|
|
||||||
(elapsedMs schliesst die Queue-Wartezeit ein, also >= 8s) → **120s Account-Cooldown auf einen voellig
|
|
||||||
gesunden Account**. Genau die vom Nutzer gemeldete „Tool sperrt sich selbst"-Klasse (Web-Variante).
|
|
||||||
Zweiteiliger Fix:
|
|
||||||
(1) `MegaWebFallback.runExclusive` (mega-web-fallback.ts) trackt `workStarted` und reicht eine
|
|
||||||
`abortErrorFactory` an `raceWithAbort`: abgebrochen-bevor-Arbeit-begann → `Mega-Web Queue-Timeout (…)`
|
|
||||||
(matcht `/queue.?timeout/i`), nur ein echter In-Arbeit-Abbruch bleibt `aborted:mega-web`.
|
|
||||||
(2) `unrestrictViaWeb` (debrid.ts:1887) bewahrt einen `/queue.?timeout/i`-klassifizierten lastError statt
|
|
||||||
ihn zu `aborted:debrid` zu plaetten → Rotation trifft die bestehende Queue-Timeout-Ausnahme
|
|
||||||
(classifyAccountFailure 2243 → cooldownMs 0) statt den Abbruch-Cooldown-Zweig.
|
|
||||||
Rot-bewiesen NICHT-vakuum (zwei Tests, beide per Temp-Revert rot verifiziert):
|
|
||||||
- Teil 1 (mega-web-fallback.test.ts): echtes `MegaWebFallback`, Queue mit langsamem Erst-Job belegt,
|
|
||||||
Zweit-Call WAEHREND in Queue abgebrochen → `rejects.toThrow(/queue.?timeout/i)` (ohne Fix: `aborted:mega-web`).
|
|
||||||
- Teil 2 (debrid.test.ts): durch `DebridService.unrestrictLink` mit `RD_MEGA_ABORT_MIN_RUN_MS=0` (besiegt die
|
|
||||||
Vakuum-Falle: der buggy Pfad WUERDE selbst bei Sofort-Abbruch cooldownen), Signal bricht WAEHREND des
|
|
||||||
Calls ab, megaWeb meldet Queue-Timeout → `getMegaDebridAccountCooldownState` bleibt null (ohne Fix: 120s
|
|
||||||
„Abbruch/Timeout nach 0s"). tsc=6, volle mega-web+debrid-Suite 103 gruen inkl. der bestehenden
|
|
||||||
Abbruch-Cooldown-Tests (echter langsamer Account cooled WEITERHIN — keine Regression).
|
|
||||||
- **Rotations-Verifikation (Advisor-Shippability-Diskriminator):** Nach dem No-Cooldown-Pfad retried der
|
|
||||||
Manager `unrestrictWithAccounts` frisch. Die Account-Wahl sortiert per `megaDebridInFlight`-TIEFE
|
|
||||||
(debrid.ts:1981-1986, least-busy zuerst). Invariante: jeder Belegt-Halter der MegaWebFallback-Queue
|
|
||||||
entspricht einem in-flight `client.unrestrictLink`, das `megaDebridInFlight` inkrementiert hat (2033,
|
|
||||||
Dekrement nur im finally 2152). Also sieht der Retry des queue-getimeouteten Links den saturierenden
|
|
||||||
Account bei Tiefe>=1 und rotiert auf einen freien — MW-1 ist NACHWEISLICH besser als das alte 120s-Lockout
|
|
||||||
(das einen nur-belegten gesunden Account sperrte). Kein Tight-Retry-Loop auf saturierter Queue, solange
|
|
||||||
irgendein Account frei ist; sind ALLE saturiert, queued der least-busy (unvermeidbar — keine freie Kapazitaet,
|
|
||||||
aber das alte Cooldown haette es via Faux-Park SCHLIMMER gemacht).
|
|
||||||
- **DOKUMENTIERT, nicht gefixt (LOW) DL-1 DebridLink-Key-Cooldown bei User-Cancel-Abbruch:** classifyKeyFailure
|
|
||||||
(debrid.ts:3118-3126) gibt fuer Text mit „aborted" (via isRetryableErrorText 597 + isTransport 3119)
|
|
||||||
`cooldownMs: 15_000` zurueck — auch bei einem SCHNELLEN User-Cancel (kein Min-Run-Gate wie bei Mega 2072).
|
|
||||||
Symptom: 15s Key-Cooldown nach Nutzer-Abbruch. Advisor (revidiert eigenen frueheren „clean fix"-Call):
|
|
||||||
classifyKeyFailure hat KEIN elapsedMs und DebridLink hat keinen Min-Run-Knopf-Aequivalent zu
|
|
||||||
`getMegaDebridAbortMinRunMs()`. Der treue Fix (Caller-Gate bei 2787 spiegeln) braucht entweder cross-Provider-
|
|
||||||
Wiederverwendung des Mega-Knopfes oder einen neuen DebridLink-Knopf = neue Oberflaeche im Live-Hot-Path fuer
|
|
||||||
einen LOW-Bug (Symptom: 15s nach User-Cancel). Klart die eigene „clean + null-Regression"-Schwelle NICHT →
|
|
||||||
dokumentiert, nicht gefixt.
|
|
||||||
- **DOKUMENTIERT, nicht gefixt (MED) DL-CONCURRENCY-PILEUP:** Bei gleichzeitigen Umwandlungen, die ALLE
|
|
||||||
Mega-Accounts saturieren, queued der least-busy-Account weitere Links seriell (per-Account-Single-Flight) →
|
|
||||||
Wartezeiten stapeln sich. MW-1 entfernt die FRUEHERE versehentliche Backpressure (der falsche 120s-Cooldown
|
|
||||||
wirkte als grobe Ratenbegrenzung), routet aber korrekt per In-Flight-Tiefe um belegte Accounts herum statt sie
|
|
||||||
faelschlich zu sperren — strikt besser. Echter Fix (In-Flight-Tiefen-Spread groesser ziehen / globale
|
|
||||||
Web-Parallelitaetsgrenze) = groessere Aenderung, 4.-Bug-Risiko auf Live-Server → deferred. MW-1 reduziert das
|
|
||||||
Pileup-in-Park-Risiko bereits (kein Faux-Lockout merely-busy Accounts).
|
|
||||||
|
|
||||||
## Runde 10 (Concurrency/Locking + Settings/Persistenz-Integritaet) — LETZTE RUNDE (Nutzer: „nach der Runde ist Schluss")
|
|
||||||
4 Finder (hybrid/targetPath-Races, Slot-Accounting, Settings-Migration/Persistenz, Backup-Restore/account-check)
|
|
||||||
→ adversarisch 3 Lenses → Synthese. Workflow wwyqsdrf9: 2 Finder (slot-accounting, settings-persistence) starben
|
|
||||||
an Stream-Idle-Timeout (Riesen-Dateien) → 0/7 confirmed war NUR fuer 2 von 4 Dimensionen ehrlich. KEIN stilles
|
|
||||||
Coverage-Loch akzeptiert → Re-Run wwyqsdrf9b (wfmu4pw2n) mit engerem Scope (Grep-dann-Region statt Ganzdatei).
|
|
||||||
- **Dim hybrid/targetPath + backup/account-check (wwyqsdrf9): 7 Kandidaten, ALLE refutiert (>=2/3), quell-reverifiziert.**
|
|
||||||
Kern-Invarianten halten: synchrones `claimTargetPath` (dl-mgr:6257) = nie zwei Items auf einem Pfad → alle drei
|
|
||||||
Hybrid-„Races" kollabieren zu Sub-Sekunden-Redundanzarbeit, kein Collision/Korruption; jeder Loss-Ausgang von
|
|
||||||
CRC/Extraction-Fail re-queued (`hybridExtractRequeue.add` 11739). Restore ist fail-closed (encrypted-or-reject,
|
|
||||||
Binaerheader „MDD1" wirft in JSON.parse vor normalizeSettings). account-check `valid:false` ist reiner
|
|
||||||
Renderer-Badge (kein src/main liest ihn als Gate; Disable laeuft ueber megaDebridDisabledAccountIds).
|
|
||||||
- **CONFIRMED MED (DOKUMENTIERT, nicht gefixt) BYTE-DROP-RETRY-1 (wfmu4pw2n):** Integrity-/too-small-/tiny-Retry
|
|
||||||
doppelzaehlt eine volle Datei in die Byte-Statistik. `dropItemContribution` (6247) loescht den
|
|
||||||
itemContributedBytes-Eintrag OHNE von session.totalDownloadedBytes abzuziehen (eigener Kommentar: „retry path
|
|
||||||
subtracts on its own"), aber die EINZIGE Subtraktion (9991-9997) liest genau diesen geloeschten Eintrag → 0 →
|
|
||||||
Subtraktion tot → Re-Download addiert N nochmal → (k+1)*fileSize nach k Integrity-Fails. enableIntegrityCheck
|
|
||||||
default true → organisch. Verdict isReal (CORRECTNESS+REPRO), aber BLAST-RADIUS = NUR Statistik: kein
|
|
||||||
Slot/Admission/Semaphore liest diese Counter (grep-belegt), Session-Counter self-healen bei jedem Neustart,
|
|
||||||
nur persistiertes totalDownloadedAllTime + avg-Speed bleiben dauerhaft inflationiert = kosmetisch. NICHT clean
|
|
||||||
(dropItemContribution ueber 26 Call-Sites ueberladen; ~10 im Retry-Bereich; ein 3-Site-Patch liefert
|
|
||||||
partielles/inkonsistentes Accounting = arguably schlimmer; Guard-Test dl-mgr.test.ts:6152 sperrt die
|
|
||||||
Completion-Removal-Semantik) → DOKUMENTIERT mit Rezept (dropItemContribution splitten in ForCompletion/ForRetry
|
|
||||||
nach Klassifikation aller Retry-Sites re-download-vs-terminal). Kosmetisch + nicht-clean → klart Live-Schwelle nicht.
|
|
||||||
- **CONFIRMED MED (GEFIXT) SET-MIG-01 (wfmu4pw2n):** Pre-v1.6.90-Config-Migration tot → Mega-Debrid wird beim
|
|
||||||
ersten Settings-Panel-Save still aus der Provider-Reihenfolge demotet. readSettingsFile (storage.ts:633) merged
|
|
||||||
`{...defaultSettings(), ...parsed}`; defaultSettings setzt megaDebridApiEnabled/WebEnabled:false (constants.ts:50-51)
|
|
||||||
BEVOR normalizeSettings laeuft → der `=== undefined`-Migrationszweig (355-360) ist tot auf dem Disk-Pfad (Git:
|
|
||||||
tot seit v1.6.90/0003d78). Renderer (App.tsx:447-494) gated Mega-Inklusion am false-Flag → persistDraftSettings
|
|
||||||
schreibt eine Mega-bereinigte providerOrder zurueck. Fix: reine `migrateLegacyMegaEnableFlags(parsed)` in
|
|
||||||
readSettingsFile — seedet apiEnabled=preferApi/webEnabled=!preferApi NUR wenn BEIDE Flags im RAW-parsed fehlen
|
|
||||||
UND Creds da sind (Creds-Erkennung wie normalizeSettings: asText(login)&&asText(password)). Lokalisiert, keine
|
|
||||||
normalizeSettings-Signatur-Aenderung, getypt (kein Cast). EHRLICHER Scope (Advisor): rettet NUR Legacy-Configs,
|
|
||||||
die seit dem Upgrade noch NICHT ueber das Settings-Panel neu gespeichert wurden (ein Post-Upgrade-Save schreibt
|
|
||||||
present-false → Trigger feuert nicht mehr) — schmales historisches Fenster, NICHT „rettet die Live-Mega dieses
|
|
||||||
Nutzers". HARTE Grenze (Advisor): Trigger bleibt absent-both; present-false NICHT anfassen (= bewusst-deaktiviert,
|
|
||||||
ununterscheidbar → das geparkte entscheidungen-offen #2-Migrationsrisiko). Rot-bewiesen NICHT-vakuum (Assertion NUR
|
|
||||||
auf den geladenen Flags — providerOrder demotet backend-seitig nicht = waere vakuum; via Temp-Revert rot bestaetigt:
|
|
||||||
apiEnabled true→false). Boundary-Test (bewusst-deaktiviert bleibt false + ohne Creds keine Migration) bleibt beim
|
|
||||||
Revert gruen = unabhaengig. Volle Suite 890 gruen, tsc=6.
|
|
||||||
- **REFUTED (wfmu4pw2n) SET-PERSIST-02:** Residual-Lost-Update nach R2-Generations-Guard — der einzige verlorene
|
|
||||||
Payload ist totalRuntimeAllTimeMs (`<=`-Ratchet, naechste Session re-derived) + contrived Sub-ms-Race der die
|
|
||||||
Prozess-Teardown gewinnen muss → self-healing kosmetisch, nicht erreichbar.
|
|
||||||
- **Coherence-Verdikt:** Concurrency-Accounting + Settings-Persistenz sind kohaerent. Byte-Counter sind
|
|
||||||
telemetry-only, voll entkoppelt von Admission/Slot (grep-belegt) → BYTE-DROP kann nicht stranden/freezen/
|
|
||||||
ueber-admiten. Persistenz kohaerent bis auf EINE benannte Inkohaerenz: Backend normalizeConfiguredProvider
|
|
||||||
(storage.ts:144) mapt „megadebrid"→konkret unabhaengig der Flags, Renderer gated auf dem Flag = Wurzel von
|
|
||||||
SET-MIG-01 (jetzt am Loader gefixt).
|
|
||||||
- **EHRLICHE Coverage-Luecke (vom Audit selbst aufgedeckt, R10-HYB-3):** Die in project_pending genannte
|
|
||||||
Passwort-Cache-Race liegt NICHT in download-manager.ts (dort kein passwordCache/resolvePassword), sondern in
|
|
||||||
src/main/extractor.ts — ausserhalb des Round-10-Scopes. Bewusst NICHT in dieser letzten Runde auditiert (Nutzer:
|
|
||||||
„nach der Runde ist Schluss"); als das eine identifizierte, un-auditierte Concurrency-Seam fuer eine etwaige
|
|
||||||
kuenftige Runde dokumentiert statt still fallengelassen.
|
|
||||||
|
|
||||||
## GOAL-ABSCHLUSS
|
|
||||||
Runde 9 + 10 erledigt (Nutzer-Anpassung). Release v1.7.220 buendelt: 85c8d6b (Synthese C1/C2/RANGE1-Haertung) +
|
|
||||||
MW-1 (HIGH, Web-Selbstcooldown) + SET-MIG-01 (MED, Legacy-Mega-Demotion). Gitea + GitHub-Mirror MIT .exe (4 Assets).
|
|
||||||
Dokumentiert-nicht-gefixt: DL-1, DL-CONCURRENCY-PILEUP, BYTE-DROP-RETRY-1, extractor.ts-Passwort-Cache-Seam.
|
|
||||||
Beim Nutzer (nicht autonom): 60s-Failover-Kappung + gespiegelter Mega-API/Web-Schalter (entscheidungen-offen.md).
|
|
||||||
|
|
||||||
## Runde 1 (laeuft)
|
## Runde 1 (laeuft)
|
||||||
- Discover+Verify-Workflow ueber 7 Subsysteme (scheduler-slots, unrestrict-retry, mega-rotation,
|
- Discover+Verify-Workflow ueber 7 Subsysteme (scheduler-slots, unrestrict-retry, mega-rotation,
|
||||||
classify-cooldown, mega-web-token, provider-chain-timeout, account-availability).
|
classify-cooldown, mega-web-token, provider-chain-timeout, account-availability).
|
||||||
@ -285,12 +168,6 @@ importiert+konstruiert). Risiko Startup-Regression empirisch ausgeschlossen.
|
|||||||
- Advisor NICHT auf Kadenz pollen — nur bei echter Ship/Fix-Entscheidung mit neuer Info.
|
- Advisor NICHT auf Kadenz pollen — nur bei echter Ship/Fix-Entscheidung mit neuer Info.
|
||||||
- Die 2 HIGH Produkt/UI-Entscheidungen bleiben beim Nutzer (nicht autonom shippen).
|
- Die 2 HIGH Produkt/UI-Entscheidungen bleiben beim Nutzer (nicht autonom shippen).
|
||||||
|
|
||||||
## Release-Status: v1.7.219 RELEASED (Gitea 6ae9f5d + GitHub-Mirror df3670a)
|
|
||||||
Roll-up Runde 7+8: be15419 MED VP-1 dt.-Tonspur + eacd0c9 HIGH RANGE-1 Silent-Corruption + REWIND-TRUNCATE.
|
|
||||||
HIGH rechtfertigt Release. Advisor explizit cleared (vor Implementierung konsultiert, Check A verifiziert).
|
|
||||||
Mirror kuratiert ohne CLAUDE.md/tasks/ (Leak-Check sauber). NAECHSTER SCHRITT (Advisor): Synthese/Regressions-
|
|
||||||
Pass ueber kumulativen Diff aller Session-Fixes (Interaktion), KEINE weitere Discovery-Runde.
|
|
||||||
|
|
||||||
## Release-Status: v1.7.218 RELEASED (Gitea bbb9355 + GitHub-Mirror 94d143b)
|
## Release-Status: v1.7.218 RELEASED (Gitea bbb9355 + GitHub-Mirror 94d143b)
|
||||||
Roll-up Runde 4+5+6: 986fbab MED Provider-Cooldown-Routing + 03c908b No-Stranding-Test +
|
Roll-up Runde 4+5+6: 986fbab MED Provider-Cooldown-Routing + 03c908b No-Stranding-Test +
|
||||||
f1e35f5 LOW Mega-Tagesreset-Park + d2a1b83 HIGH Shelve-Loop-RetryLimit. HIGH rechtfertigt das
|
f1e35f5 LOW Mega-Tagesreset-Park + d2a1b83 HIGH Shelve-Loop-RetryLimit. HIGH rechtfertigt das
|
||||||
@ -302,28 +179,6 @@ Multi-Agent-adversarisch, Routing-Fix frueher advisor-gesegnet, Praezedenz 215/2
|
|||||||
Runde-5/6-Charakterisierungen (PP-SEM-1 benign, DISK-1 deferred, deferred-LOW-Cluster #10/#11/#13
|
Runde-5/6-Charakterisierungen (PP-SEM-1 benign, DISK-1 deferred, deferred-LOW-Cluster #10/#11/#13
|
||||||
benign) NICHT released — dokumentiert.
|
benign) NICHT released — dokumentiert.
|
||||||
|
|
||||||
## SYNTHESE/Regressions-Pass (Fix-Interaktion ueber kumulativen Diff v1.7.212..HEAD) — Workflow wrexz7mdf
|
|
||||||
3 Reviewer (Catch-Cascade / Streaming / Provider-Chain) + adversarisch verifizieren. 3 confirmed (1 MED, 2 LOW),
|
|
||||||
2 refuted. Bestaetigt: Rest komponiert sauber; Risiko war (wie Advisor sagte) Fix-Interaktion, nicht neue Bugs.
|
|
||||||
- **CONFIRMED MED (GEFIXT) C1 reset_park maskiert kurzen Cooldown:** Bei BEIDEN Mega-Modi aktiv aggregiert die
|
|
||||||
Provider-Kette beide Token (`mega_debrid_reset_park:LONG` von API-Park + `mega_debrid_cooldown:30000` von
|
|
||||||
Web-Cooldown). Manager prueft reset_park VOR cooldown → Item ~24h geparkt obwohl Web in ~30s erholt. Fix:
|
|
||||||
Cooldown-Zweig VOR reset_park (kuerzerer Delay gewinnt). Rot-bewiesen (Single-Pass-processItem-Test: Aggregat
|
|
||||||
beider Token → retryAfter < 60s, fullStatus "Cooldown" nicht "Tagesreset"; ohne Reorder ~24h).
|
|
||||||
- **CONFIRMED LOW (GEFIXT) C2 Token-Truncation:** reset_park-Token konnte von compactErrorText (220-Zeichen-Cap)
|
|
||||||
abgeschnitten werden, wenn Mega nicht Lead + vorheriger Provider verbose → Park still uebersprungen. Fix: Mega-
|
|
||||||
Token aus der UNGEKUERZTEN error.message parsen (megaRawError). (Im selben Edit wie C1.)
|
|
||||||
- **CONFIRMED LOW (GEHAERTET + Doku korrigiert) RANGE1-TRUNCATEFAIL-FINALIZE:** Mein 219-Commit OVERCLAIMte —
|
|
||||||
bei FEHLGESCHLAGENEM finalem Rewind-truncate finalisiert tryFinalizeItemFromDisk(9238) die Garbage-Datei
|
|
||||||
(size==totalBytes) VOR dem Re-Entry. NET-NEUTRAL vs pre-audit (v1.7.212 hatte denselben Finalize), KEINE
|
|
||||||
Regression. Haertung: bei truncate-Fehler jetzt rmSync(Teil-Datei)+downloadedBytes=0 → Finalize lehnt ab →
|
|
||||||
sauberer Re-Download (best-effort; faellt der rm auch, bleibt es net-neutral). Macht den 219-Claim wahr.
|
|
||||||
- **Refuted (1/3 je):** reset_park von shelve-guard/unrestrictRetries-Exhaustion unter finite retryLimit
|
|
||||||
verdraengt — beide nicht bestaetigt (compound/nicht erreichbar).
|
|
||||||
- **Capstone-Verdikt:** Die 8-Runden-Fixes komponieren — inner-rewind success-only-reset und final-rewind sind
|
|
||||||
per-attempt mutually exclusive; prealloc-reconcile double-truncated nicht nach erfolgreichem Rewind; Routing
|
|
||||||
+ cooldown + park kohaerent NACH C1/C2-Fix. Diese 3 Synthese-Fixes buendeln zu v1.7.220.
|
|
||||||
|
|
||||||
## Runde 8 (Byte-Streaming downloadToFile: Range/Resume/Append/Truncation) — Workflow wwjz4srkq
|
## Runde 8 (Byte-Streaming downloadToFile: Range/Resume/Append/Truncation) — Workflow wwjz4srkq
|
||||||
3 Finder + adversarisch verifizieren. 3 confirmed (1 HIGH + 2 MED, alle Silent-Corruption-Familie), 1 refuted.
|
3 Finder + adversarisch verifizieren. 3 confirmed (1 HIGH + 2 MED, alle Silent-Corruption-Familie), 1 refuted.
|
||||||
Advisor VOR Implementierung konsultiert (HIGH-Hot-Path) — Design + Check-A-Branch (totalBytes-null) bestaetigt.
|
Advisor VOR Implementierung konsultiert (HIGH-Hot-Path) — Design + Check-A-Branch (totalBytes-null) bestaetigt.
|
||||||
|
|||||||
@ -63,30 +63,6 @@ ODER zwei unabhängige Pro-Modus-Schalter (mehr Kontrolle, aber UI + Migration n
|
|||||||
|
|
||||||
## Erledigt in dieser Runde (zur Info, kein Handlungsbedarf)
|
## Erledigt in dieser Runde (zur Info, kein Handlungsbedarf)
|
||||||
|
|
||||||
- **Alte Konfiguration: Mega-Debrid fällt nach einem Upgrade nicht mehr still aus der Provider-Reihenfolge:**
|
|
||||||
Eine Konfigurationsdatei, die noch von einer sehr alten Version (vor v1.6.90) stammt, kannte die getrennten
|
|
||||||
Mega-Debrid „API aktiv"/„Web aktiv"-Schalter noch nicht. Beim Laden wurden diese fehlenden Schalter still auf
|
|
||||||
„aus" gesetzt, obwohl Mega-Zugangsdaten vorhanden waren — und sobald man danach das erste Mal die Einstellungen
|
|
||||||
speicherte, wurde Mega-Debrid dadurch lautlos aus der Provider-Reihenfolge entfernt. Jetzt wird beim Laden einer
|
|
||||||
solchen alten Datei erkannt, dass die Schalter komplett fehlen, und Mega-Debrid passend zu deiner Bevorzugung
|
|
||||||
(API oder Web) aktiviert — genau die Migration, die ursprünglich gedacht war, aber durch einen Default-Vorrang
|
|
||||||
nie ausgelöst hatte. Ehrlicher Umfang: Das betrifft nur alte Dateien, die seit dem Upgrade noch NICHT über die
|
|
||||||
Einstellungen neu gespeichert wurden — wer seit dem Update schon einmal in den Einstellungen gespeichert hat, hat
|
|
||||||
die Schalter bereits als „aus" stehen und greift dort weiterhin manuell ein (das ist Absicht: ein bewusst auf
|
|
||||||
„aus" gestellter Schalter wird NICHT wieder angeschaltet). Rot-bewiesener Test; voller Testlauf grün.
|
|
||||||
|
|
||||||
- **Mega-Web: gesunder Account sperrt sich nicht mehr selbst, nur weil er gerade belegt war (deine „Tool sperrt sich selbst"-Klasse, Web-Variante):**
|
|
||||||
Wenn mehrere Links gleichzeitig über DENSELBEN Mega-Account umgewandelt wurden, laufen sie absichtlich nacheinander
|
|
||||||
(eine Warteschlange pro Account, damit nicht doppelt eingeloggt/gehämmert wird). Wartete ein Link in dieser Schlange
|
|
||||||
noch auf seinen Vorgänger und lief dabei der 60-Sekunden-Gesamttimeout ab, wurde der Abbruch fälschlich wie ein echter
|
|
||||||
Account-Fehler gewertet → der völlig gesunde Account bekam 120 Sekunden Sperre. Jetzt wird ein Abbruch, der NOCH IN DER
|
|
||||||
Warteschlange passiert (bevor echte Arbeit begann), als reiner Warteschlangen-Timeout erkannt: KEINE Account-Sperre,
|
|
||||||
der Link wird einfach erneut versucht und rotiert dann von selbst auf einen freien Account. Ein echter Abbruch MITTEN
|
|
||||||
in der Arbeit sperrt den Account weiterhin (damit langsame Accounts korrekt übersprungen werden) — das blieb unverändert.
|
|
||||||
Zwei rot-bewiesene Tests (jeder ohne Fix nachweislich rot; der zweite läuft komplett durch die echte Account-Rotation
|
|
||||||
und prüft, dass keine Sperre gesetzt wird). Die Rotation auf einen freien Account ist über die Auslastungs-Verteilung
|
|
||||||
(am-wenigsten-belegter-Account-zuerst) abgesichert — strikt besser als die alte 120s-Pauschalsperre.
|
|
||||||
|
|
||||||
- **Stille Datei-Beschädigung beim Resume nach Verbindungsabbruch behoben (für Dateien mit bekannter Größe):**
|
- **Stille Datei-Beschädigung beim Resume nach Verbindungsabbruch behoben (für Dateien mit bekannter Größe):**
|
||||||
Wenn ein Debrid-Server beim Abbruch einen kleinen Fehler-Müll-Block mitten in den Datenstrom schreibt und
|
Wenn ein Debrid-Server beim Abbruch einen kleinen Fehler-Müll-Block mitten in den Datenstrom schreibt und
|
||||||
das genau im LETZTEN Wiederhol-Versuch passierte, blieb dieser Müll in der Datei und der anschließende
|
das genau im LETZTEN Wiederhol-Versuch passierte, blieb dieser Müll in der Datei und der anschließende
|
||||||
|
|||||||
@ -1961,43 +1961,6 @@ describe("debrid service", () => {
|
|||||||
expect(result.directUrl).toBe("https://mega-web.example/ok.rar");
|
expect(result.directUrl).toBe("https://mega-web.example/ok.rar");
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("setzt KEINEN Account-Cooldown, wenn der Mega-Web-Abbruch nur ein Queue-Timeout war (Account war belegt, nicht ungesund)", async () => {
|
|
||||||
process.env.RD_MEGA_ABORT_MIN_RUN_MS = "0";
|
|
||||||
const settings = {
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "",
|
|
||||||
bestToken: "",
|
|
||||||
allDebridToken: "",
|
|
||||||
megaLogin: "user",
|
|
||||||
megaPassword: "pass",
|
|
||||||
megaCredentials: "user:pass",
|
|
||||||
megaDebridPreferApi: false,
|
|
||||||
providerOrder: [] as const,
|
|
||||||
providerPrimary: "megadebrid" as const,
|
|
||||||
providerSecondary: "none" as const,
|
|
||||||
providerTertiary: "none" as const,
|
|
||||||
autoProviderFallback: false
|
|
||||||
};
|
|
||||||
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
let calls = 0;
|
|
||||||
const megaWeb = vi.fn(async () => {
|
|
||||||
calls += 1;
|
|
||||||
controller.abort("caller-timeout");
|
|
||||||
throw new Error("Mega-Web Queue-Timeout (abgebrochen nach 60s Wartezeit, Account war belegt)");
|
|
||||||
});
|
|
||||||
|
|
||||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
|
||||||
await expect(
|
|
||||||
service.unrestrictLink("https://rapidgator.net/file/queue-timeout-no-cooldown", controller.signal)
|
|
||||||
).rejects.toThrow();
|
|
||||||
|
|
||||||
const key = `${getMegaDebridAccountId("user")}:web`;
|
|
||||||
expect(getMegaDebridAccountCooldownState(key)).toBeNull();
|
|
||||||
expect(calls).toBeGreaterThanOrEqual(1);
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("escalates a Mega-Debrid account to 'until restart' after the empty-response streak threshold", () => {
|
it("escalates a Mega-Debrid account to 'until restart' after the empty-response streak threshold", () => {
|
||||||
const key = `${getMegaDebridAccountId("user1")}:web`;
|
const key = `${getMegaDebridAccountId("user1")}:web`;
|
||||||
expect(MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART).toBe(3);
|
expect(MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART).toBe(3);
|
||||||
|
|||||||
@ -301,57 +301,6 @@ describe("download manager", () => {
|
|||||||
expect(session.items[itemId].retries).toBeLessThan(20);
|
expect(session.items[itemId].retries).toBeLessThan(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefers the short Mega cooldown over the until-reset park when an aggregate error carries both tokens", async () => {
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-mega-both-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
const storagePaths = createStoragePaths(path.join(root, "state"));
|
|
||||||
initPackageLogs(storagePaths.baseDir);
|
|
||||||
initItemLogs(storagePaths.baseDir);
|
|
||||||
|
|
||||||
const session = emptySession();
|
|
||||||
const packageId = "mega-both-pkg";
|
|
||||||
const itemId = "mega-both-item";
|
|
||||||
const outputDir = path.join(root, "downloads", "MegaBoth");
|
|
||||||
const extractDir = path.join(root, "extract", "MegaBoth");
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
fs.mkdirSync(extractDir, { recursive: true });
|
|
||||||
session.packageOrder = [packageId];
|
|
||||||
session.packages[packageId] = {
|
|
||||||
id: packageId, name: "MegaBoth", outputDir, extractDir,
|
|
||||||
status: "downloading", itemIds: [itemId], cancelled: false, enabled: true,
|
|
||||||
createdAt: Date.now(), updatedAt: Date.now()
|
|
||||||
} as any;
|
|
||||||
session.items[itemId] = {
|
|
||||||
id: itemId, packageId, url: "https://rapidgator.net/file/mega-both.rar", provider: "megadebrid-api",
|
|
||||||
status: "downloading", retries: 0, speedBps: 0, downloadedBytes: 0, totalBytes: null,
|
|
||||||
progressPercent: 0, fileName: "mega-both.rar", targetPath: "", resumable: true,
|
|
||||||
attempts: 0, lastError: "", fullStatus: "", createdAt: Date.now(), updatedAt: Date.now()
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const manager = new DownloadManager(
|
|
||||||
{ ...defaultSettings(), megaLogin: "u", megaPassword: "p", megaCredentials: "u:p", megaDebridApiEnabled: true, megaDebridWebEnabled: true, outputDir: path.join(root, "downloads"), extractDir: path.join(root, "extract") },
|
|
||||||
session,
|
|
||||||
storagePaths
|
|
||||||
);
|
|
||||||
(manager as any).retryStateByItem.set(itemId, { freshRetryUsed: true, resumeHardResetUsed: true, stallRetries: 0, genericErrorRetries: 0, unrestrictRetries: 0 });
|
|
||||||
(manager as any).debridService.unrestrictLink = async () => {
|
|
||||||
throw new Error("Unrestrict fehlgeschlagen: Mega-Debrid API: mega_debrid_reset_park:86400000:Alle Accounts bis zum Tagesreset gesperrt | Mega-Debrid Web: mega_debrid_cooldown:30000:rate limit");
|
|
||||||
};
|
|
||||||
|
|
||||||
const active = { itemId, packageId, abortController: new AbortController(), abortReason: "none", resumable: true, nonResumableCounted: false, blockedOnDiskWrite: false, blockedOnDiskSince: 0 };
|
|
||||||
(manager as any).activeTasks.set(itemId, active);
|
|
||||||
|
|
||||||
const before = Date.now();
|
|
||||||
await (manager as any).processItem(active);
|
|
||||||
|
|
||||||
const retryAfter = (manager as any).retryAfterByItem.get(itemId) as number;
|
|
||||||
expect(retryAfter).toBeGreaterThan(0);
|
|
||||||
const delayMs = retryAfter - before;
|
|
||||||
expect(delayMs).toBeLessThan(60000);
|
|
||||||
expect(session.items[itemId].fullStatus).toMatch(/Cooldown/i);
|
|
||||||
expect(session.items[itemId].fullStatus).not.toMatch(/Tagesreset/i);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps the quick post-process requeue once the final package items are finished", () => {
|
it("keeps the quick post-process requeue once the final package items are finished", () => {
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-postprocess-final-"));
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-postprocess-final-"));
|
||||||
tempDirs.push(root);
|
tempDirs.push(root);
|
||||||
|
|||||||
@ -314,41 +314,5 @@ describe("mega-web-fallback", () => {
|
|||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("klassifiziert einen Abbruch WAEHREND in der Queue als Queue-Timeout (nicht harter Abbruch), damit der belegte Account nicht bestraft wird", async () => {
|
|
||||||
let releaseLogin: () => void = () => {};
|
|
||||||
const loginGate = new Promise<void>((resolve) => { releaseLogin = resolve; });
|
|
||||||
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
|
|
||||||
const u = String(url);
|
|
||||||
if (u.includes("form=login")) {
|
|
||||||
await loginGate;
|
|
||||||
const headers = new Headers();
|
|
||||||
headers.append("set-cookie", "session=c; path=/");
|
|
||||||
return new Response("", { headers, status: 200 });
|
|
||||||
}
|
|
||||||
if (u.includes("page=debrideur")) return new Response('<form id="debridForm"></form>', { status: 200 });
|
|
||||||
if (u.includes("form=debrid")) return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l</h3><a href="javascript:processDebrid(1,'code',0)">d</a></div>`, { status: 200 });
|
|
||||||
if (u.includes("ajax=debrid")) return new Response(JSON.stringify({ link: "https://mega.direct/ok" }), { status: 200 });
|
|
||||||
return new Response("Not found", { status: 404 });
|
|
||||||
}) as unknown as typeof fetch;
|
|
||||||
|
|
||||||
const fallback = new MegaWebFallback(() => ({ login: "same", password: "pw" }));
|
|
||||||
const firstCtrl = new AbortController();
|
|
||||||
const first = fallback.unrestrict("https://mega.debrid/a", firstCtrl.signal, { login: "same", password: "pw" });
|
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
|
||||||
|
|
||||||
const secondCtrl = new AbortController();
|
|
||||||
const second = fallback.unrestrict("https://mega.debrid/b", secondCtrl.signal, { login: "same", password: "pw" });
|
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
|
||||||
secondCtrl.abort("caller-timeout");
|
|
||||||
|
|
||||||
await expect(second).rejects.toThrow(/queue.?timeout/i);
|
|
||||||
|
|
||||||
releaseLogin();
|
|
||||||
await first.catch(() => null);
|
|
||||||
await new Promise((r) => setTimeout(r, 20));
|
|
||||||
}, 10000);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -180,67 +180,6 @@ describe("settings storage", () => {
|
|||||||
expect(webNormalized.hosterRouting.rapidgator).toBe("megadebrid-web");
|
expect(webNormalized.hosterRouting.rapidgator).toBe("megadebrid-web");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migriert eine pre-v1.6.90-Config (Mega-Creds, beide Enable-Flags fehlen) zu aktiviertem Mega-Debrid statt es still auf false zu setzen", () => {
|
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
|
|
||||||
tempDirs.push(dir);
|
|
||||||
const paths = createStoragePaths(dir);
|
|
||||||
const legacyApi = {
|
|
||||||
megaLogin: "mega-user",
|
|
||||||
megaPassword: "mega-pass",
|
|
||||||
megaDebridPreferApi: true,
|
|
||||||
providerPrimary: "realdebrid",
|
|
||||||
providerSecondary: "megadebrid"
|
|
||||||
};
|
|
||||||
fs.writeFileSync(paths.configFile, JSON.stringify(legacyApi), "utf8");
|
|
||||||
const loadedApi = loadSettings(paths);
|
|
||||||
expect(loadedApi.megaDebridApiEnabled).toBe(true);
|
|
||||||
expect(loadedApi.megaDebridWebEnabled).toBe(false);
|
|
||||||
|
|
||||||
const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
|
|
||||||
tempDirs.push(dir2);
|
|
||||||
const paths2 = createStoragePaths(dir2);
|
|
||||||
const legacyWeb = {
|
|
||||||
megaLogin: "mega-user",
|
|
||||||
megaPassword: "mega-pass",
|
|
||||||
megaDebridPreferApi: false,
|
|
||||||
providerPrimary: "realdebrid",
|
|
||||||
providerSecondary: "megadebrid"
|
|
||||||
};
|
|
||||||
fs.writeFileSync(paths2.configFile, JSON.stringify(legacyWeb), "utf8");
|
|
||||||
const loadedWeb = loadSettings(paths2);
|
|
||||||
expect(loadedWeb.megaDebridApiEnabled).toBe(false);
|
|
||||||
expect(loadedWeb.megaDebridWebEnabled).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("re-aktiviert KEINE bewusst deaktivierten Mega-Flags und migriert nicht ohne Mega-Creds", () => {
|
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
|
|
||||||
tempDirs.push(dir);
|
|
||||||
const paths = createStoragePaths(dir);
|
|
||||||
const deliberatelyDisabled = {
|
|
||||||
megaLogin: "mega-user",
|
|
||||||
megaPassword: "mega-pass",
|
|
||||||
megaDebridPreferApi: true,
|
|
||||||
megaDebridApiEnabled: false,
|
|
||||||
megaDebridWebEnabled: false
|
|
||||||
};
|
|
||||||
fs.writeFileSync(paths.configFile, JSON.stringify(deliberatelyDisabled), "utf8");
|
|
||||||
const loaded = loadSettings(paths);
|
|
||||||
expect(loaded.megaDebridApiEnabled).toBe(false);
|
|
||||||
expect(loaded.megaDebridWebEnabled).toBe(false);
|
|
||||||
|
|
||||||
const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
|
|
||||||
tempDirs.push(dir2);
|
|
||||||
const paths2 = createStoragePaths(dir2);
|
|
||||||
const noCreds = {
|
|
||||||
megaDebridPreferApi: true,
|
|
||||||
providerPrimary: "realdebrid"
|
|
||||||
};
|
|
||||||
fs.writeFileSync(paths2.configFile, JSON.stringify(noCreds), "utf8");
|
|
||||||
const loadedNoCreds = loadSettings(paths2);
|
|
||||||
expect(loadedNoCreds.megaDebridApiEnabled).toBe(false);
|
|
||||||
expect(loadedNoCreds.megaDebridWebEnabled).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("normalizes provider daily limits and resets stale daily usage", () => {
|
it("normalizes provider daily limits and resets stale daily usage", () => {
|
||||||
const [debridLinkKey] = parseDebridLinkApiKeys("dl-key-one");
|
const [debridLinkKey] = parseDebridLinkApiKeys("dl-key-one");
|
||||||
const normalized = normalizeSettings({
|
const normalized = normalizeSettings({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user