mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
feat(update-center): add update list icons
This commit is contained in:
201
electron/main/backend/update-center/icons.ts
Normal file
201
electron/main/backend/update-center/icons.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import type { UpdateCenterItem } from "./types";
|
||||
|
||||
const APM_BASE_PATH = "/var/lib/apm/apm/files/ace-env/var/lib/apm";
|
||||
const REMOTE_ICON_BASE_URL = "https://erotica.spark-app.store";
|
||||
|
||||
const trimTrailingSlashes = (value: string): string =>
|
||||
value.replace(/\/+$/, "");
|
||||
|
||||
const readDesktopIcon = (desktopPath: string): string => {
|
||||
if (!fs.existsSync(desktopPath)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(desktopPath, "utf-8");
|
||||
const iconMatch = content.match(/^Icon=(.+)$/m);
|
||||
return iconMatch?.[1]?.trim() ?? "";
|
||||
};
|
||||
|
||||
const listPackageFiles = (pkgname: string): Set<string> => {
|
||||
const result = spawnSync("dpkg", ["-L", pkgname]);
|
||||
if (result.error || result.status !== 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return new Set(
|
||||
result.stdout
|
||||
.toString()
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0),
|
||||
);
|
||||
};
|
||||
|
||||
const findDesktopIconInDirectories = (
|
||||
directories: string[],
|
||||
pkgname: string,
|
||||
): string => {
|
||||
const packageFiles = listPackageFiles(pkgname);
|
||||
|
||||
for (const directory of directories) {
|
||||
if (!fs.existsSync(directory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(directory)) {
|
||||
if (!entry.endsWith(".desktop")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const desktopPath = path.join(directory, entry);
|
||||
if (
|
||||
!desktopPath.startsWith(`/opt/apps/${pkgname}/`) &&
|
||||
!packageFiles.has(desktopPath)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const desktopIcon = readDesktopIcon(desktopPath);
|
||||
if (!desktopIcon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedIcon = resolveIconName(desktopIcon, [
|
||||
`/usr/share/pixmaps/${desktopIcon}.png`,
|
||||
`/usr/share/icons/hicolor/48x48/apps/${desktopIcon}.png`,
|
||||
`/usr/share/icons/hicolor/scalable/apps/${desktopIcon}.svg`,
|
||||
`/opt/apps/${pkgname}/entries/icons/hicolor/48x48/apps/${desktopIcon}.png`,
|
||||
]);
|
||||
if (resolvedIcon) {
|
||||
return resolvedIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const resolveIconName = (iconName: string, candidates: string[]): string => {
|
||||
if (path.isAbsolute(iconName)) {
|
||||
return fs.existsSync(iconName) ? iconName : "";
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
export const resolveDesktopIcon = (pkgname: string): string => {
|
||||
return findDesktopIconInDirectories(
|
||||
["/usr/share/applications", `/opt/apps/${pkgname}/entries/applications`],
|
||||
pkgname,
|
||||
);
|
||||
};
|
||||
|
||||
export const resolveApmIcon = (pkgname: string): string => {
|
||||
const apmRoots = [APM_BASE_PATH, "/opt/apps"];
|
||||
|
||||
for (const apmRoot of apmRoots) {
|
||||
const desktopDirectory = path.join(
|
||||
apmRoot,
|
||||
pkgname,
|
||||
"entries",
|
||||
"applications",
|
||||
);
|
||||
if (!fs.existsSync(desktopDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const desktopFile of fs.readdirSync(desktopDirectory)) {
|
||||
if (!desktopFile.endsWith(".desktop")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const desktopIcon = readDesktopIcon(
|
||||
path.join(desktopDirectory, desktopFile),
|
||||
);
|
||||
if (!desktopIcon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedIcon = resolveIconName(desktopIcon, [
|
||||
path.join(
|
||||
apmRoot,
|
||||
pkgname,
|
||||
"entries",
|
||||
"icons",
|
||||
"hicolor",
|
||||
"48x48",
|
||||
"apps",
|
||||
`${desktopIcon}.png`,
|
||||
),
|
||||
path.join(
|
||||
apmRoot,
|
||||
pkgname,
|
||||
"entries",
|
||||
"icons",
|
||||
"hicolor",
|
||||
"scalable",
|
||||
"apps",
|
||||
`${desktopIcon}.svg`,
|
||||
),
|
||||
`/usr/share/pixmaps/${desktopIcon}.png`,
|
||||
`/usr/share/icons/hicolor/48x48/apps/${desktopIcon}.png`,
|
||||
`/usr/share/icons/hicolor/scalable/apps/${desktopIcon}.svg`,
|
||||
]);
|
||||
if (resolvedIcon) {
|
||||
return resolvedIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
export const buildRemoteFallbackIconUrl = ({
|
||||
pkgname,
|
||||
source,
|
||||
arch,
|
||||
category,
|
||||
}: Pick<
|
||||
UpdateCenterItem,
|
||||
"pkgname" | "source" | "arch" | "category"
|
||||
>): string => {
|
||||
const baseUrl = trimTrailingSlashes(REMOTE_ICON_BASE_URL);
|
||||
if (!baseUrl || !arch || !category) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const storeArch = arch.includes("-")
|
||||
? arch
|
||||
: `${arch}-${source === "aptss" ? "store" : "apm"}`;
|
||||
return `${baseUrl}/${storeArch}/${category}/${pkgname}/icon.png`;
|
||||
};
|
||||
|
||||
export const resolveUpdateItemIcon = (item: UpdateCenterItem): string => {
|
||||
const localIcon =
|
||||
item.source === "aptss"
|
||||
? resolveDesktopIcon(item.pkgname)
|
||||
: resolveApmIcon(item.pkgname);
|
||||
if (localIcon) {
|
||||
return localIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
buildRemoteFallbackIconUrl({
|
||||
pkgname: item.pkgname,
|
||||
source: item.source,
|
||||
arch: item.arch,
|
||||
category: item.category,
|
||||
}) || ""
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user