From 1cf729e7fd9b4efe2fb706e45add05de3286fb88 Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 19 Feb 2026 19:22:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=AF=E5=8A=A8=E9=80=9F?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/backend/install-manager.ts | 25 ++++-- electron/main/index.ts | 20 +++++ src/App.vue | 106 +++++++++++++---------- 3 files changed, 99 insertions(+), 52 deletions(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index d1b791ab..f9db98c9 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -138,7 +138,7 @@ const parseUpgradableList = (output: string) => { // Listen for download requests from renderer process ipcMain.on("queue-install", async (event, download_json) => { const download = JSON.parse(download_json); - const { id, pkgname, metalinkUrl, filename } = download || {}; + const { id, pkgname, metalinkUrl, filename, upgradeOnly } = download || {}; if (!id || !pkgname) { logger.warn("passed arguments missing id or pkgname"); @@ -172,17 +172,28 @@ ipcMain.on("queue-install", async (event, download_json) => { const execParams = []; const downloadDir = `/tmp/spark-store/download/${pkgname}`; - if (superUserCmd.length > 0) { + // 升级操作:使用 spark-update-tool + if (upgradeOnly) { + execCommand = "pkexec"; + execParams.push("spark-update-tool", pkgname); + logger.info(`升级模式: 使用 spark-update-tool 升级 ${pkgname}`); + } else if (superUserCmd.length > 0) { execCommand = superUserCmd; execParams.push(SHELL_CALLER_PATH); + + if (metalinkUrl && filename) { + execParams.push("ssinstall", `${downloadDir}/${filename}`, "--delete-after-install"); + } else { + execParams.push("aptss", "install", "-y", pkgname); + } } else { execCommand = SHELL_CALLER_PATH; - } - if (metalinkUrl && filename) { - execParams.push("ssinstall", `${downloadDir}/${filename}` , "--delete-after-install"); - } else { - execParams.push("aptss", "install", "-y", pkgname); + if (metalinkUrl && filename) { + execParams.push("ssinstall", `${downloadDir}/${filename}`, "--delete-after-install"); + } else { + execParams.push("aptss", "install", "-y", pkgname); + } } const task: InstallTask = { diff --git a/electron/main/index.ts b/electron/main/index.ts index 25e17e91..56e9fb2f 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -140,6 +140,26 @@ ipcMain.on("set-theme-source", (event, theme: "system" | "light" | "dark") => { nativeTheme.themeSource = theme; }); +// 启动系统更新工具(使用 pkexec 提升权限) +ipcMain.handle("run-update-tool", async () => { + try { + const { spawn } = await import("node:child_process"); + const pkexecPath = "/usr/bin/pkexec"; + const args = ["spark-update-tool"]; + const child = spawn(pkexecPath, args, { + detached: true, + stdio: "ignore", + }); + // 让子进程在后台运行且不影响主进程退出 + child.unref(); + logger.info("Launched pkexec spark-update-tool"); + return { success: true }; + } catch (err) { + logger.error({ err }, "Failed to launch spark-update-tool"); + return { success: false, message: (err as Error)?.message || String(err) }; + } +}); + app.whenReady().then(() => { // Set User-Agent for client session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { diff --git a/src/App.vue b/src/App.vue index 246c5ba7..4c0f98b2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -346,8 +346,15 @@ const nextScreen = () => { } }; -const handleUpdate = () => { - openUpdateModal(); +const handleUpdate = async () => { + try { + const result = await window.ipcRenderer.invoke("run-update-tool"); + if (!result || !result.success) { + logger.warn(`启动更新工具失败: ${result?.message || "未知错误"}`); + } + } catch (error) { + logger.error(`调用更新工具时出错: ${error}`); + } }; const handleList = () => { @@ -639,54 +646,58 @@ const loadCategories = async () => { } }; -const loadApps = async () => { - loading.value = true; +const loadApps = async (onFirstBatch?: () => void) => { try { - logger.info("开始加载应用数据..."); + logger.info("开始加载应用数据(并发分批)..."); - // 改为顺序加载,避免同时发送过多请求 - for (const category of Object.keys(categories.value)) { - try { - logger.info(`加载分类: ${category}`); - const response = await axiosInstance.get( - cacheBuster(`/${window.apm_store.arch}/${category}/applist.json`), - ); + const categoriesList = Object.keys(categories.value || {}); + const concurrency = 4; // 同时并发请求数量,可根据网络条件调整 - const categoryApps = response.status === 200 ? response.data : []; - categoryApps.forEach((appJson) => { - // Convert AppJson to App here - const normalizedApp: App = { - name: appJson.Name, - pkgname: appJson.Pkgname, - version: appJson.Version, - filename: appJson.Filename, - torrent_address: appJson.Torrent_address, - author: appJson.Author, - contributor: appJson.Contributor, - website: appJson.Website, - update: appJson.Update, - size: appJson.Size, - more: appJson.More, - tags: appJson.Tags, - img_urls: - typeof appJson.img_urls === "string" - ? JSON.parse(appJson.img_urls) - : appJson.img_urls, - icons: appJson.icons, - category: category, - currentStatus: "not-installed", - }; - apps.value.push(normalizedApp); - }); - } catch (error) { - logger.warn(`加载分类 ${category} 失败: ${error}`); - // 继续加载其他分类 - } + for (let i = 0; i < categoriesList.length; i += concurrency) { + const batch = categoriesList.slice(i, i + concurrency); + await Promise.all( + batch.map(async (category) => { + try { + logger.info(`加载分类: ${category}`); + const response = await axiosInstance.get( + cacheBuster(`/${window.apm_store.arch}/${category}/applist.json`), + ); + const categoryApps = response.status === 200 ? response.data : []; + categoryApps.forEach((appJson) => { + const normalizedApp: App = { + name: appJson.Name, + pkgname: appJson.Pkgname, + version: appJson.Version, + filename: appJson.Filename, + torrent_address: appJson.Torrent_address, + author: appJson.Author, + contributor: appJson.Contributor, + website: appJson.Website, + update: appJson.Update, + size: appJson.Size, + more: appJson.More, + tags: appJson.Tags, + img_urls: + typeof appJson.img_urls === "string" + ? JSON.parse(appJson.img_urls) + : appJson.img_urls, + icons: appJson.icons, + category: category, + currentStatus: "not-installed", + }; + apps.value.push(normalizedApp); + }); + } catch (error) { + logger.warn(`加载分类 ${category} 失败: ${error}`); + } + }), + ); + + // 首批完成回调(用于隐藏首屏 loading) + if (i === 0 && typeof onFirstBatch === "function") onFirstBatch(); } } catch (error) { logger.error(`加载应用数据失败: ${error}`); - } finally { - loading.value = false; } }; @@ -699,7 +710,12 @@ onMounted(async () => { initTheme(); await loadCategories(); - await loadApps(); + // 先显示 loading,并异步开始分批加载应用列表。 + loading.value = true; + loadApps(() => { + // 当第一批分类加载完成后,隐藏首屏 loading + loading.value = false; + }); // 设置键盘导航 document.addEventListener("keydown", (e) => {