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",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.28",
|
"version": "1.6.29",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -314,6 +314,9 @@ export class AppController {
|
|||||||
normalizeLoadedSession(parsed.session)
|
normalizeLoadedSession(parsed.session)
|
||||||
);
|
);
|
||||||
saveSession(this.storagePaths, restoredSession);
|
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." };
|
return { restored: true, message: "Backup wiederhergestellt. Bitte App neustarten." };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -801,6 +801,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
private storagePaths: StoragePaths;
|
private storagePaths: StoragePaths;
|
||||||
|
|
||||||
|
public skipShutdownPersist = false;
|
||||||
|
|
||||||
private debridService: DebridService;
|
private debridService: DebridService;
|
||||||
|
|
||||||
private invalidateMegaSessionFn?: () => void;
|
private invalidateMegaSessionFn?: () => void;
|
||||||
@ -2390,7 +2392,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const baseName = sanitizeFilename(parsed.name || "video");
|
const baseName = sanitizeFilename(parsed.name || "video");
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
while (true) {
|
const MAX_ATTEMPTS = 10000;
|
||||||
|
while (index <= MAX_ATTEMPTS) {
|
||||||
const candidateName = index <= 1
|
const candidateName = index <= 1
|
||||||
? `${baseName}${extension}`
|
? `${baseName}${extension}`
|
||||||
: `${baseName} (${index})${extension}`;
|
: `${baseName} (${index})${extension}`;
|
||||||
@ -2406,6 +2409,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
index += 1;
|
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> {
|
private async collectMkvFilesToLibrary(packageId: string, pkg: PackageEntry): Promise<void> {
|
||||||
@ -2821,6 +2829,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
this.retryStateByItem.clear();
|
this.retryStateByItem.clear();
|
||||||
|
this.itemContributedBytes.clear();
|
||||||
|
this.reservedTargetPaths.clear();
|
||||||
|
this.claimedTargetPathByItem.clear();
|
||||||
this.session.running = true;
|
this.session.running = true;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
this.session.runStartedAt = nowMs();
|
this.session.runStartedAt = nowMs();
|
||||||
@ -3047,6 +3058,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.runCompletedPackages.clear();
|
this.runCompletedPackages.clear();
|
||||||
this.retryAfterByItem.clear();
|
this.retryAfterByItem.clear();
|
||||||
this.retryStateByItem.clear();
|
this.retryStateByItem.clear();
|
||||||
|
this.itemContributedBytes.clear();
|
||||||
|
this.reservedTargetPaths.clear();
|
||||||
|
this.claimedTargetPathByItem.clear();
|
||||||
|
|
||||||
this.session.running = true;
|
this.session.running = true;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
@ -3195,8 +3209,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.nonResumableActive = 0;
|
this.nonResumableActive = 0;
|
||||||
this.session.summaryText = "";
|
this.session.summaryText = "";
|
||||||
// Persist synchronously on shutdown to guarantee data is written before process exits
|
// Persist synchronously on shutdown to guarantee data is written before process exits
|
||||||
saveSession(this.storagePaths, this.session);
|
// Skip if a backup was just imported — the restored session on disk must not be overwritten
|
||||||
saveSettings(this.storagePaths, this.settings);
|
if (!this.skipShutdownPersist) {
|
||||||
|
saveSession(this.storagePaths, this.session);
|
||||||
|
saveSettings(this.storagePaths, this.settings);
|
||||||
|
}
|
||||||
this.emitState(true);
|
this.emitState(true);
|
||||||
logger.info(`Shutdown-Vorbereitung beendet: requeued=${requeuedItems}`);
|
logger.info(`Shutdown-Vorbereitung beendet: requeued=${requeuedItems}`);
|
||||||
}
|
}
|
||||||
@ -6056,8 +6073,16 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
if (item.status === "completed" && item.targetPath) {
|
if (item.status === "completed" && item.targetPath) {
|
||||||
completedPaths.add(pathKey(item.targetPath));
|
completedPaths.add(pathKey(item.targetPath));
|
||||||
} else if (item.targetPath) {
|
} else {
|
||||||
pendingPaths.add(pathKey(item.targetPath));
|
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) {
|
if (completedPaths.size === 0) {
|
||||||
@ -6408,13 +6433,21 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const errorText = String(error || "");
|
const errorText = String(error || "");
|
||||||
if (errorText.includes("aborted:extract")) {
|
if (errorText.includes("aborted:extract")) {
|
||||||
logger.info(`Hybrid-Extract abgebrochen: pkg=${pkg.name}`);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
logger.warn(`Hybrid-Extract Fehler: pkg=${pkg.name}, reason=${compactErrorText(error)}`);
|
logger.warn(`Hybrid-Extract Fehler: pkg=${pkg.name}, reason=${compactErrorText(error)}`);
|
||||||
const errorAt = nowMs();
|
const errorAt = nowMs();
|
||||||
for (const entry of hybridItems) {
|
for (const entry of hybridItems) {
|
||||||
if (isExtractedLabel(entry.fullStatus || "")) continue;
|
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.fullStatus = `Entpacken - Error`;
|
||||||
entry.updatedAt = errorAt;
|
entry.updatedAt = errorAt;
|
||||||
}
|
}
|
||||||
@ -6892,6 +6925,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private finishRun(): void {
|
private finishRun(): void {
|
||||||
|
const runStartedAt = this.session.runStartedAt;
|
||||||
this.session.running = false;
|
this.session.running = false;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
this.session.runStartedAt = 0;
|
this.session.runStartedAt = 0;
|
||||||
@ -6901,7 +6935,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const failed = outcomes.filter((status) => status === "failed").length;
|
const failed = outcomes.filter((status) => status === "failed").length;
|
||||||
const cancelled = outcomes.filter((status) => status === "cancelled").length;
|
const cancelled = outcomes.filter((status) => status === "cancelled").length;
|
||||||
const extracted = this.runCompletedPackages.size;
|
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);
|
const avgSpeed = Math.floor(this.session.totalDownloadedBytes / duration);
|
||||||
this.summary = {
|
this.summary = {
|
||||||
total,
|
total,
|
||||||
|
|||||||
@ -1637,6 +1637,8 @@ export function collectArchiveCleanupTargets(sourceArchivePath: string, director
|
|||||||
if (multipartRar) {
|
if (multipartRar) {
|
||||||
const prefix = escapeRegex(multipartRar[1]);
|
const prefix = escapeRegex(multipartRar[1]);
|
||||||
addMatching(new RegExp(`^${prefix}\\.part\\d+\\.rar$`, "i"));
|
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"));
|
addMatching(new RegExp(`^${prefix}\\.rev$`, "i"));
|
||||||
return Array.from(targets);
|
return Array.from(targets);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -424,6 +424,18 @@ export function normalizeLoadedSessionTransientFields(session: SessionState): Se
|
|||||||
item.speedBps = 0;
|
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;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user