Release v1.6.16
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56c0b633c8
commit
b02aef2af9
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.6.15",
|
||||
"version": "1.6.16",
|
||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -81,8 +81,15 @@ export class AppController {
|
||||
void this.manager.getStartConflicts().then((conflicts) => {
|
||||
const hasConflicts = conflicts.length > 0;
|
||||
if (this.hasAnyProviderToken(this.settings) && !hasConflicts) {
|
||||
// If the onState handler is already set (renderer connected), start immediately.
|
||||
// Otherwise mark as pending so the onState setter triggers the start.
|
||||
if (this.onStateHandler) {
|
||||
logger.info("Auto-Resume beim Start aktiviert (nach Konflikt-Check)");
|
||||
void this.manager.start().catch((err) => logger.warn(`Auto-Resume Start Fehler: ${String(err)}`));
|
||||
} else {
|
||||
this.autoResumePending = true;
|
||||
logger.info("Auto-Resume beim Start vorgemerkt");
|
||||
}
|
||||
} else if (hasConflicts) {
|
||||
logger.info("Auto-Resume übersprungen: Start-Konflikte erkannt");
|
||||
}
|
||||
@ -299,6 +306,9 @@ export class AppController {
|
||||
// so no extraction tasks from it should keep running.
|
||||
this.manager.stop();
|
||||
this.manager.abortAllPostProcessing();
|
||||
// Cancel any deferred persist timer so the old in-memory session
|
||||
// does not overwrite the restored session file on disk.
|
||||
this.manager.clearPersistTimer();
|
||||
const restoredSession = parsed.session as ReturnType<typeof loadSession>;
|
||||
saveSession(this.storagePaths, restoredSession);
|
||||
return { restored: true, message: "Backup wiederhergestellt. Bitte App neustarten." };
|
||||
|
||||
@ -2527,6 +2527,7 @@ export class DownloadManager extends EventEmitter {
|
||||
pkg.cancelled = false;
|
||||
pkg.enabled = true;
|
||||
pkg.updatedAt = nowMs();
|
||||
this.historyRecordedPackages.delete(packageId);
|
||||
|
||||
logger.info(`Paket "${pkg.name}" zurückgesetzt (${itemIds.length} Items)`);
|
||||
this.persistSoon();
|
||||
@ -3302,7 +3303,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
private clearPersistTimer(): void {
|
||||
public clearPersistTimer(): void {
|
||||
if (!this.persistTimer) {
|
||||
return;
|
||||
}
|
||||
@ -3557,8 +3558,12 @@ export class DownloadManager extends EventEmitter {
|
||||
await new Promise<void>((resolve) => {
|
||||
this.packagePostProcessWaiters.push({ packageId, resolve });
|
||||
});
|
||||
// Guard: stop() may have reset the counter to 0 while we were waiting.
|
||||
// Only increment if below max to avoid phantom slot usage.
|
||||
if (this.packagePostProcessActive < maxConcurrent) {
|
||||
this.packagePostProcessActive += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private releasePostProcessSlot(): void {
|
||||
// Guard: stop() resets active to 0, but old tasks (aborted waiters) still
|
||||
@ -6715,9 +6720,14 @@ export class DownloadManager extends EventEmitter {
|
||||
};
|
||||
this.session.summaryText = `Summary: Dauer ${duration}s, Ø Speed ${humanSize(avgSpeed)}/s, Erfolg ${success}/${total}`;
|
||||
this.runItemIds.clear();
|
||||
this.runPackageIds.clear();
|
||||
this.runOutcomes.clear();
|
||||
// Keep runPackageIds and runCompletedPackages alive when post-processing tasks
|
||||
// are still running (autoExtractWhenStopped) so handlePackagePostProcessing()
|
||||
// can still update runCompletedPackages. They are cleared by the next start().
|
||||
if (this.packagePostProcessTasks.size === 0) {
|
||||
this.runPackageIds.clear();
|
||||
this.runCompletedPackages.clear();
|
||||
}
|
||||
this.retryAfterByItem.clear();
|
||||
this.retryStateByItem.clear();
|
||||
this.reservedTargetPaths.clear();
|
||||
|
||||
@ -50,6 +50,7 @@ process.on("unhandledRejection", (reason) => {
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let tray: Tray | null = null;
|
||||
let clipboardTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let updateQuitTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastClipboardText = "";
|
||||
const controller = new AppController();
|
||||
const CLIPBOARD_MAX_TEXT_CHARS = 50_000;
|
||||
@ -236,7 +237,7 @@ function registerIpcHandlers(): void {
|
||||
mainWindow.webContents.send(IPC_CHANNELS.UPDATE_INSTALL_PROGRESS, progress);
|
||||
});
|
||||
if (result.started) {
|
||||
setTimeout(() => {
|
||||
updateQuitTimer = setTimeout(() => {
|
||||
app.quit();
|
||||
}, 2500);
|
||||
}
|
||||
@ -289,8 +290,8 @@ function registerIpcHandlers(): void {
|
||||
ipcMain.handle(IPC_CHANNELS.CLEAR_ALL, () => controller.clearAll());
|
||||
ipcMain.handle(IPC_CHANNELS.START, () => controller.start());
|
||||
ipcMain.handle(IPC_CHANNELS.START_PACKAGES, (_event: IpcMainInvokeEvent, packageIds: string[]) => {
|
||||
if (!Array.isArray(packageIds)) throw new Error("packageIds muss ein Array sein");
|
||||
return controller.startPackages(packageIds);
|
||||
validateStringArray(packageIds ?? [], "packageIds");
|
||||
return controller.startPackages(packageIds ?? []);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.START_ITEMS, (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||
validateStringArray(itemIds ?? [], "itemIds");
|
||||
@ -337,15 +338,18 @@ function registerIpcHandlers(): void {
|
||||
ipcMain.handle(IPC_CHANNELS.SET_PACKAGE_PRIORITY, (_event: IpcMainInvokeEvent, packageId: string, priority: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
validateString(priority, "priority");
|
||||
return controller.setPackagePriority(packageId, priority as any);
|
||||
if (priority !== "high" && priority !== "normal" && priority !== "low") {
|
||||
throw new Error("priority muss 'high', 'normal' oder 'low' sein");
|
||||
}
|
||||
return controller.setPackagePriority(packageId, priority);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.SKIP_ITEMS, (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||
if (!Array.isArray(itemIds)) throw new Error("itemIds must be an array");
|
||||
return controller.skipItems(itemIds);
|
||||
validateStringArray(itemIds ?? [], "itemIds");
|
||||
return controller.skipItems(itemIds ?? []);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.RESET_ITEMS, (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||
if (!Array.isArray(itemIds)) throw new Error("itemIds must be an array");
|
||||
return controller.resetItems(itemIds);
|
||||
validateStringArray(itemIds ?? [], "itemIds");
|
||||
return controller.resetItems(itemIds ?? []);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.GET_HISTORY, () => controller.getHistory());
|
||||
ipcMain.handle(IPC_CHANNELS.CLEAR_HISTORY, () => controller.clearHistory());
|
||||
@ -493,6 +497,7 @@ app.on("window-all-closed", () => {
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
if (updateQuitTimer) { clearTimeout(updateQuitTimer); updateQuitTimer = null; }
|
||||
stopClipboardWatcher();
|
||||
destroyTray();
|
||||
try {
|
||||
|
||||
@ -2935,7 +2935,7 @@ export function App(): ReactElement {
|
||||
{(hasPackages || hasStartableItems) && (
|
||||
<button className="ctx-menu-item" onClick={() => {
|
||||
const pkgIds = [...selectedIds].filter((id) => snapshot.session.packages[id]);
|
||||
const itemIds = [...selectedIds].filter((id) => snapshot.session.items[id]);
|
||||
const itemIds = [...selectedIds].filter((id) => { const it = snapshot.session.items[id]; return it && startableStatuses.has(it.status); });
|
||||
if (pkgIds.length > 0) void window.rd.startPackages(pkgIds);
|
||||
if (itemIds.length > 0) void window.rd.startItems(itemIds);
|
||||
setContextMenu(null);
|
||||
@ -3071,9 +3071,9 @@ export function App(): ReactElement {
|
||||
const contextEntry = historyEntries.find(e => e.id === historyCtxMenu.entryId);
|
||||
const hasUrls = (contextEntry?.urls?.length ?? 0) > 0;
|
||||
const removeSelected = (): void => {
|
||||
const ids = [...selectedHistoryIds];
|
||||
void Promise.all(ids.map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
||||
setHistoryEntries((prev) => prev.filter((e) => !selectedHistoryIds.has(e.id)));
|
||||
const idSet = new Set(selectedHistoryIds);
|
||||
void Promise.all([...idSet].map(id => window.rd.removeHistoryEntry(id))).then(() => {
|
||||
setHistoryEntries((prev) => prev.filter((e) => !idSet.has(e.id)));
|
||||
setSelectedHistoryIds(new Set());
|
||||
}).catch(() => {
|
||||
void window.rd.getHistory().then((entries) => { setHistoryEntries(entries); setSelectedHistoryIds(new Set()); }).catch(() => {});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user