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;
}
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 (fs.existsSync(checkScript)) {
const child = spawn(checkScript, [pkgname], {
shell: false,
env: process.env,
});
await new Promise<void>((resolve) => {
child.on("error", (err) => {
logger.error(`check-installed 脚本执行失败: ${err?.message || err}`);
resolve();
});
child.on("close", (code) => {
if (code === 0) {
if (code === 0) {
const line = stdout.trim();
if (line) {
const parts = line.split("\t");
if (parts.length >= 2) {
const status = parts[1].trim();
// 检查状态是否为 "install ok installed"
if (status === "install ok installed") {
isInstalled = true;
logger.info(`应用已安装 (脚本检测): ${pkgname}`);
logger.info(`应用已安装 (dpkg检测): ${pkgname}`);
}
resolve();
});
});
if (isInstalled) return true;
}
}
}
return isInstalled;
@@ -793,7 +788,11 @@ ipcMain.on("remove-installed", async (_event, payload) => {
ipcMain.handle(
"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";
try {
@@ -809,9 +808,54 @@ ipcMain.handle(
}> = [];
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", [
"-W",
"-f=${Package} ${Version} ${Architecture}\\n",
"-f=${Package}\\t${Version}\\t${Architecture}\\t${Status}\\n",
]);
if (code !== 0) {
@@ -827,17 +871,21 @@ ipcMain.handle(
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
const parts = trimmed.split(" ");
if (parts.length >= 3) {
installedApps.push({
pkgname: parts[0],
name: parts[0],
version: parts[1],
arch: parts[2],
flags: "[installed]",
origin: "spark",
isDependency: false,
});
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 };
+44 -15
View File
@@ -49,6 +49,21 @@ interface RemoteCategoryAppEntry {
const REMOTE_STORE_BASE_URL = "https://erotica.spark-app.store";
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 = {
command: "bash",
args: [
@@ -352,49 +367,63 @@ const enrichItemIcons = (items: UpdateCenterItem[]): UpdateCenterItem[] => {
export const loadUpdateCenterItems = async (
runCommand: UpdateCenterCommandRunner = runCommandCapture,
): Promise<UpdateCenterLoadItemsResult> => {
const aptssAvailable = await isAptssAvailable();
const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] =
await Promise.all([
runCommand(
APTSS_LIST_UPGRADABLE_COMMAND.command,
APTSS_LIST_UPGRADABLE_COMMAND.args,
),
aptssAvailable
? runCommand(
APTSS_LIST_UPGRADABLE_COMMAND.command,
APTSS_LIST_UPGRADABLE_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
runCommand("apm", ["list", "--upgradable"]),
runCommand(
DPKG_QUERY_INSTALLED_COMMAND.command,
DPKG_QUERY_INSTALLED_COMMAND.args,
),
aptssAvailable
? runCommand(
DPKG_QUERY_INSTALLED_COMMAND.command,
DPKG_QUERY_INSTALLED_COMMAND.args,
)
: Promise.resolve({ code: 0, stdout: "", stderr: "" }),
runCommand("apm", ["list", "--installed"]),
]);
const warnings = [
getCommandError("aptss upgradable query", aptssResult),
aptssAvailable
? getCommandError("aptss upgradable query", aptssResult)
: null,
getCommandError("apm upgradable query", apmResult),
getCommandError("dpkg installed query", aptssInstalledResult),
aptssAvailable
? getCommandError("dpkg installed query", aptssInstalledResult)
: null,
getCommandError("apm installed query", apmInstalledResult),
].filter((message): message is string => message !== null);
const aptssItems =
aptssResult.code === 0
aptssAvailable && aptssResult.code === 0
? parseAptssUpgradableOutput(aptssResult.stdout)
: [];
const apmItems =
apmResult.code === 0 ? parseApmUpgradableOutput(apmResult.stdout) : [];
if (aptssResult.code !== 0 && apmResult.code !== 0) {
if (apmResult.code !== 0) {
throw new Error(warnings.join("; "));
}
const installedSources = buildInstalledSourceMap(
aptssInstalledResult.code === 0 ? aptssInstalledResult.stdout : "",
aptssAvailable && aptssInstalledResult.code === 0
? aptssInstalledResult.stdout
: "",
apmInstalledResult.code === 0 ? apmInstalledResult.stdout : "",
);
const [categorizedAptssItems, categorizedApmItems] = await Promise.all([
enrichItemCategories(aptssItems),
aptssAvailable ? enrichItemCategories(aptssItems) : Promise.resolve([]),
enrichItemCategories(apmItems),
]);
const [enrichedAptssItems, enrichedApmItems] = await Promise.all([
enrichAptssItems(categorizedAptssItems, runCommand),
aptssAvailable
? enrichAptssItems(categorizedAptssItems, runCommand)
: Promise.resolve({ items: [], warnings: [] }),
enrichApmItems(categorizedApmItems, runCommand),
]);
+13 -10
View File
@@ -816,7 +816,19 @@ const refreshInstalledApps = async () => {
installedError.value = "";
try {
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) {
installedApps.value = [];
installedError.value = result?.message || "读取已安装应用失败";
@@ -835,15 +847,6 @@ const refreshInstalledApps = async () => {
continue;
}
// 二次确认:使用 check-installed 验证包是否真正安装
const isReallyInstalled = await window.ipcRenderer.invoke(
"check-installed",
{ pkgname: app.pkgname, origin: app.origin },
);
if (!isReallyInstalled) {
continue;
}
if (appInfo) {
appInfo.flags = app.flags;
appInfo.arch = app.arch;