Release v1.6.28
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a00304a93
commit
20a0a59670
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.27",
|
"version": "1.6.28",
|
||||||
"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",
|
||||||
|
|||||||
@ -1051,7 +1051,11 @@ export class DebridService {
|
|||||||
providerLabel: PROVIDER_LABELS[primary]
|
providerLabel: PROVIDER_LABELS[primary]
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Unrestrict fehlgeschlagen: ${PROVIDER_LABELS[primary]}: ${compactErrorText(error)}`);
|
const errorText = compactErrorText(error);
|
||||||
|
if (signal?.aborted || (/aborted/i.test(errorText) && !/timeout/i.test(errorText))) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new Error(`Unrestrict fehlgeschlagen: ${PROVIDER_LABELS[primary]}: ${errorText}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1103,7 +1103,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (pkg) {
|
if (pkg) {
|
||||||
pkg.itemIds = pkg.itemIds.filter((id) => id !== itemId);
|
pkg.itemIds = pkg.itemIds.filter((id) => id !== itemId);
|
||||||
if (pkg.itemIds.length === 0) {
|
if (pkg.itemIds.length === 0) {
|
||||||
this.removePackageFromSession(item.packageId, []);
|
this.removePackageFromSession(item.packageId, [itemId]);
|
||||||
} else {
|
} else {
|
||||||
pkg.updatedAt = nowMs();
|
pkg.updatedAt = nowMs();
|
||||||
}
|
}
|
||||||
@ -2659,6 +2659,17 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
// Reset parent package status if it was completed/failed (now has queued items again)
|
// Reset parent package status if it was completed/failed (now has queued items again)
|
||||||
for (const pkgId of affectedPackageIds) {
|
for (const pkgId of affectedPackageIds) {
|
||||||
|
// Abort active post-processing for this package
|
||||||
|
const postProcessController = this.packagePostProcessAbortControllers.get(pkgId);
|
||||||
|
if (postProcessController && !postProcessController.signal.aborted) {
|
||||||
|
postProcessController.abort("reset");
|
||||||
|
}
|
||||||
|
this.packagePostProcessAbortControllers.delete(pkgId);
|
||||||
|
this.packagePostProcessTasks.delete(pkgId);
|
||||||
|
this.hybridExtractRequeue.delete(pkgId);
|
||||||
|
this.runCompletedPackages.delete(pkgId);
|
||||||
|
this.historyRecordedPackages.delete(pkgId);
|
||||||
|
|
||||||
const pkg = this.session.packages[pkgId];
|
const pkg = this.session.packages[pkgId];
|
||||||
if (pkg && (pkg.status === "completed" || pkg.status === "failed" || pkg.status === "cancelled")) {
|
if (pkg && (pkg.status === "completed" || pkg.status === "failed" || pkg.status === "cancelled")) {
|
||||||
pkg.status = "queued";
|
pkg.status = "queued";
|
||||||
@ -3822,7 +3833,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.autoExtract && failed === 0 && cancelled === 0 && success > 0) {
|
if (this.settings.autoExtract && failed === 0 && success > 0) {
|
||||||
const needsExtraction = items.some((item) => item.status === "completed" && !isExtractedLabel(item.fullStatus));
|
const needsExtraction = items.some((item) => item.status === "completed" && !isExtractedLabel(item.fullStatus));
|
||||||
if (needsExtraction) {
|
if (needsExtraction) {
|
||||||
pkg.status = "queued";
|
pkg.status = "queued";
|
||||||
@ -3883,7 +3894,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const allDone = success + failed + cancelled >= items.length;
|
const allDone = success + failed + cancelled >= items.length;
|
||||||
|
|
||||||
// Full extraction: all items done, no failures
|
// Full extraction: all items done, no failures
|
||||||
if (allDone && failed === 0 && cancelled === 0 && success > 0) {
|
if (allDone && failed === 0 && success > 0) {
|
||||||
const needsExtraction = items.some((item) =>
|
const needsExtraction = items.some((item) =>
|
||||||
item.status === "completed" && !isExtractedLabel(item.fullStatus)
|
item.status === "completed" && !isExtractedLabel(item.fullStatus)
|
||||||
);
|
);
|
||||||
@ -4944,22 +4955,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shelve check for non-stall errors
|
|
||||||
const totalNonStallFailures = (active.stallRetries || 0) + (active.unrestrictRetries || 0) + (active.genericErrorRetries || 0);
|
|
||||||
if (totalNonStallFailures >= 15) {
|
|
||||||
item.retries += 1;
|
|
||||||
active.stallRetries = Math.floor((active.stallRetries || 0) / 2);
|
|
||||||
active.unrestrictRetries = Math.floor((active.unrestrictRetries || 0) / 2);
|
|
||||||
active.genericErrorRetries = Math.floor((active.genericErrorRetries || 0) / 2);
|
|
||||||
logger.warn(`Item shelved (error path): ${item.fileName || item.id}, totalFailures=${totalNonStallFailures}, error=${errorText}`);
|
|
||||||
this.queueRetry(item, active, 300000, `Viele Fehler (${totalNonStallFailures}x), Pause 5 min`);
|
|
||||||
item.lastError = errorText;
|
|
||||||
this.persistSoon();
|
|
||||||
this.emitState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permanent link errors (dead link, file removed, hoster unavailable) → fail immediately
|
// Permanent link errors (dead link, file removed, hoster unavailable) → fail immediately
|
||||||
|
// Check BEFORE shelve to avoid 5-min pause on dead links
|
||||||
if (isPermanentLinkError(errorText)) {
|
if (isPermanentLinkError(errorText)) {
|
||||||
logger.error(`Link permanent ungültig: item=${item.fileName || item.id}, error=${errorText}, link=${item.url.slice(0, 80)}`);
|
logger.error(`Link permanent ungültig: item=${item.fileName || item.id}, error=${errorText}, link=${item.url.slice(0, 80)}`);
|
||||||
item.status = "failed";
|
item.status = "failed";
|
||||||
@ -4974,6 +4971,21 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shelve check for non-stall errors (after permanent link error check)
|
||||||
|
const totalNonStallFailures = (active.stallRetries || 0) + (active.unrestrictRetries || 0) + (active.genericErrorRetries || 0);
|
||||||
|
if (totalNonStallFailures >= 15) {
|
||||||
|
item.retries += 1;
|
||||||
|
active.stallRetries = Math.floor((active.stallRetries || 0) / 2);
|
||||||
|
active.unrestrictRetries = Math.floor((active.unrestrictRetries || 0) / 2);
|
||||||
|
active.genericErrorRetries = Math.floor((active.genericErrorRetries || 0) / 2);
|
||||||
|
logger.warn(`Item shelved (error path): ${item.fileName || item.id}, totalFailures=${totalNonStallFailures}, error=${errorText}`);
|
||||||
|
this.queueRetry(item, active, 300000, `Viele Fehler (${totalNonStallFailures}x), Pause 5 min`);
|
||||||
|
item.lastError = errorText;
|
||||||
|
this.persistSoon();
|
||||||
|
this.emitState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isUnrestrictFailure(errorText) && active.unrestrictRetries < maxUnrestrictRetries) {
|
if (isUnrestrictFailure(errorText) && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||||
active.unrestrictRetries += 1;
|
active.unrestrictRetries += 1;
|
||||||
item.retries += 1;
|
item.retries += 1;
|
||||||
@ -5031,6 +5043,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
item.speedBps = 0;
|
item.speedBps = 0;
|
||||||
item.updatedAt = nowMs();
|
item.updatedAt = nowMs();
|
||||||
|
// Refresh package status so it reflects "failed" when all items are done
|
||||||
|
const failPkg = this.session.packages[item.packageId];
|
||||||
|
if (failPkg) this.refreshPackageStatus(failPkg);
|
||||||
this.persistSoon();
|
this.persistSoon();
|
||||||
this.emitState();
|
this.emitState();
|
||||||
return;
|
return;
|
||||||
@ -6379,7 +6394,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const status = entry.fullStatus || "";
|
const status = entry.fullStatus || "";
|
||||||
if (/^Entpacken\b/i.test(status)) {
|
if (/^Entpacken\b/i.test(status) || /^Passwort\b/i.test(status)) {
|
||||||
if (result.failed > 0) {
|
if (result.failed > 0) {
|
||||||
entry.fullStatus = "Entpacken - Error";
|
entry.fullStatus = "Entpacken - Error";
|
||||||
} else if (result.extracted > 0) {
|
} else if (result.extracted > 0) {
|
||||||
@ -6399,7 +6414,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
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 || "") || 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 - Ausstehend" || entry.fullStatus === "Entpacken - Warten auf Parts") {
|
||||||
entry.fullStatus = `Entpacken - Error`;
|
entry.fullStatus = `Entpacken - Error`;
|
||||||
entry.updatedAt = errorAt;
|
entry.updatedAt = errorAt;
|
||||||
}
|
}
|
||||||
@ -6708,7 +6723,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
timeoutHandled = true;
|
timeoutHandled = true;
|
||||||
} else {
|
} else {
|
||||||
for (const entry of completedItems) {
|
for (const entry of completedItems) {
|
||||||
if (/^Entpacken/i.test(entry.fullStatus || "")) {
|
if (/^Entpacken/i.test(entry.fullStatus || "") || /^Passwort/i.test(entry.fullStatus || "")) {
|
||||||
entry.fullStatus = "Entpacken abgebrochen (wird fortgesetzt)";
|
entry.fullStatus = "Entpacken abgebrochen (wird fortgesetzt)";
|
||||||
}
|
}
|
||||||
entry.updatedAt = nowMs();
|
entry.updatedAt = nowMs();
|
||||||
@ -6879,6 +6894,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
private finishRun(): void {
|
private finishRun(): void {
|
||||||
this.session.running = false;
|
this.session.running = false;
|
||||||
this.session.paused = false;
|
this.session.paused = false;
|
||||||
|
this.session.runStartedAt = 0;
|
||||||
const total = this.runItemIds.size;
|
const total = this.runItemIds.size;
|
||||||
const outcomes = Array.from(this.runOutcomes.values());
|
const outcomes = Array.from(this.runOutcomes.values());
|
||||||
const success = outcomes.filter((status) => status === "completed").length;
|
const success = outcomes.filter((status) => status === "completed").length;
|
||||||
|
|||||||
@ -2006,6 +2006,7 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
|||||||
extracted += 1;
|
extracted += 1;
|
||||||
resumeCompleted.add(archiveResumeKey);
|
resumeCompleted.add(archiveResumeKey);
|
||||||
extractedArchives.add(archivePath);
|
extractedArchives.add(archivePath);
|
||||||
|
await writeExtractResumeState(options.packageDir, resumeCompleted, options.packageId);
|
||||||
clearInterval(pulseTimer);
|
clearInterval(pulseTimer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -295,7 +295,7 @@ function registerIpcHandlers(): void {
|
|||||||
});
|
});
|
||||||
ipcMain.handle(IPC_CHANNELS.START_ITEMS, (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
ipcMain.handle(IPC_CHANNELS.START_ITEMS, (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||||
validateStringArray(itemIds ?? [], "itemIds");
|
validateStringArray(itemIds ?? [], "itemIds");
|
||||||
return controller.startItems(itemIds);
|
return controller.startItems(itemIds ?? []);
|
||||||
});
|
});
|
||||||
ipcMain.handle(IPC_CHANNELS.STOP, () => controller.stop());
|
ipcMain.handle(IPC_CHANNELS.STOP, () => controller.stop());
|
||||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PAUSE, () => controller.togglePause());
|
ipcMain.handle(IPC_CHANNELS.TOGGLE_PAUSE, () => controller.togglePause());
|
||||||
|
|||||||
@ -360,8 +360,8 @@ function sortPackageOrderBySize(order: string[], packages: Record<string, Packag
|
|||||||
function sortPackageOrderByHoster(order: string[], packages: Record<string, PackageEntry>, items: Record<string, DownloadItem>, descending: boolean): string[] {
|
function sortPackageOrderByHoster(order: string[], packages: Record<string, PackageEntry>, items: Record<string, DownloadItem>, descending: boolean): string[] {
|
||||||
const sorted = [...order];
|
const sorted = [...order];
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
const hosterA = [...new Set((packages[a]?.itemIds ?? []).map((id) => items[id]?.provider).filter(Boolean))].join(",").toLowerCase();
|
const hosterA = [...new Set((packages[a]?.itemIds ?? []).map((id) => extractHoster(items[id]?.url ?? "")).filter(Boolean))].join(",").toLowerCase();
|
||||||
const hosterB = [...new Set((packages[b]?.itemIds ?? []).map((id) => items[id]?.provider).filter(Boolean))].join(",").toLowerCase();
|
const hosterB = [...new Set((packages[b]?.itemIds ?? []).map((id) => extractHoster(items[id]?.url ?? "")).filter(Boolean))].join(",").toLowerCase();
|
||||||
const cmp = hosterA.localeCompare(hosterB);
|
const cmp = hosterA.localeCompare(hosterB);
|
||||||
return descending ? -cmp : cmp;
|
return descending ? -cmp : cmp;
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user