Release v1.6.29
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
20a0a59670
commit
1d0ee31001
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.6.28",
|
||||
"version": "1.6.29",
|
||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -314,6 +314,9 @@ export class AppController {
|
||||
normalizeLoadedSession(parsed.session)
|
||||
);
|
||||
saveSession(this.storagePaths, restoredSession);
|
||||
// Prevent prepareForShutdown from overwriting the restored session file
|
||||
// with the old in-memory session when the app quits after backup restore.
|
||||
this.manager.skipShutdownPersist = true;
|
||||
return { restored: true, message: "Backup wiederhergestellt. Bitte App neustarten." };
|
||||
}
|
||||
|
||||
|
||||
@ -801,6 +801,8 @@ export class DownloadManager extends EventEmitter {
|
||||
|
||||
private storagePaths: StoragePaths;
|
||||
|
||||
public skipShutdownPersist = false;
|
||||
|
||||
private debridService: DebridService;
|
||||
|
||||
private invalidateMegaSessionFn?: () => void;
|
||||
@ -2390,7 +2392,8 @@ export class DownloadManager extends EventEmitter {
|
||||
const baseName = sanitizeFilename(parsed.name || "video");
|
||||
|
||||
let index = 1;
|
||||
while (true) {
|
||||
const MAX_ATTEMPTS = 10000;
|
||||
while (index <= MAX_ATTEMPTS) {
|
||||
const candidateName = index <= 1
|
||||
? `${baseName}${extension}`
|
||||
: `${baseName} (${index})${extension}`;
|
||||
@ -2406,6 +2409,11 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
// Fallback: use timestamp-based name to guarantee termination
|
||||
const fallbackName = `${baseName} (${Date.now()})${extension}`;
|
||||
const fallbackPath = path.join(targetDir, fallbackName);
|
||||
reserved.add(pathKey(fallbackPath));
|
||||
return fallbackPath;
|
||||
}
|
||||
|
||||
private async collectMkvFilesToLibrary(packageId: string, pkg: PackageEntry): Promise<void> {
|
||||
@ -2821,6 +2829,9 @@ export class DownloadManager extends EventEmitter {
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
this.retryStateByItem.clear();
|
||||
this.itemContributedBytes.clear();
|
||||
this.reservedTargetPaths.clear();
|
||||
this.claimedTargetPathByItem.clear();
|
||||
this.session.running = true;
|
||||
this.session.paused = false;
|
||||
this.session.runStartedAt = nowMs();
|
||||
@ -3047,6 +3058,9 @@ export class DownloadManager extends EventEmitter {
|
||||
this.runCompletedPackages.clear();
|
||||
this.retryAfterByItem.clear();
|
||||
this.retryStateByItem.clear();
|
||||
this.itemContributedBytes.clear();
|
||||
this.reservedTargetPaths.clear();
|
||||
this.claimedTargetPathByItem.clear();
|
||||
|
||||
this.session.running = true;
|
||||
this.session.paused = false;
|
||||
@ -3195,8 +3209,11 @@ export class DownloadManager extends EventEmitter {
|
||||
this.nonResumableActive = 0;
|
||||
this.session.summaryText = "";
|
||||
// Persist synchronously on shutdown to guarantee data is written before process exits
|
||||
// Skip if a backup was just imported — the restored session on disk must not be overwritten
|
||||
if (!this.skipShutdownPersist) {
|
||||
saveSession(this.storagePaths, this.session);
|
||||
saveSettings(this.storagePaths, this.settings);
|
||||
}
|
||||
this.emitState(true);
|
||||
logger.info(`Shutdown-Vorbereitung beendet: requeued=${requeuedItems}`);
|
||||
}
|
||||
@ -6056,9 +6073,17 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
if (item.status === "completed" && item.targetPath) {
|
||||
completedPaths.add(pathKey(item.targetPath));
|
||||
} else if (item.targetPath) {
|
||||
} else {
|
||||
if (item.targetPath) {
|
||||
pendingPaths.add(pathKey(item.targetPath));
|
||||
}
|
||||
// Items that haven't started yet have no targetPath but may have a fileName.
|
||||
// Include their projected path so the archive-readiness check doesn't
|
||||
// prematurely trigger extraction while parts are still queued.
|
||||
if (item.fileName && pkg.outputDir) {
|
||||
pendingPaths.add(pathKey(path.join(pkg.outputDir, item.fileName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (completedPaths.size === 0) {
|
||||
return ready;
|
||||
@ -6408,13 +6433,21 @@ export class DownloadManager extends EventEmitter {
|
||||
const errorText = String(error || "");
|
||||
if (errorText.includes("aborted:extract")) {
|
||||
logger.info(`Hybrid-Extract abgebrochen: pkg=${pkg.name}`);
|
||||
const abortAt = nowMs();
|
||||
for (const entry of hybridItems) {
|
||||
if (isExtractedLabel(entry.fullStatus || "")) continue;
|
||||
if (/^Entpacken\b/i.test(entry.fullStatus || "") || /^Passwort\b/i.test(entry.fullStatus || "")) {
|
||||
entry.fullStatus = "Entpacken abgebrochen (wird fortgesetzt)";
|
||||
entry.updatedAt = abortAt;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger.warn(`Hybrid-Extract Fehler: pkg=${pkg.name}, reason=${compactErrorText(error)}`);
|
||||
const errorAt = nowMs();
|
||||
for (const entry of hybridItems) {
|
||||
if (isExtractedLabel(entry.fullStatus || "")) continue;
|
||||
if (/^Entpacken\b/i.test(entry.fullStatus || "") || /^Passwort\b/i.test(entry.fullStatus || "") || entry.fullStatus === "Entpacken - Ausstehend" || entry.fullStatus === "Entpacken - Warten auf Parts") {
|
||||
if (/^Entpacken\b/i.test(entry.fullStatus || "") || /^Passwort\b/i.test(entry.fullStatus || "")) {
|
||||
entry.fullStatus = `Entpacken - Error`;
|
||||
entry.updatedAt = errorAt;
|
||||
}
|
||||
@ -6892,6 +6925,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
private finishRun(): void {
|
||||
const runStartedAt = this.session.runStartedAt;
|
||||
this.session.running = false;
|
||||
this.session.paused = false;
|
||||
this.session.runStartedAt = 0;
|
||||
@ -6901,7 +6935,7 @@ export class DownloadManager extends EventEmitter {
|
||||
const failed = outcomes.filter((status) => status === "failed").length;
|
||||
const cancelled = outcomes.filter((status) => status === "cancelled").length;
|
||||
const extracted = this.runCompletedPackages.size;
|
||||
const duration = this.session.runStartedAt > 0 ? Math.max(1, Math.floor((nowMs() - this.session.runStartedAt) / 1000)) : 1;
|
||||
const duration = runStartedAt > 0 ? Math.max(1, Math.floor((nowMs() - runStartedAt) / 1000)) : 1;
|
||||
const avgSpeed = Math.floor(this.session.totalDownloadedBytes / duration);
|
||||
this.summary = {
|
||||
total,
|
||||
|
||||
@ -1637,6 +1637,8 @@ export function collectArchiveCleanupTargets(sourceArchivePath: string, director
|
||||
if (multipartRar) {
|
||||
const prefix = escapeRegex(multipartRar[1]);
|
||||
addMatching(new RegExp(`^${prefix}\\.part\\d+\\.rar$`, "i"));
|
||||
// RAR5 recovery volumes: prefix.partN.rev AND legacy prefix.rev
|
||||
addMatching(new RegExp(`^${prefix}\\.part\\d+\\.rev$`, "i"));
|
||||
addMatching(new RegExp(`^${prefix}\\.rev$`, "i"));
|
||||
return Array.from(targets);
|
||||
}
|
||||
|
||||
@ -424,6 +424,18 @@ export function normalizeLoadedSessionTransientFields(session: SessionState): Se
|
||||
item.speedBps = 0;
|
||||
}
|
||||
|
||||
// Reset package-level active statuses to queued (mirrors item reset above)
|
||||
const ACTIVE_PKG_STATUSES = new Set(["downloading", "validating", "extracting", "integrity_check", "paused", "reconnect_wait"]);
|
||||
for (const pkg of Object.values(session.packages)) {
|
||||
if (ACTIVE_PKG_STATUSES.has(pkg.status)) {
|
||||
pkg.status = "queued";
|
||||
}
|
||||
}
|
||||
|
||||
// Clear stale session-level running/paused flags
|
||||
session.running = false;
|
||||
session.paused = false;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user