perf(spark): 优化已安装应用检查逻辑

- 对于 Spark 应用,使用 dpkg-query 替代自定义脚本检查安装状态
- 在 list-installed 接口中支持传入包名列表进行批量检查,避免全量扫描
- 添加 aptss 可用性检查,避免在不可用时执行相关命令
- 移除冗余的 check-installed 二次验证步骤
This commit is contained in:
2026-04-16 00:10:14 +08:00
parent 9eb141ee35
commit 68dd6a0a26
3 changed files with 140 additions and 60 deletions
+83 -35
View File
@@ -699,31 +699,26 @@ ipcMain.handle("check-installed", async (_event, payload: any) => {
return isInstalled; return isInstalled;
} }
const checkScript = "/opt/spark-store/extras/check-is-installed"; // Spark: 使用 dpkg-query 检查安装状态
const { code, stdout } = await runCommandCapture("dpkg-query", [
"-W",
"-f=${Package}\\t${Status}\\n",
pkgname,
]);
// 首先尝试使用内置脚本 if (code === 0) {
if (fs.existsSync(checkScript)) { const line = stdout.trim();
const child = spawn(checkScript, [pkgname], { if (line) {
shell: false, const parts = line.split("\t");
env: process.env, if (parts.length >= 2) {
}); const status = parts[1].trim();
// 检查状态是否为 "install ok installed"
await new Promise<void>((resolve) => { if (status === "install ok installed") {
child.on("error", (err) => {
logger.error(`check-installed 脚本执行失败: ${err?.message || err}`);
resolve();
});
child.on("close", (code) => {
if (code === 0) {
isInstalled = true; isInstalled = true;
logger.info(`应用已安装 (脚本检测): ${pkgname}`); logger.info(`应用已安装 (dpkg检测): ${pkgname}`);
} }
resolve(); }
}); }
});
if (isInstalled) return true;
} }
return isInstalled; return isInstalled;
@@ -793,7 +788,11 @@ ipcMain.on("remove-installed", async (_event, payload) => {
ipcMain.handle( ipcMain.handle(
"list-installed", "list-installed",
async (_event, origin: "apm" | "spark" = "apm") => { async (
_event,
payload: { origin: "apm" | "spark"; pkgnameList?: string[] },
) => {
const { origin, pkgnameList } = payload;
const apmBasePath = "/var/lib/apm/apm/files/ace-env/var/lib/apm"; const apmBasePath = "/var/lib/apm/apm/files/ace-env/var/lib/apm";
try { try {
@@ -809,9 +808,54 @@ ipcMain.handle(
}> = []; }> = [];
if (origin === "spark") { if (origin === "spark") {
// 如果提供了包名列表,只检查这些包的安装状态(优化版)
if (pkgnameList && pkgnameList.length > 0) {
logger.info(
`使用优化模式检查 ${pkgnameList.length} 个 Spark 包的安装状态`,
);
// 批量查询这些包的状态
// 注意:dpkg-query 在部分包不存在时也会返回非零码,但已找到的包会输出到 stdout
const { stdout, stderr } = await runCommandCapture("dpkg-query", [
"-W",
"-f=${Package}\\t${Version}\\t${Architecture}\\t${Status}\\n",
...pkgnameList,
]);
// 即使没有错误,也可能有警告信息输出到 stderr
if (stderr) {
logger.debug(`dpkg-query warnings: ${stderr}`);
}
const lines = stdout.split("\n");
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
const parts = trimmed.split("\t");
if (parts.length >= 4) {
const status = parts[3].trim();
// 只保留状态为 "install ok installed" 的包
if (status === "install ok installed") {
installedApps.push({
pkgname: parts[0],
name: parts[0],
version: parts[1],
arch: parts[2],
flags: "[installed]",
origin: "spark",
isDependency: false,
});
}
}
}
return { success: true, apps: installedApps };
}
// 回退到全量扫描模式(未提供包名列表时)
logger.info("使用全量扫描模式获取所有 Spark 已安装包");
const { code, stdout } = await runCommandCapture("dpkg-query", [ const { code, stdout } = await runCommandCapture("dpkg-query", [
"-W", "-W",
"-f=${Package} ${Version} ${Architecture}\\n", "-f=${Package}\\t${Version}\\t${Architecture}\\t${Status}\\n",
]); ]);
if (code !== 0) { if (code !== 0) {
@@ -827,17 +871,21 @@ ipcMain.handle(
for (const line of lines) { for (const line of lines) {
const trimmed = line.trim(); const trimmed = line.trim();
if (!trimmed) continue; if (!trimmed) continue;
const parts = trimmed.split(" "); const parts = trimmed.split("\t");
if (parts.length >= 3) { if (parts.length >= 4) {
installedApps.push({ const status = parts[3].trim();
pkgname: parts[0], // 只保留状态为 "install ok installed" 的包
name: parts[0], if (status === "install ok installed") {
version: parts[1], installedApps.push({
arch: parts[2], pkgname: parts[0],
flags: "[installed]", name: parts[0],
origin: "spark", version: parts[1],
isDependency: false, arch: parts[2],
}); flags: "[installed]",
origin: "spark",
isDependency: false,
});
}
} }
} }
return { success: true, apps: installedApps }; return { success: true, apps: installedApps };
+44 -15
View File
@@ -49,6 +49,21 @@ interface RemoteCategoryAppEntry {
const REMOTE_STORE_BASE_URL = "https://erotica.spark-app.store"; const REMOTE_STORE_BASE_URL = "https://erotica.spark-app.store";
const categoryCache = new Map<string, Promise<StoreAppMetadataMap>>(); const categoryCache = new Map<string, Promise<StoreAppMetadataMap>>();
const isAptssAvailable = async (): Promise<boolean> => {
return await new Promise((resolve) => {
const child = spawn("command", ["-v", "aptss"], {
shell: false,
env: process.env,
});
child.on("close", (code) => {
resolve(code === 0);
});
child.on("error", () => {
resolve(false);
});
});
};
const APTSS_LIST_UPGRADABLE_COMMAND = { const APTSS_LIST_UPGRADABLE_COMMAND = {
command: "bash", command: "bash",
args: [ args: [
@@ -352,49 +367,63 @@ const enrichItemIcons = (items: UpdateCenterItem[]): UpdateCenterItem[] => {
export const loadUpdateCenterItems = async ( export const loadUpdateCenterItems = async (
runCommand: UpdateCenterCommandRunner = runCommandCapture, runCommand: UpdateCenterCommandRunner = runCommandCapture,
): Promise<UpdateCenterLoadItemsResult> => { ): Promise<UpdateCenterLoadItemsResult> => {
const aptssAvailable = await isAptssAvailable();
const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] = const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] =
await Promise.all([ await Promise.all([
runCommand( aptssAvailable
APTSS_LIST_UPGRADABLE_COMMAND.command, ? runCommand(
APTSS_LIST_UPGRADABLE_COMMAND.args, APTSS_LIST_UPGRADABLE_COMMAND.command,
), APTSS_LIST_UPGRADABLE_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
runCommand("apm", ["list", "--upgradable"]), runCommand("apm", ["list", "--upgradable"]),
runCommand( aptssAvailable
DPKG_QUERY_INSTALLED_COMMAND.command, ? runCommand(
DPKG_QUERY_INSTALLED_COMMAND.args, DPKG_QUERY_INSTALLED_COMMAND.command,
), DPKG_QUERY_INSTALLED_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
runCommand("apm", ["list", "--installed"]), runCommand("apm", ["list", "--installed"]),
]); ]);
const warnings = [ const warnings = [
getCommandError("aptss upgradable query", aptssResult), aptssAvailable
? getCommandError("aptss upgradable query", aptssResult)
: null,
getCommandError("apm upgradable query", apmResult), getCommandError("apm upgradable query", apmResult),
getCommandError("dpkg installed query", aptssInstalledResult), aptssAvailable
? getCommandError("dpkg installed query", aptssInstalledResult)
: null,
getCommandError("apm installed query", apmInstalledResult), getCommandError("apm installed query", apmInstalledResult),
].filter((message): message is string => message !== null); ].filter((message): message is string => message !== null);
const aptssItems = const aptssItems =
aptssResult.code === 0 aptssAvailable && aptssResult.code === 0
? parseAptssUpgradableOutput(aptssResult.stdout) ? parseAptssUpgradableOutput(aptssResult.stdout)
: []; : [];
const apmItems = const apmItems =
apmResult.code === 0 ? parseApmUpgradableOutput(apmResult.stdout) : []; apmResult.code === 0 ? parseApmUpgradableOutput(apmResult.stdout) : [];
if (aptssResult.code !== 0 && apmResult.code !== 0) { if (apmResult.code !== 0) {
throw new Error(warnings.join("; ")); throw new Error(warnings.join("; "));
} }
const installedSources = buildInstalledSourceMap( const installedSources = buildInstalledSourceMap(
aptssInstalledResult.code === 0 ? aptssInstalledResult.stdout : "", aptssAvailable && aptssInstalledResult.code === 0
? aptssInstalledResult.stdout
: "",
apmInstalledResult.code === 0 ? apmInstalledResult.stdout : "", apmInstalledResult.code === 0 ? apmInstalledResult.stdout : "",
); );
const [categorizedAptssItems, categorizedApmItems] = await Promise.all([ const [categorizedAptssItems, categorizedApmItems] = await Promise.all([
enrichItemCategories(aptssItems), aptssAvailable ? enrichItemCategories(aptssItems) : Promise.resolve([]),
enrichItemCategories(apmItems), enrichItemCategories(apmItems),
]); ]);
const [enrichedAptssItems, enrichedApmItems] = await Promise.all([ const [enrichedAptssItems, enrichedApmItems] = await Promise.all([
enrichAptssItems(categorizedAptssItems, runCommand), aptssAvailable
? enrichAptssItems(categorizedAptssItems, runCommand)
: Promise.resolve({ items: [], warnings: [] }),
enrichApmItems(categorizedApmItems, runCommand), enrichApmItems(categorizedApmItems, runCommand),
]); ]);
+13 -10
View File
@@ -816,7 +816,19 @@ const refreshInstalledApps = async () => {
installedError.value = ""; installedError.value = "";
try { try {
const origin = activeInstalledOrigin.value; const origin = activeInstalledOrigin.value;
const result = await window.ipcRenderer.invoke("list-installed", origin);
// Spark 优化:只检查远端商店目录中的应用,避免全量扫描
let pkgnameList: string[] | undefined;
if (origin === "spark") {
pkgnameList = apps.value
.filter((a) => a.origin === "spark")
.map((a) => a.pkgname);
}
const result = await window.ipcRenderer.invoke("list-installed", {
origin,
pkgnameList,
});
if (!result?.success) { if (!result?.success) {
installedApps.value = []; installedApps.value = [];
installedError.value = result?.message || "读取已安装应用失败"; installedError.value = result?.message || "读取已安装应用失败";
@@ -835,15 +847,6 @@ const refreshInstalledApps = async () => {
continue; continue;
} }
// 二次确认:使用 check-installed 验证包是否真正安装
const isReallyInstalled = await window.ipcRenderer.invoke(
"check-installed",
{ pkgname: app.pkgname, origin: app.origin },
);
if (!isReallyInstalled) {
continue;
}
if (appInfo) { if (appInfo) {
appInfo.flags = app.flags; appInfo.flags = app.flags;
appInfo.arch = app.arch; appInfo.arch = app.arch;