diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index e40efb74..dff27117 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -22,6 +22,7 @@ type InstallTask = { metalinkUrl?: string; filename?: string; origin: "spark" | "apm"; + cancelled?: boolean; }; const SHELL_CALLER_PATH = "/opt/spark-store/extras/shell-caller.sh"; @@ -294,11 +295,28 @@ ipcMain.on("cancel-install", (event, id) => { if (tasks.has(id)) { const task = tasks.get(id); if (task) { - task.download_process?.kill(); // Kill the download process - task.install_process?.kill(); // Kill the install process + task.cancelled = true; + task.download_process?.kill(); + task.install_process?.kill(); logger.info(`已取消任务: ${id}`); + + // 主动发送完成(失败)事件,close 回调会因 cancelled 标志跳过 + task.webContents?.send("install-complete", { + id, + success: false, + time: Date.now(), + exitCode: -1, + message: JSON.stringify({ + message: "用户取消", + stdout: "", + stderr: "", + }), + }); + + tasks.delete(id); + idle = true; + if (tasks.size > 0) processNextInQueue(); } - // Note: 'close' handler usually handles cleanup } }); @@ -455,6 +473,10 @@ async function processNextInQueue() { child.on("close", (code) => { clearInterval(timeoutChecker); + if (task.cancelled) { + resolve(); + return; + } if (code === 0) { webContents?.send("install-progress", { id, progress: 1 }); resolve(); @@ -514,6 +536,10 @@ async function processNextInQueue() { }); child.on("close", (code) => { + if (task.cancelled) { + resolve({ code: code ?? -1, stdout, stderr }); + return; + } resolve({ code: code ?? -1, stdout, stderr }); }); child.on("error", (err) => { @@ -553,11 +579,13 @@ async function processNextInQueue() { }), }); } finally { - tasks.delete(id); - idle = true; - // Trigger next - if (tasks.size > 0) { - processNextInQueue(); + // 如果已被 cancel handler 清理,跳过重复清理 + if (!task.cancelled) { + tasks.delete(id); + idle = true; + if (tasks.size > 0) { + processNextInQueue(); + } } } } diff --git a/src/App.vue b/src/App.vue index d9a41943..66656155 100644 --- a/src/App.vue +++ b/src/App.vue @@ -884,15 +884,12 @@ const cancelDownload = (id: DownloadItem) => { // 发送到主进程取消 window.ipcRenderer.send("cancel-install", download.id); - download.status = "failed"; // TODO: Use 'cancelled'instead of failed to type will be better though + download.status = "failed"; download.logs.push({ time: Date.now(), message: "下载已取消", }); - // TODO: Remove from the list,but is it really necessary? - // Maybe keep it with 'cancelled' status for user reference - const idx = downloads.value.findIndex((d) => d.id === id.id); - if (idx !== -1) downloads.value.splice(idx, 1); + // 保留在队列中以便用户可以重试或查看日志 } };