Prevent queue loss during app updates
- Increase quit timeout from 900ms to 5000ms to ensure pending saves complete - Add persistNowSync() called before update install to flush queue to disk - Remove blockAllPersistence from shutdown save condition — shutdown must always persist to prevent data loss across restarts - Add temp file recovery as last resort when both primary and backup session files are corrupted Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ffb48a8883
commit
5aeab9ecad
+143
-143
@@ -257,7 +257,7 @@ function registerIpcHandlers(): void {
|
||||
if (result.started) {
|
||||
updateQuitTimer = setTimeout(() => {
|
||||
app.quit();
|
||||
}, 900);
|
||||
}, 5000);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
@@ -379,48 +379,48 @@ function registerIpcHandlers(): void {
|
||||
validateString(itemId, "itemId");
|
||||
return controller.removeItem(itemId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.togglePackage(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_PACKAGE_SELECTION, async (_event: IpcMainInvokeEvent, packageIds: string[]) => {
|
||||
const validPackageIds = validateStringArray(packageIds ?? [], "packageIds");
|
||||
const exported = controller.exportPackageSelection(validPackageIds);
|
||||
if (exported.packageCount === 0 || exported.linkCount === 0) {
|
||||
return { saved: false, packageCount: 0, linkCount: 0 };
|
||||
}
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Link Export", extensions: ["txt"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false, packageCount: exported.packageCount, linkCount: exported.linkCount };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.text, "utf8");
|
||||
return { saved: true, packageCount: exported.packageCount, linkCount: exported.linkCount, filePath: result.filePath };
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_ITEM_SELECTION, async (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||
const validItemIds = validateStringArray(itemIds ?? [], "itemIds");
|
||||
const exported = controller.exportItemSelection(validItemIds);
|
||||
if (exported.packageCount === 0 || exported.linkCount === 0) {
|
||||
return { saved: false, packageCount: 0, linkCount: 0 };
|
||||
}
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Link Export", extensions: ["txt"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false, packageCount: exported.packageCount, linkCount: exported.linkCount };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.text, "utf8");
|
||||
return { saved: true, packageCount: exported.packageCount, linkCount: exported.linkCount, filePath: result.filePath };
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.RETRY_EXTRACTION, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.retryExtraction(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.togglePackage(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_PACKAGE_SELECTION, async (_event: IpcMainInvokeEvent, packageIds: string[]) => {
|
||||
const validPackageIds = validateStringArray(packageIds ?? [], "packageIds");
|
||||
const exported = controller.exportPackageSelection(validPackageIds);
|
||||
if (exported.packageCount === 0 || exported.linkCount === 0) {
|
||||
return { saved: false, packageCount: 0, linkCount: 0 };
|
||||
}
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Link Export", extensions: ["txt"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false, packageCount: exported.packageCount, linkCount: exported.linkCount };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.text, "utf8");
|
||||
return { saved: true, packageCount: exported.packageCount, linkCount: exported.linkCount, filePath: result.filePath };
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_ITEM_SELECTION, async (_event: IpcMainInvokeEvent, itemIds: string[]) => {
|
||||
const validItemIds = validateStringArray(itemIds ?? [], "itemIds");
|
||||
const exported = controller.exportItemSelection(validItemIds);
|
||||
if (exported.packageCount === 0 || exported.linkCount === 0) {
|
||||
return { saved: false, packageCount: 0, linkCount: 0 };
|
||||
}
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Link Export", extensions: ["txt"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false, packageCount: exported.packageCount, linkCount: exported.linkCount };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.text, "utf8");
|
||||
return { saved: true, packageCount: exported.packageCount, linkCount: exported.linkCount, filePath: result.filePath };
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.RETRY_EXTRACTION, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.retryExtraction(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXTRACT_NOW, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.extractNow(packageId);
|
||||
@@ -496,12 +496,12 @@ function registerIpcHandlers(): void {
|
||||
};
|
||||
const result = mainWindow ? await dialog.showOpenDialog(mainWindow, options) : await dialog.showOpenDialog(options);
|
||||
return result.canceled ? [] : result.filePaths;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SESSION_STATS, () => controller.getSessionStats());
|
||||
ipcMain.handle(IPC_CHANNELS.RESET_SESSION_STATS, () => controller.resetSessionStats());
|
||||
ipcMain.handle(IPC_CHANNELS.RESET_DOWNLOAD_STATS, () => controller.resetDownloadStats());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.RESTART, () => {
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SESSION_STATS, () => controller.getSessionStats());
|
||||
ipcMain.handle(IPC_CHANNELS.RESET_SESSION_STATS, () => controller.resetSessionStats());
|
||||
ipcMain.handle(IPC_CHANNELS.RESET_DOWNLOAD_STATS, () => controller.resetDownloadStats());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.RESTART, () => {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
});
|
||||
@@ -510,106 +510,106 @@ function registerIpcHandlers(): void {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_BACKUP, async () => {
|
||||
const options = {
|
||||
defaultPath: `mdd-backup-${new Date().toISOString().slice(0, 10)}.mdd`,
|
||||
filters: [{ name: "MDD Backup", extensions: ["mdd"] }]
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_BACKUP, async () => {
|
||||
const options = {
|
||||
defaultPath: `mdd-backup-${new Date().toISOString().slice(0, 10)}.mdd`,
|
||||
filters: [{ name: "MDD Backup", extensions: ["mdd"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false };
|
||||
}
|
||||
const encrypted = controller.exportBackup();
|
||||
await fs.promises.writeFile(result.filePath, encrypted);
|
||||
return { saved: true };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_SUPPORT_BUNDLE, async () => {
|
||||
const exported = controller.exportSupportBundle();
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Support Bundle", extensions: ["zip"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.buffer);
|
||||
return { saved: true, filePath: result.filePath };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_LOG, async () => {
|
||||
const logPath = getLogFilePath();
|
||||
await shell.openPath(logPath);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_AUDIT_LOG, async () => {
|
||||
const logPath = controller.getAuditLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_RENAME_LOG, async () => {
|
||||
const logPath = controller.getRenameLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_SESSION_LOG, async () => {
|
||||
const logPath = controller.getSessionLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_TRACE_LOG, async () => {
|
||||
const logPath = controller.getTraceLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_PACKAGE_LOG, async (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
const logPath = controller.getPackageLogPath(packageId);
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK, async () => controller.getDebugSetupCheck());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_TRACE_CONFIG, async () => controller.getTraceConfig());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.SET_TRACE_ENABLED, async (_event: IpcMainInvokeEvent, enabled: boolean, note?: string, durationMinutes?: number) => {
|
||||
if (typeof enabled !== "boolean") {
|
||||
throw new Error("enabled muss ein Boolean sein");
|
||||
}
|
||||
if (note !== undefined) {
|
||||
validateString(note, "note");
|
||||
}
|
||||
if (durationMinutes !== undefined && (!Number.isFinite(durationMinutes) || durationMinutes <= 0)) {
|
||||
throw new Error("durationMinutes muss eine positive Zahl sein");
|
||||
}
|
||||
return controller.setTraceEnabled(enabled, note, durationMinutes ? durationMinutes * 60 * 1000 : undefined);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.ROTATE_DEBUG_TOKEN, async () => {
|
||||
const rotated = controller.rotateDebugToken();
|
||||
return { path: rotated.path };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_ITEM_LOG, async (_event: IpcMainInvokeEvent, itemId: string) => {
|
||||
validateString(itemId, "itemId");
|
||||
const logPath = controller.getItemLogPath(itemId);
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_REALDEBRID_LOGIN, async () => {
|
||||
await fs.promises.writeFile(result.filePath, encrypted);
|
||||
return { saved: true };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_SUPPORT_BUNDLE, async () => {
|
||||
const exported = controller.exportSupportBundle();
|
||||
const options = {
|
||||
defaultPath: exported.defaultFileName,
|
||||
filters: [{ name: "Support Bundle", extensions: ["zip"] }]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showSaveDialog(mainWindow, options) : await dialog.showSaveDialog(options);
|
||||
if (result.canceled || !result.filePath) {
|
||||
return { saved: false };
|
||||
}
|
||||
await fs.promises.writeFile(result.filePath, exported.buffer);
|
||||
return { saved: true, filePath: result.filePath };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_LOG, async () => {
|
||||
const logPath = getLogFilePath();
|
||||
await shell.openPath(logPath);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_AUDIT_LOG, async () => {
|
||||
const logPath = controller.getAuditLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_RENAME_LOG, async () => {
|
||||
const logPath = controller.getRenameLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_SESSION_LOG, async () => {
|
||||
const logPath = controller.getSessionLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_TRACE_LOG, async () => {
|
||||
const logPath = controller.getTraceLogPath();
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_PACKAGE_LOG, async (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
const logPath = controller.getPackageLogPath(packageId);
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_DEBUG_SETUP_CHECK, async () => controller.getDebugSetupCheck());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_TRACE_CONFIG, async () => controller.getTraceConfig());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.SET_TRACE_ENABLED, async (_event: IpcMainInvokeEvent, enabled: boolean, note?: string, durationMinutes?: number) => {
|
||||
if (typeof enabled !== "boolean") {
|
||||
throw new Error("enabled muss ein Boolean sein");
|
||||
}
|
||||
if (note !== undefined) {
|
||||
validateString(note, "note");
|
||||
}
|
||||
if (durationMinutes !== undefined && (!Number.isFinite(durationMinutes) || durationMinutes <= 0)) {
|
||||
throw new Error("durationMinutes muss eine positive Zahl sein");
|
||||
}
|
||||
return controller.setTraceEnabled(enabled, note, durationMinutes ? durationMinutes * 60 * 1000 : undefined);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.ROTATE_DEBUG_TOKEN, async () => {
|
||||
const rotated = controller.rotateDebugToken();
|
||||
return { path: rotated.path };
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_ITEM_LOG, async (_event: IpcMainInvokeEvent, itemId: string) => {
|
||||
validateString(itemId, "itemId");
|
||||
const logPath = controller.getItemLogPath(itemId);
|
||||
if (logPath) {
|
||||
await shell.openPath(logPath);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_REALDEBRID_LOGIN, async () => {
|
||||
await controller.openRealDebridLoginWindow();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user