update 修复更新工具缺少软件名检查的问题

This commit is contained in:
2026-04-12 19:39:17 +08:00
parent 60628ff1fa
commit 67aa83fe26
5 changed files with 111 additions and 24 deletions

View File

@@ -33,14 +33,20 @@ export interface UpdateCenterLoadItemsResult {
warnings: string[]; warnings: string[];
} }
type StoreCategoryMap = Map<string, string>; interface RemoteAppMetadata {
category: string;
name?: string;
}
type StoreAppMetadataMap = Map<string, RemoteAppMetadata>;
interface RemoteCategoryAppEntry { interface RemoteCategoryAppEntry {
Name?: string;
Pkgname?: string; Pkgname?: string;
} }
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<StoreCategoryMap>>(); const categoryCache = new Map<string, Promise<StoreAppMetadataMap>>();
const APTSS_LIST_UPGRADABLE_COMMAND = { const APTSS_LIST_UPGRADABLE_COMMAND = {
command: "bash", command: "bash",
@@ -157,16 +163,22 @@ const loadAptssItemMetadata = async (
> => { > => {
console.log(`[DEBUG] Loading APTSS metadata for ${item.pkgname}`); console.log(`[DEBUG] Loading APTSS metadata for ${item.pkgname}`);
const printUrisCommand = getAptssPrintUrisCommand(item.pkgname); const printUrisCommand = getAptssPrintUrisCommand(item.pkgname);
console.log(`[DEBUG] APTSS command: ${printUrisCommand.command} ${printUrisCommand.args.join(' ')}`); console.log(
`[DEBUG] APTSS command: ${printUrisCommand.command} ${printUrisCommand.args.join(" ")}`,
);
const metadataResult = await runCommand( const metadataResult = await runCommand(
printUrisCommand.command, printUrisCommand.command,
printUrisCommand.args, printUrisCommand.args,
); );
console.log(`[DEBUG] APTSS metadata result code: ${metadataResult.code}`); console.log(`[DEBUG] APTSS metadata result code: ${metadataResult.code}`);
console.log(`[DEBUG] APTSS metadata stdout: ${metadataResult.stdout.substring(0, 500)}`); console.log(
console.log(`[DEBUG] APTSS metadata stderr: ${metadataResult.stderr.substring(0, 500)}`); `[DEBUG] APTSS metadata stdout: ${metadataResult.stdout.substring(0, 500)}`,
);
console.log(
`[DEBUG] APTSS metadata stderr: ${metadataResult.stderr.substring(0, 500)}`,
);
const commandError = getCommandError( const commandError = getCommandError(
`aptss metadata query for ${item.pkgname}`, `aptss metadata query for ${item.pkgname}`,
metadataResult, metadataResult,
@@ -178,7 +190,7 @@ const loadAptssItemMetadata = async (
const metadata = parsePrintUrisOutput(metadataResult.stdout); const metadata = parsePrintUrisOutput(metadataResult.stdout);
console.log(`[DEBUG] APTSS parsed metadata:`, metadata); console.log(`[DEBUG] APTSS parsed metadata:`, metadata);
if (!metadata) { if (!metadata) {
return { return {
item: null, item: null,
@@ -252,7 +264,7 @@ const loadJson = async <T>(url: string): Promise<T> => {
const loadStoreCategoryMap = async ( const loadStoreCategoryMap = async (
storeArch: string, storeArch: string,
): Promise<StoreCategoryMap> => { ): Promise<StoreAppMetadataMap> => {
const categories = await loadJson<Record<string, unknown>>( const categories = await loadJson<Record<string, unknown>>(
`${REMOTE_STORE_BASE_URL}/${storeArch}/categories.json`, `${REMOTE_STORE_BASE_URL}/${storeArch}/categories.json`,
); );
@@ -266,7 +278,7 @@ const loadStoreCategoryMap = async (
}), }),
); );
const categoryMap: StoreCategoryMap = new Map(); const categoryMap: StoreAppMetadataMap = new Map();
for (const entry of categoryEntries) { for (const entry of categoryEntries) {
if (entry.status !== "fulfilled") { if (entry.status !== "fulfilled") {
continue; continue;
@@ -274,7 +286,10 @@ const loadStoreCategoryMap = async (
for (const app of entry.value.apps) { for (const app of entry.value.apps) {
if (app.Pkgname && !categoryMap.has(app.Pkgname)) { if (app.Pkgname && !categoryMap.has(app.Pkgname)) {
categoryMap.set(app.Pkgname, entry.value.category); categoryMap.set(app.Pkgname, {
category: entry.value.category,
name: app.Name,
});
} }
} }
} }
@@ -282,7 +297,9 @@ const loadStoreCategoryMap = async (
return categoryMap; return categoryMap;
}; };
const getStoreCategoryMap = (storeArch: string): Promise<StoreCategoryMap> => { const getStoreCategoryMap = (
storeArch: string,
): Promise<StoreAppMetadataMap> => {
const cached = categoryCache.get(storeArch); const cached = categoryCache.get(storeArch);
if (cached) { if (cached) {
return cached; return cached;
@@ -311,8 +328,14 @@ const enrichItemCategories = async (
} }
const categoryMap = await getStoreCategoryMap(storeArch); const categoryMap = await getStoreCategoryMap(storeArch);
const category = categoryMap.get(item.pkgname); const metadata = categoryMap.get(item.pkgname);
return category ? { ...item, category } : item; return metadata
? {
...item,
category: metadata.category,
...(metadata.name ? { name: metadata.name } : {}),
}
: item;
}), }),
); );
}; };

View File

@@ -91,7 +91,7 @@ const toState = (
items: snapshot.items.map((item) => ({ items: snapshot.items.map((item) => ({
taskKey: getTaskKey(item), taskKey: getTaskKey(item),
packageName: item.pkgname, packageName: item.pkgname,
displayName: item.pkgname, displayName: item.name || item.pkgname,
currentVersion: item.currentVersion, currentVersion: item.currentVersion,
newVersion: item.nextVersion, newVersion: item.nextVersion,
source: item.source, source: item.source,
@@ -191,9 +191,7 @@ export const createUpdateCenterService = (
async start(taskKeys) { async start(taskKeys) {
const snapshot = queue.getSnapshot(); const snapshot = queue.getSnapshot();
const selectedItems = snapshot.items.filter( const selectedItems = snapshot.items.filter(
(item) => (item) => taskKeys.includes(getTaskKey(item)) && !item.ignored,
taskKeys.includes(getTaskKey(item)) &&
!item.ignored,
); );
if (selectedItems.length === 0) { if (selectedItems.length === 0) {
@@ -235,7 +233,9 @@ export const createUpdateCenterService = (
webContents.send("queue-install", JSON.stringify(installTaskData)); webContents.send("queue-install", JSON.stringify(installTaskData));
// 从更新中心的 items 中移除该应用(不再显示在更新列表中) // 从更新中心的 items 中移除该应用(不再显示在更新列表中)
currentItems = currentItems.filter((i) => getTaskKey(i) !== getTaskKey(item)); currentItems = currentItems.filter(
(i) => getTaskKey(i) !== getTaskKey(item),
);
} }
// 更新队列中的 items // 更新队列中的 items

View File

@@ -7,6 +7,7 @@ export interface InstalledSourceState {
export interface UpdateCenterItem { export interface UpdateCenterItem {
pkgname: string; pkgname: string;
name?: string;
source: UpdateSource; source: UpdateSource;
currentVersion: string; currentVersion: string;
nextVersion: string; nextVersion: string;

View File

@@ -19,6 +19,12 @@ const DPKG_QUERY_INSTALLED_KEY =
const APM_PRINT_URIS_KEY = const APM_PRINT_URIS_KEY =
"bash -lc amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download spark-weather --print-uris"; "bash -lc amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download spark-weather --print-uris";
const APTSS_WEATHER_PRINT_URIS_KEY =
"bash -lc /usr/bin/apt download spark-weather --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf -o Dir::Etc::sourcelist=/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list -o Dir::Etc::sourceparts=/dev/null";
const APTSS_NOTES_PRINT_URIS_KEY =
"bash -lc /usr/bin/apt download spark-notes --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf -o Dir::Etc::sourcelist=/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list -o Dir::Etc::sourceparts=/dev/null";
const loadUpdateCenterModule = async ( const loadUpdateCenterModule = async (
remoteStore: Record<string, RemoteStoreResponse>, remoteStore: Record<string, RemoteStoreResponse>,
) => { ) => {
@@ -141,19 +147,28 @@ describe("update-center load items", () => {
stderr: "", stderr: "",
}, },
], ],
[
APTSS_WEATHER_PRINT_URIS_KEY,
{
code: 0,
stdout:
"'https://example.invalid/spark-weather_2.0.0_amd64.deb' spark-weather_2.0.0_amd64.deb 123456 SHA512:deadbeef",
stderr: "",
},
],
]); ]);
const { loadUpdateCenterItems } = await loadUpdateCenterModule({ const { loadUpdateCenterItems } = await loadUpdateCenterModule({
"https://erotica.spark-app.store/amd64-store/categories.json": { "https://erotica.spark-app.store/amd64-store/categories.json": {
tools: { zh: "Tools" }, tools: { zh: "Tools" },
}, },
"https://erotica.spark-app.store/amd64-store/tools/applist.json": [ "https://erotica.spark-app.store/amd64-store/tools/applist.json": [
{ Pkgname: "spark-weather" }, { Name: "Spark Weather", Pkgname: "spark-weather" },
], ],
"https://erotica.spark-app.store/amd64-apm/categories.json": { "https://erotica.spark-app.store/amd64-apm/categories.json": {
tools: { zh: "Tools" }, tools: { zh: "Tools" },
}, },
"https://erotica.spark-app.store/amd64-apm/tools/applist.json": [ "https://erotica.spark-app.store/amd64-apm/tools/applist.json": [
{ Pkgname: "spark-weather" }, { Name: "Spark Weather", Pkgname: "spark-weather" },
], ],
}); });
@@ -175,6 +190,7 @@ describe("update-center load items", () => {
nextVersion: "3.0.0", nextVersion: "3.0.0",
arch: "amd64", arch: "amd64",
category: "tools", category: "tools",
name: "Spark Weather",
remoteIcon: remoteIcon:
"https://erotica.spark-app.store/amd64-apm/tools/spark-weather/icon.png", "https://erotica.spark-app.store/amd64-apm/tools/spark-weather/icon.png",
downloadUrl: "https://example.invalid/spark-weather_3.0.0_amd64.deb", downloadUrl: "https://example.invalid/spark-weather_3.0.0_amd64.deb",
@@ -194,7 +210,7 @@ describe("update-center load items", () => {
office: { zh: "Office" }, office: { zh: "Office" },
}, },
"https://erotica.spark-app.store/amd64-store/office/applist.json": [ "https://erotica.spark-app.store/amd64-store/office/applist.json": [
{ Pkgname: "spark-notes" }, { Name: "Spark Notes", Pkgname: "spark-notes" },
], ],
}); });
@@ -217,6 +233,15 @@ describe("update-center load items", () => {
}; };
} }
if (key === APTSS_NOTES_PRINT_URIS_KEY) {
return {
code: 0,
stdout:
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
stderr: "",
};
}
if (key === "apm list --upgradable" || key === "apm list --installed") { if (key === "apm list --upgradable" || key === "apm list --installed") {
return { return {
code: 127, code: 127,
@@ -236,8 +261,13 @@ describe("update-center load items", () => {
nextVersion: "2.0.0", nextVersion: "2.0.0",
arch: "amd64", arch: "amd64",
category: "office", category: "office",
name: "Spark Notes",
remoteIcon: remoteIcon:
"https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png", "https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png",
downloadUrl: "https://example.invalid/spark-notes_2.0.0_amd64.deb",
fileName: "spark-notes_2.0.0_amd64.deb",
size: 654321,
sha512: "beadfeed",
}, },
]); ]);
expect(result.warnings).toEqual([ expect(result.warnings).toEqual([
@@ -289,6 +319,7 @@ describe("update-center load items", () => {
currentVersion: "1.0.0", currentVersion: "1.0.0",
nextVersion: "2.0.0", nextVersion: "2.0.0",
arch: "amd64", arch: "amd64",
name: "Spark Notes",
}, },
]); ]);
@@ -298,7 +329,7 @@ describe("update-center load items", () => {
}; };
remoteStore[ remoteStore[
"https://erotica.spark-app.store/amd64-store/office/applist.json" "https://erotica.spark-app.store/amd64-store/office/applist.json"
] = [{ Pkgname: "spark-notes" }]; ] = [{ Name: "Spark Notes", Pkgname: "spark-notes" }];
const secondResult = await loadUpdateCenterItems(runCommand); const secondResult = await loadUpdateCenterItems(runCommand);
@@ -310,6 +341,7 @@ describe("update-center load items", () => {
nextVersion: "2.0.0", nextVersion: "2.0.0",
arch: "amd64", arch: "amd64",
category: "office", category: "office",
name: "Spark Notes",
remoteIcon: remoteIcon:
"https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png", "https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png",
}, },
@@ -323,7 +355,7 @@ describe("update-center load items", () => {
tools: { zh: "Tools" }, tools: { zh: "Tools" },
}, },
"https://erotica.spark-app.store/amd64-store/office/applist.json": [ "https://erotica.spark-app.store/amd64-store/office/applist.json": [
{ Pkgname: "spark-notes" }, { Name: "Spark Notes", Pkgname: "spark-notes" },
], ],
}); });
@@ -346,6 +378,15 @@ describe("update-center load items", () => {
}; };
} }
if (key === APTSS_NOTES_PRINT_URIS_KEY) {
return {
code: 0,
stdout:
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
stderr: "",
};
}
if (key === "apm list --upgradable" || key === "apm list --installed") { if (key === "apm list --upgradable" || key === "apm list --installed") {
return { return {
code: 127, code: 127,
@@ -365,6 +406,7 @@ describe("update-center load items", () => {
nextVersion: "2.0.0", nextVersion: "2.0.0",
arch: "amd64", arch: "amd64",
category: "office", category: "office",
name: "Spark Notes",
remoteIcon: remoteIcon:
"https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png", "https://erotica.spark-app.store/amd64-store/office/spark-notes/icon.png",
}, },

View File

@@ -262,6 +262,27 @@ describe("update-center/ipc", () => {
await startPromise; await startPromise;
}); });
it("service item snapshots prefer resolved app names over package names", async () => {
const service = createUpdateCenterService({
loadItems: async () => [
{
...createItem(),
name: "Spark Weather",
},
],
});
const snapshot = await service.refresh();
expect(snapshot.items).toMatchObject([
{
taskKey: "aptss:spark-weather",
packageName: "spark-weather",
displayName: "Spark Weather",
},
]);
});
it("concurrent start calls still serialize through one processing pipeline", async () => { it("concurrent start calls still serialize through one processing pipeline", async () => {
const startedTaskIds: number[] = []; const startedTaskIds: number[] = [];
const releases: Array<() => void> = []; const releases: Array<() => void> = [];