|
1 | | -import { readdirSafe, readFileSafe } from "../main/utils"; |
2 | | -import type { AppInfo } from "../reducers/app"; |
3 | 1 | import type { AppReader } from "./utils"; |
4 | 2 | import fs from "fs"; |
5 | 3 | import ini from "ini"; |
6 | 4 | import path from "path"; |
| 5 | +import { Result } from "ts-results"; |
7 | 6 |
|
8 | 7 | const desktopFilesDir = "/usr/share/applications"; |
9 | 8 |
|
10 | | -async function readAppInfo(desktopFile: string): Promise<AppInfo | undefined> { |
11 | | - const content = await readFileSafe(desktopFile); |
| 9 | +const readAppInfo = (desktopFile: string) => |
| 10 | + Result.wrapAsync(async () => { |
| 11 | + const content = await fs.promises.readFile(desktopFile, { |
| 12 | + encoding: "utf-8", |
| 13 | + }); |
| 14 | + const entry = ini.parse(content)["Desktop Entry"] as |
| 15 | + | { |
| 16 | + Name?: string; |
| 17 | + Icon?: string; |
| 18 | + Exec?: string; |
| 19 | + } |
| 20 | + | undefined; |
12 | 21 |
|
13 | | - const entry = ini.parse(content)["Desktop Entry"] as { |
14 | | - Name?: string; |
15 | | - Icon?: string; |
16 | | - Exec?: string; |
17 | | - }; |
18 | | - if (!entry || !entry.Exec) return; |
| 22 | + if (!entry?.Exec) throw new Error("Exec not found"); |
19 | 23 |
|
20 | | - let exePath = ""; |
21 | | - if (entry.Exec.startsWith('"')) { |
22 | | - exePath = entry.Exec.replace(/^"(.*)".*/, "$1"); |
23 | | - } else { |
24 | | - // Remove arg |
25 | | - exePath = entry.Exec.split(/\s+/)[0] ?? ""; |
26 | | - } |
27 | | - |
28 | | - if (!exePath.startsWith("/")) return; |
29 | | - |
30 | | - if (!fs.existsSync(path.join(exePath, "../resources/electron.asar"))) return; |
| 24 | + let exePath = ""; |
| 25 | + if (entry.Exec.startsWith('"')) { |
| 26 | + exePath = entry.Exec.replace(/^"(.*)".*/, "$1"); |
| 27 | + } else { |
| 28 | + // Remove arg |
| 29 | + exePath = entry.Exec.split(/\s+/)[0] ?? ""; |
| 30 | + } |
31 | 31 |
|
32 | | - let icon = ""; |
33 | | - if (entry.Icon) { |
34 | | - try { |
35 | | - const iconBuffer = await fs.promises.readFile( |
36 | | - `/usr/share/icons/hicolor/1024x1024/apps/${entry.Icon}.png`, |
37 | | - ); |
38 | | - icon = "data:image/png;base64," + iconBuffer.toString("base64"); |
39 | | - } catch (err) { |
40 | | - console.error(err); |
| 32 | + if (!exePath.startsWith("/")) { |
| 33 | + throw new Error("Exec path invalid"); |
| 34 | + } |
| 35 | + if (!fs.existsSync(path.join(exePath, "../resources/electron.asar"))) { |
| 36 | + throw new Error("resources/electron.asar not exists"); |
41 | 37 | } |
42 | | - } |
43 | 38 |
|
44 | | - return { |
45 | | - id: exePath, |
46 | | - icon: icon, // TODO: Read icon |
47 | | - name: entry.Name || path.basename(exePath), |
48 | | - exePath: exePath, |
49 | | - }; |
50 | | -} |
| 39 | + let icon = ""; |
| 40 | + if (entry.Icon) { |
| 41 | + try { |
| 42 | + const iconBuffer = await fs.promises.readFile( |
| 43 | + `/usr/share/icons/hicolor/1024x1024/apps/${entry.Icon}.png`, |
| 44 | + ); |
| 45 | + icon = "data:image/png;base64," + iconBuffer.toString("base64"); |
| 46 | + } catch (err) { |
| 47 | + console.error(err); |
| 48 | + } |
| 49 | + } |
51 | 50 |
|
52 | | -export const adapter: AppReader = { |
53 | | - async readAll() { |
54 | | - const files = await readdirSafe(desktopFilesDir); |
55 | | - const apps = await Promise.all( |
56 | | - files.map((file) => { |
57 | | - if (!file.endsWith(".desktop")) return; |
58 | | - return readAppInfo(path.join(desktopFilesDir, file)); |
59 | | - }), |
60 | | - ); |
61 | | - return apps; |
62 | | - }, |
63 | | - async readByPath(p: string) { |
64 | 51 | return { |
65 | | - id: p, |
66 | | - name: path.basename(p), |
67 | | - icon: "", |
68 | | - exePath: p, |
| 52 | + id: exePath, |
| 53 | + icon: icon, // TODO: Read icon |
| 54 | + name: entry.Name || path.basename(exePath), |
| 55 | + exePath: exePath, |
69 | 56 | }; |
70 | | - }, |
| 57 | + }); |
| 58 | + |
| 59 | +export const adapter: AppReader = { |
| 60 | + readAll: () => |
| 61 | + Result.wrapAsync(async () => { |
| 62 | + const files = await fs.promises.readdir(desktopFilesDir); |
| 63 | + const apps = await Promise.all( |
| 64 | + files |
| 65 | + .filter((f) => f.endsWith(".desktop")) |
| 66 | + .map((file) => readAppInfo(path.join(desktopFilesDir, file))), |
| 67 | + ); |
| 68 | + return apps.flatMap((app) => (app.ok ? [app.unwrap()] : [])); |
| 69 | + }), |
| 70 | + readByPath: (p: string) => |
| 71 | + Result.wrapAsync(async () => { |
| 72 | + // TODO: |
| 73 | + return { |
| 74 | + id: p, |
| 75 | + name: path.basename(p), |
| 76 | + icon: "", |
| 77 | + exePath: p, |
| 78 | + }; |
| 79 | + }), |
71 | 80 | }; |
0 commit comments