mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-05-30 01:31:06 +08:00
perf(spark): 优化已安装应用检查逻辑
- 对于 Spark 应用,使用 dpkg-query 替代自定义脚本检查安装状态 - 在 list-installed 接口中支持传入包名列表进行批量检查,避免全量扫描 - 添加 aptss 可用性检查,避免在不可用时执行相关命令 - 移除冗余的 check-installed 二次验证步骤
This commit is contained in:
@@ -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 };
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user