From 50fb1a00658119191a35e98413c13b39d5e5699e Mon Sep 17 00:00:00 2001 From: Elysia Date: Sun, 25 Jan 2026 22:30:39 +0800 Subject: [PATCH] feat(install): added basis install process Now it is able to install apps from the render process and properly display logs on the app detial page. --- .gitignore | 3 +- electron/main/backend/download-manager.ts | 168 ++++++++++++++++++++++ electron/main/handle-url-scheme.ts | 1 - electron/main/index.ts | 34 ++--- package.json | 6 +- src/App.vue | 52 ++----- src/components/AppCard.vue | 2 +- src/components/AppDetailModal.vue | 2 +- src/components/DownloadQueue.vue | 3 +- src/global/StoreConfig.ts | 2 - src/global/downloadStatus.ts | 4 + src/global/storeConfig.ts | 5 + src/global/typedefinition.ts | 32 +++++ src/js/processInstall.ts | 70 +++++++++ src/main.ts | 3 +- 15 files changed, 318 insertions(+), 69 deletions(-) create mode 100644 electron/main/backend/download-manager.ts delete mode 100644 src/global/StoreConfig.ts create mode 100644 src/global/downloadStatus.ts create mode 100644 src/global/storeConfig.ts create mode 100644 src/global/typedefinition.ts create mode 100644 src/js/processInstall.ts diff --git a/.gitignore b/.gitignore index f798fbc2..4fa88e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ release # lockfile package-lock.json pnpm-lock.yaml -yarn.lock \ No newline at end of file +yarn.lock +.lock diff --git a/electron/main/backend/download-manager.ts b/electron/main/backend/download-manager.ts new file mode 100644 index 00000000..33008a24 --- /dev/null +++ b/electron/main/backend/download-manager.ts @@ -0,0 +1,168 @@ +import { ipcMain, WebContents } from 'electron'; +import { spawn, ChildProcess, exec } from 'node:child_process'; +import readline from 'node:readline'; +import { promisify } from 'node:util'; +import pino from 'pino'; + +const logger = pino({ 'name': 'download-manager' }); + +type DownloadTask = { + id: number; + execCommand: string; + execParams: string[]; + process: ChildProcess | null; + webContents: WebContents | null; +}; + +const tasks = new Map(); + +let idle = true; // Indicates if the installation manager is idle + +// Listen for download requests from renderer process +ipcMain.on('queue-install', async (event, download) => { + const { id, pkgname } = download || {}; + + if (!id || !pkgname) { + logger.warn('passed arguments missing id or pkgname'); + return; + } + + logger.info(`收到下载任务: ${id}, 软件包名称: ${pkgname}`); + + // 避免重复添加同一任务 + if (tasks.has(id)) { + tasks.get(id)?.webContents.send('install-log', { + id, + time: Date.now(), + message: `任务id: ${id} 已在列表中,忽略重复添加` + }); + tasks.get(id)?.webContents.send('install-complete', { + id: id, + success: false, + time: Date.now(), + exitCode: -1, + message: `{"message":"任务id: ${id} 已在列表中,忽略重复添加","stdout":"","stderr":""}` + }); + return; + } + + const webContents = event.sender; + + // 开始组装安装命令 + const execAsync = promisify(exec); + let superUserCmd = ''; + if (process.getuid && process.getuid() !== 0) { + const { stdout, stderr } = await execAsync('which pkexec'); + if (stderr) { + logger.error('没有找到 pkexec 命令'); + return; + } + logger.info(`找到提升权限命令: ${stdout.trim()}`); + superUserCmd = stdout.trim(); + + if (superUserCmd.length === 0) { + logger.error('没有找到提升权限的命令 pkexec, 无法继续安装'); + webContents.send('install-error', { + id, + time: Date.now(), + message: '无法找到提升权限的命令 pkexec,请手动安装' + }); + return; + } + } + + let execCommand = ''; + let execParams = []; + if (superUserCmd.length > 0) { + execCommand = superUserCmd; + execParams.push('/usr/bin/apm'); + } else { + execCommand = '/usr/bin/apm'; + } + execParams.push('install', '-y', pkgname); + + const task: DownloadTask = { + id, + execCommand, + execParams, + process: null, + webContents + }; + tasks.set(id, task); + if (idle) processNextInQueue(0); +}); + +function processNextInQueue(index: number) { + if (!idle) return; + + idle = false; + const task = Array.from(tasks.values())[index]; + const webContents = task.webContents; + let stdoutData = ''; + let stderrData = ''; + + webContents.send('install-status', { + id: task.id, + time: Date.now(), + message: 'installing' + }) + webContents.send('install-log', { + id: task.id, + time: Date.now(), + message: `开始执行: ${task.execCommand} ${task.execParams.join(' ')}` + }); + logger.info(`启动安装命令: ${task.execCommand} ${task.execParams.join(' ')}`); + + const child = spawn(task.execCommand, task.execParams, { + shell: true, + env: process.env + }); + task.process = child; + + // 监听 stdout + child.stdout.on('data', (data) => { + stdoutData += data.toString(); + webContents.send('install-log', { + id: task.id, + time: Date.now(), + message: data.toString() + }); + }); + + // 监听 stderr + child.stderr.on('data', (data) => { + stderrData += data.toString(); + webContents.send('install-log', { + id: task.id, + time: Date.now(), + message: data.toString() + }); + }); + child.on('close', (code) => { + const success = code === 0; + // 拼接json消息 + const messageJSONObj = { + message: success ? '安装完成' : `安装失败,退出码 ${code}`, + stdout: stdoutData, + stderr: stderrData + }; + + if (success) { + logger.info(messageJSONObj); + } else { + logger.error(messageJSONObj); + } + + webContents.send('install-complete', { + id: task.id, + success: success, + time: Date.now(), + exitCode: code, + message: JSON.stringify(messageJSONObj) + }); + tasks.delete(task.id); + idle = true; + if (tasks.size > 0) + processNextInQueue(0); + }); +} \ No newline at end of file diff --git a/electron/main/handle-url-scheme.ts b/electron/main/handle-url-scheme.ts index af5fd875..0f7216c8 100644 --- a/electron/main/handle-url-scheme.ts +++ b/electron/main/handle-url-scheme.ts @@ -1,4 +1,3 @@ -import { dialog } from 'electron' import { deepLink } from './deeplink'; deepLink.on("event", (query) => { diff --git a/electron/main/index.ts b/electron/main/index.ts index a4b3c2cd..0be3d0bb 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,16 +1,16 @@ -import { app, BrowserWindow, shell, ipcMain, dialog } from 'electron' +import { app, BrowserWindow, shell } from 'electron' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import path from 'node:path' import os from 'node:os' -import './handle-url-scheme.js' - // Assure single instance application if (!app.requestSingleInstanceLock()) { app.exit(0); } +import './handle-url-scheme.js' +import './backend/download-manager.js' const require = createRequire(import.meta.url) const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -111,18 +111,18 @@ app.on('activate', () => { }) // New window example arg: new windows url -ipcMain.handle('open-win', (_, arg) => { - const childWindow = new BrowserWindow({ - webPreferences: { - preload, - nodeIntegration: true, - contextIsolation: false, - }, - }) +// ipcMain.handle('open-win', (_, arg) => { +// const childWindow = new BrowserWindow({ +// webPreferences: { +// preload, +// nodeIntegration: true, +// contextIsolation: false, +// }, +// }) - if (VITE_DEV_SERVER_URL) { - childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`) - } else { - childWindow.loadFile(indexHtml, { hash: arg }) - } -}) +// if (VITE_DEV_SERVER_URL) { +// childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`) +// } else { +// childWindow.loadFile(indexHtml, { hash: arg }) +// } +// }) diff --git a/package.json b/package.json index 4dbc667f..3e8f1cf0 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "type": "module", "scripts": { - "dev": "vite --mode debug", + "dev": "vite --mode debug | pino-pretty", "build": "vue-tsc --noEmit && vite build --mode production && electron-builder", "preview": "vite preview --mode debug" }, @@ -31,6 +31,7 @@ "electron": "^39.2.7", "electron-app-universal-protocol-client": "github:witcher112/electron-app-universal-protocol-client", "electron-builder": "^24.13.3", + "pino-pretty": "^13.1.3", "typescript": "^5.4.2", "vite": "^5.1.5", "vite-plugin-electron": "^0.28.4", @@ -39,6 +40,7 @@ "vue-tsc": "^2.0.6" }, "dependencies": { - "axios": "^1.13.2" + "axios": "^1.13.2", + "pino": "^10.3.0" } } diff --git a/src/App.vue b/src/App.vue index 74aa38bd..bb115d76 100644 --- a/src/App.vue +++ b/src/App.vue @@ -29,6 +29,8 @@