From 6921350878edbf88e6b22b118571cbf83a51b1b1 Mon Sep 17 00:00:00 2001 From: MarcusPy827 Date: Tue, 30 Jun 2026 03:13:28 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20APM=E5=AE=89=E8=A3=85=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=90=8E=E6=97=A0=E6=B3=95=E8=87=AA=E5=8A=A8=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=BF=AB=E6=8D=B7=E6=96=B9=E5=BC=8F=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + electron/main/backend/install-manager.ts | 94 +++++++++++++++++++++++- package-lock.json | 4 +- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 0af92e01..fea3e637 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ yarn.lock test-results.json .worktrees/ .superpowers/ + +# VSCode CMake Extension +build/* diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index de169d2e..257b302e 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -1,6 +1,7 @@ import { ipcMain, WebContents } from "electron"; import { spawn, ChildProcess } from "node:child_process"; import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; import pino from "pino"; @@ -45,6 +46,86 @@ type InstallTask = { const SHELL_CALLER_PATH = "/opt/spark-store/extras/shell-caller.sh"; +// 以下路径配置参考自index.ts并且与其保持一致 +// 其中,SPARK_CONFIG_DIR为配置目录,若此目录下出现ssshell-config-do-not-create-desktop文件 +// 则代表「关闭『自动创建桌面启动器』功能」 +const SPARK_CONFIG_DIR = path.join( + os.homedir(), + ".config/spark-union/spark-store", +); +const CREATE_DESKTOP_CONFIG = "ssshell-config-do-not-create-desktop"; +const CREATE_DESKTOP_CONFIG_PATH = path.join( + SPARK_CONFIG_DIR, + CREATE_DESKTOP_CONFIG, +); + +// APM应用的.desktop文件可能在以下几个位置 +const APM_DESKTOP_ENTRY_DIRS = [ + "/var/lib/apm", // 实体机/宿主系统 + "/var/lib/apm/apm/files/ace-env/var/lib/apm", // ACE容器 +]; + + + +// Helper: 为APM安装的应用创建桌面快捷方式(如果启用了「自动创建桌面启动器」) +const createApmDesktopShortcut = (pkgname: string, sendLog: (msg: string) => void) => { + // 如上所述,配置目录里面有ssshell-config-do-not-create-desktop文件就是功能关闭 + if (fs.existsSync(CREATE_DESKTOP_CONFIG_PATH)) { + logger.debug( + `Desktop shortcut creation has been disabled. Skipping creating it for ${pkgname}.`, + ); + + // 这种情况直接终止这个函数的执行即可 + return; + } + + // 桌面路径 + const desktopDir = path.join(os.homedir(), "Desktop"); + + // 遍历APM应用的.desktop文件可能在以下几个位置 + for (const baseDir of APM_DESKTOP_ENTRY_DIRS) { + const entriesPath = path.join(baseDir, pkgname, "entries", "applications"); + if (!fs.existsSync(entriesPath)) { + // 没找到就下一个 + continue; + } + + // 找着了 + try { + const files = fs.readdirSync(entriesPath); + for (const file of files) { + // 忽略扩展名不符的 + if (!file.endsWith(".desktop")) { + continue; + } + + const srcPath = path.join(entriesPath, file); + const destPath = path.join(desktopDir, file); + + // 目标已存在则跳过 + if (fs.existsSync(destPath)) { + logger.debug(`Shortcut already exists: ${destPath}`); + sendLog(`Shortcut already exists: ${file}`); + return; + } + + // 读取.desktop文件内容 + const content = fs.readFileSync(srcPath, "utf-8"); + + // 写入用户桌面,顺带处理一下权限问题 + fs.writeFileSync(destPath, content, { mode: 0o755 }); + sendLog(`Wrote desktop shortcut: ${file}`); + logger.info(`Wrote shortcut ${destPath} for ${pkgname}.`); + return; + } + } catch (err) { + logger.warn(`Failed to read APM .desktop file for ${entriesPath}: ${err}.`); + } + } + + logger.debug(`Could NOT find ${pkgname}'s .desktop file...`); +}; + export const tasks = new Map(); let idle = true; // Indicates if the installation manager is idle @@ -584,8 +665,17 @@ async function processNextInQueue() { stderr: result.stderr, }; - if (success) logger.info(msgObj); - else logger.error(msgObj); + if (success) { + logger.info(msgObj); + + // 安装成功后,如果是APM安装的,就调用createApmDesktopShortcut + // 这个函数负责处理桌面快捷方式,它自己会读取设置并且决定要不要创建 + if (task.origin === "apm" && task.pkgname) { + createApmDesktopShortcut(task.pkgname, sendLog); + } + } else { + logger.error(msgObj); + } webContents?.send("install-complete", { id, diff --git a/package-lock.json b/package-lock.json index 3a8fb69a..72b29bdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "spark-store", - "version": "5.1.1", + "version": "5.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "spark-store", - "version": "5.1.1", + "version": "5.1.2", "license": "GPL-3.0", "dependencies": { "@tailwindcss/vite": "^4.1.18", From 98d6ebd2b43d02f79cf7048f3eecd763e131c4bb Mon Sep 17 00:00:00 2001 From: MarcusPy827 Date: Tue, 30 Jun 2026 03:27:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20APM=E6=A1=8C=E9=9D=A2=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E6=96=B9=E5=BC=8F=E8=A1=A5=E4=B8=81=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E8=AF=BB=E5=8F=96+=E4=BB=8EXDG=E6=8B=BF?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/backend/install-manager.ts | 90 ++++++++++++++++-------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 257b302e..c54e9cb9 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -1,6 +1,7 @@ import { ipcMain, WebContents } from "electron"; import { spawn, ChildProcess } from "node:child_process"; import fs from "node:fs"; +import { promises as fsp } from "node:fs"; import os from "node:os"; import path from "node:path"; import pino from "pino"; @@ -65,61 +66,92 @@ const APM_DESKTOP_ENTRY_DIRS = [ "/var/lib/apm/apm/files/ace-env/var/lib/apm", // ACE容器 ]; +// Helper: 用XDG_DESKTOP_DIR拿桌面路径,读不到我就回退到~/Desktop +const resolveDesktopDir = (): string => { + const userDirsPath = path.join(os.homedir(), ".config", "user-dirs.dirs"); + try { + const content = fs.readFileSync(userDirsPath, "utf-8"); + const matchRes = content.match(/^XDG_DESKTOP_DIR="\$HOME\/(.+)"$/m); + if (matchRes?.[1]) { + return path.join(os.homedir(), matchRes[1]); + } + } catch { + // user-dirs.dirs无法读取 + logger.warn(`Failed to get XDG_DESKTOP_DIR, I'm falling back to ~/Desktop!!`); + } + return path.join(os.homedir(), "Desktop"); +}; // Helper: 为APM安装的应用创建桌面快捷方式(如果启用了「自动创建桌面启动器」) -const createApmDesktopShortcut = (pkgname: string, sendLog: (msg: string) => void) => { +const createApmDesktopShortcut = async ( + pkgname: string, + sendLog: (msg: string) => void, +) => { // 如上所述,配置目录里面有ssshell-config-do-not-create-desktop文件就是功能关闭 - if (fs.existsSync(CREATE_DESKTOP_CONFIG_PATH)) { + try { + await fsp.access(CREATE_DESKTOP_CONFIG_PATH); logger.debug( `Desktop shortcut creation has been disabled. Skipping creating it for ${pkgname}.`, ); - - // 这种情况直接终止这个函数的执行即可 return; + } catch { + // 文件不存在就是功能启用 继续 } - // 桌面路径 - const desktopDir = path.join(os.homedir(), "Desktop"); + // 解析桌面路径,确保目录存在 + const desktopDir = resolveDesktopDir(); + try { + await fsp.mkdir(desktopDir, { recursive: true }); + } catch (err) { + logger.warn(`Failed to create desktop directory ${desktopDir}: ${err}`); + return; + } // 遍历APM应用的.desktop文件可能在以下几个位置 for (const baseDir of APM_DESKTOP_ENTRY_DIRS) { const entriesPath = path.join(baseDir, pkgname, "entries", "applications"); - if (!fs.existsSync(entriesPath)) { - // 没找到就下一个 + let files: string[]; + try { + files = await fsp.readdir(entriesPath); + } catch { continue; } - // 找着了 - try { - const files = fs.readdirSync(entriesPath); - for (const file of files) { - // 忽略扩展名不符的 - if (!file.endsWith(".desktop")) { - continue; - } + for (const file of files) { + // 忽略扩展名不符的 + if (!file.endsWith(".desktop")) { + continue; + } - const srcPath = path.join(entriesPath, file); - const destPath = path.join(desktopDir, file); + const srcPath = path.join(entriesPath, file); + const destPath = path.join(desktopDir, file); - // 目标已存在则跳过 - if (fs.existsSync(destPath)) { - logger.debug(`Shortcut already exists: ${destPath}`); - sendLog(`Shortcut already exists: ${file}`); - return; - } + // 目标已存在则跳过 + try { + await fsp.access(destPath); + logger.debug(`Shortcut already exists: ${destPath}`); + sendLog(`Shortcut already exists: ${file}`); + return; + } catch { + // 不存在,继续 + } + try { // 读取.desktop文件内容 - const content = fs.readFileSync(srcPath, "utf-8"); + const content = await fsp.readFile(srcPath, "utf-8"); // 写入用户桌面,顺带处理一下权限问题 - fs.writeFileSync(destPath, content, { mode: 0o755 }); + await fsp.writeFile(destPath, content, { mode: 0o755 }); sendLog(`Wrote desktop shortcut: ${file}`); logger.info(`Wrote shortcut ${destPath} for ${pkgname}.`); return; + } catch (err) { + logger.warn( + `Failed to create desktop shortcut for ${pkgname}: ${err}`, + ); + return; } - } catch (err) { - logger.warn(`Failed to read APM .desktop file for ${entriesPath}: ${err}.`); } } @@ -671,7 +703,7 @@ async function processNextInQueue() { // 安装成功后,如果是APM安装的,就调用createApmDesktopShortcut // 这个函数负责处理桌面快捷方式,它自己会读取设置并且决定要不要创建 if (task.origin === "apm" && task.pkgname) { - createApmDesktopShortcut(task.pkgname, sendLog); + await createApmDesktopShortcut(task.pkgname, sendLog); } } else { logger.error(msgObj); From 51d7e1870c2176220b226bfcc24bc623d6fd6b78 Mon Sep 17 00:00:00 2001 From: MarcusPy827 Date: Tue, 30 Jun 2026 03:43:21 +0800 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20APM=E6=A1=8C=E9=9D=A2=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E6=96=B9=E5=BC=8F=E8=A1=A5=E4=B8=81=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E8=AF=BB=E5=8F=96+=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BF=87=E6=97=A9return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/backend/install-manager.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index c54e9cb9..82ffb38b 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -67,11 +67,11 @@ const APM_DESKTOP_ENTRY_DIRS = [ ]; // Helper: 用XDG_DESKTOP_DIR拿桌面路径,读不到我就回退到~/Desktop -const resolveDesktopDir = (): string => { +const resolveDesktopDir = async (): Promise => { const userDirsPath = path.join(os.homedir(), ".config", "user-dirs.dirs"); try { - const content = fs.readFileSync(userDirsPath, "utf-8"); + const content = await fsp.readFile(userDirsPath, "utf-8"); const matchRes = content.match(/^XDG_DESKTOP_DIR="\$HOME\/(.+)"$/m); if (matchRes?.[1]) { return path.join(os.homedir(), matchRes[1]); @@ -100,7 +100,7 @@ const createApmDesktopShortcut = async ( } // 解析桌面路径,确保目录存在 - const desktopDir = resolveDesktopDir(); + const desktopDir = await resolveDesktopDir(); try { await fsp.mkdir(desktopDir, { recursive: true }); } catch (err) { @@ -127,12 +127,12 @@ const createApmDesktopShortcut = async ( const srcPath = path.join(entriesPath, file); const destPath = path.join(desktopDir, file); - // 目标已存在则跳过 + // 目标已存在则跳过,继续检查下一个 try { await fsp.access(destPath); logger.debug(`Shortcut already exists: ${destPath}`); sendLog(`Shortcut already exists: ${file}`); - return; + continue; } catch { // 不存在,继续 } @@ -703,7 +703,13 @@ async function processNextInQueue() { // 安装成功后,如果是APM安装的,就调用createApmDesktopShortcut // 这个函数负责处理桌面快捷方式,它自己会读取设置并且决定要不要创建 if (task.origin === "apm" && task.pkgname) { - await createApmDesktopShortcut(task.pkgname, sendLog); + try { + await createApmDesktopShortcut(task.pkgname, sendLog); + } catch (err) { + logger.warn( + `Failed to create shortcut for ${task.pkgname}: ${err}`, + ); + } } } else { logger.error(msgObj); From 2efea67a60ce08d31591d5d9735c27aa231d36c3 Mon Sep 17 00:00:00 2001 From: MarcusPy827 Date: Tue, 30 Jun 2026 03:44:57 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20APM=E6=A1=8C=E9=9D=A2=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E6=96=B9=E5=BC=8F=E8=A1=A5=E4=B8=81=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E6=9D=83=E9=99=900755=20->=200644?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/backend/install-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 82ffb38b..64f7c655 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -142,7 +142,7 @@ const createApmDesktopShortcut = async ( const content = await fsp.readFile(srcPath, "utf-8"); // 写入用户桌面,顺带处理一下权限问题 - await fsp.writeFile(destPath, content, { mode: 0o755 }); + await fsp.writeFile(destPath, content, { mode: 0o644 }); sendLog(`Wrote desktop shortcut: ${file}`); logger.info(`Wrote shortcut ${destPath} for ${pkgname}.`); return;