diff --git a/package.json b/package.json index cbd9cfd..7bfe722 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "HugoAura", - "version": "0.1.1-pre-III", + "version": "0.1.1-pre-IV", "description": "Aura for SeewoHugo", "main": "app.asar/main.js", "dependencies": {}, diff --git a/src/aura/init/main/ipcHandler.js b/src/aura/init/main/ipcHandler.js index 759aea1..8cdddcc 100755 --- a/src/aura/init/main/ipcHandler.js +++ b/src/aura/init/main/ipcHandler.js @@ -74,6 +74,7 @@ const buildIpcMain = (electron) => { }; const { applyConfigIpcHandler } = require("./ipcModules/configIpcHandler"); + const { applyFsIpcHandler } = require("./ipcModules/fsIpcHandler"); const { applyPlsIpcHandler } = require("./ipcModules/plsIpcHandler"); ipcMain.handle("$aura.base.restartApplication", async () => { @@ -82,6 +83,7 @@ const buildIpcMain = (electron) => { }); applyConfigIpcHandler(ipcMain); + applyFsIpcHandler(ipcMain); applyPlsIpcHandler(ipcMain); }; diff --git a/src/aura/init/main/ipcModules/fsIpcHandler.js b/src/aura/init/main/ipcModules/fsIpcHandler.js new file mode 100644 index 0000000..6d0727a --- /dev/null +++ b/src/aura/init/main/ipcModules/fsIpcHandler.js @@ -0,0 +1,186 @@ +// @ts-check + +const __SCOPE = "main"; + +const { exec } = require("child_process"); +const nodeHttp = require("http"); +const nodeHttps = require("https"); +const fs = require("fs"); +const path = require("path"); + +const { genRandomHex } = require("../../../utils/crypto"); + +const composableFunctions = { + /** + * + * @param {string} url + * @param {string} targetPath + * @param {((arg: DownloadTask) => any)} progressCallback + */ + downloadFile: async (url, targetPath, progressCallback) => { + if (!progressCallback) return false; + const taskId = genRandomHex(); + + /** + * @type {DownloadTask} + */ + const failedTemplate = { + id: taskId, + progress: 100, + status: "failed", + dlUrl: url, + savePath: targetPath, + message: "", + }; + + if (!url || !targetPath) { + failedTemplate.message = "Invalid arg"; + progressCallback(failedTemplate); + return false; + } + if (!fs.existsSync(path.dirname(targetPath))) { + failedTemplate.message = "Path not exists"; + progressCallback(failedTemplate); + } + const httpModuleIns = url.startsWith("https") ? nodeHttps : nodeHttp; + + global.__HUGO_AURA__.fsTasks?.downloadTasks.set(taskId, { + status: "waiting", + cancelReq: null, + }); + + const fsStream = fs.createWriteStream(targetPath); + + const dlReq = httpModuleIns.get(url, (response) => { + if (response.statusCode !== 200) { + fsStream.close(); + failedTemplate.message = `Request error: HTTP ${response.statusCode}`; + progressCallback(failedTemplate); + return false; + } + + const contentLength = response.headers["content-length"]; + // @ts-expect-error + const totalBytes = parseInt(contentLength, 10) || 0; // No error handling 😆 + let curRecvBytes = 0; + + global.__HUGO_AURA__.fsTasks?.downloadTasks.set(taskId, { + status: "progressing", + cancelReq: () => { + dlReq.destroy(); + fsStream.close(); + fs.unlink(targetPath, () => {}); + global.__HUGO_AURA__.fsTasks?.downloadTasks.delete(taskId); + progressCallback({ + id: taskId, + progress: 100, + curBytes: curRecvBytes, + totalBytes: totalBytes, + status: "cancelled", + dlUrl: url, + savePath: targetPath, + }); + }, + }); + + response.on("data", (chunk) => { + curRecvBytes += chunk.length; + const curProgress = + totalBytes > 0 ? (curRecvBytes / totalBytes) * 100 : 0; + progressCallback({ + id: taskId, + progress: curProgress.toFixed(2), + curBytes: curRecvBytes, + totalBytes: totalBytes, + status: "progressing", + dlUrl: url, + savePath: targetPath, + }); + }); + + response.pipe(fsStream); + + fsStream.on("finish", () => { + fsStream.close(); + progressCallback({ + id: taskId, + progress: (100).toFixed(2), + curBytes: curRecvBytes, + totalBytes: totalBytes, + status: "done", + dlUrl: url, + savePath: targetPath, + }); + }); + + global.__HUGO_AURA__.fsTasks?.downloadTasks.delete(taskId); + return true; + }); + + dlReq.on("error", (e) => { + fsStream.close(); + fs.unlink(targetPath, () => {}); + failedTemplate.message = + "Request error: Unexpected error while downloading file"; + failedTemplate.errorObj = e; + progressCallback(failedTemplate); + global.__HUGO_AURA__.fsTasks?.downloadTasks.delete(taskId); + return false; + }); + }, +}; + +/** + * + * @param {import("electron").IpcMain} ipcMain + */ +const applyFsIpcHandler = (ipcMain) => { + const methodBase = "$aura.fs"; + + global.__HUGO_AURA__.fsTasks = { + downloadTasks: new Map(), + }; + + ipcMain.handle( + `${methodBase}.dl.cancelDownloadTask`, + /** + * + * @param {import("electron").IpcMainInvokeEvent} _evt + * @param {{ targetTaskId: string }} arg + * @returns {{ success: boolean, error: string | null }} + */ + (_evt, arg) => { + if (!arg.targetTaskId) { + return { + success: false, + error: "ARG_INVALID", + }; + } + + if (!global.__HUGO_AURA__.fsTasks?.downloadTasks.has(arg.targetTaskId)) { + return { + success: false, + error: "TASK_ID_NOT_FOUND", + }; + } + + const taskObj = global.__HUGO_AURA__.fsTasks.downloadTasks.get( + arg.targetTaskId + ); + if (!taskObj?.cancelReq) { + return { + success: false, + error: "TASK_NOT_STARTED", + }; + } + + taskObj.cancelReq(); + return { + success: true, + error: null, + }; + } + ); +}; + +module.exports = { fsComposables: composableFunctions, applyFsIpcHandler }; diff --git a/src/aura/init/main/ipcModules/plsIpcHandler.js b/src/aura/init/main/ipcModules/plsIpcHandler.js index 20cbb05..cffd88b 100755 --- a/src/aura/init/main/ipcModules/plsIpcHandler.js +++ b/src/aura/init/main/ipcModules/plsIpcHandler.js @@ -5,6 +5,8 @@ const __SCOPE = "main"; const { exec } = require("child_process"); const fs = require("fs"); const path = require("path"); +const nodeHttps = require("https"); +const { fsComposables } = require("./fsIpcHandler"); const functions = { querySvcDetail: ( @@ -47,8 +49,9 @@ const functions = { * @returns {Promise<{ success: boolean, errorObj?: Error }>} */ execCommand: (logHeader, binPath, command) => { + const processedPath = binPath.includes(" ") ? `"${binPath}"` : binPath; return new Promise((resolve) => { - exec(`"${binPath}" ${command}`, (error, stdout, stderr) => { + exec(`${processedPath} ${command}`, (error, stdout, stderr) => { if (error) { console.error(`${logHeader} Failed to execute command:`, error); resolve({ success: false, errorObj: error }); @@ -58,6 +61,103 @@ const functions = { }); }); }, + + /** + * + * @param {"stable" | "alpha"} channel + * @param {(arg: DownloadTask) => any} callbackFn + * @param {string} binPath + */ + handlePLSDownload: async (channel, callbackFn, binPath) => { + // TODO: Channel selection + const apiInfo = global.__HUGO_AURA_API__; + let plsVersionInfo = {}; + + const getVerPromise = new Promise((resolve) => { + // ↓ 目前 channel param 没有什么用处 + nodeHttps + .get( + `${apiInfo.baseUrl}${apiInfo.plsUpdate}?channel=${channel}`, + (rep) => { + let dataChunk = ""; + rep.on("data", (chunk) => { + dataChunk += chunk; + }); + + rep.on("end", () => { + resolve({ + success: true, + data: dataChunk, + }); + }); + } + ) + .on("error", (e) => { + resolve({ + success: false, + data: null, + errorObj: e, + }); + }); + }); + + const rawResInfo = await getVerPromise; + if (!rawResInfo.success) { + callbackFn({ + id: "", + progress: 0, + status: "failed", + dlUrl: null, + savePath: null, + message: "未能获取 PLS 版本信息", + errorObj: rawResInfo.errorObj ? rawResInfo.errorObj : null, + }); + return false; + } + + try { + plsVersionInfo = JSON.parse(rawResInfo.data); + } catch (e) { + callbackFn({ + id: "", + progress: 0, + status: "failed", + dlUrl: null, + savePath: null, + message: "PLS 版本信息解析失败", + errorObj: e, + }); + console.error( + "[HugoAura / IPC / PLS] Error querying PLS version info:", + e + ); + return false; + } + + let deviceArch = process.env.PROCESSOR_ARCHITEW6432 + ? process.env.PROCESSOR_ARCHITEW6432 + : process.env.PROCESSOR_ARCHITECTURE; + // @ts-expect-error + deviceArch = deviceArch.toLowerCase(); + + if (!Object.keys(plsVersionInfo.data.downloadUrl).includes(deviceArch)) { + callbackFn({ + id: "", + progress: 0, + status: "failed", + dlUrl: null, + savePath: null, + message: `处理器架构识别失败, 检测到的架构: ${deviceArch}`, + }); + return false; + } + + fsComposables.downloadFile( + plsVersionInfo.data.downloadUrl[deviceArch], + binPath, + callbackFn + ); + }, }; /** @@ -67,8 +167,8 @@ const functions = { const applyPlsIpcHandler = (ipcMain) => { const methodBase = "$aura.pls"; - const PLS_INSTALL_DIR = path.join("C:\\Program Files", "HugoAura PLS"); - const PLS_BIN_PATH = path.join(PLS_INSTALL_DIR, "bin", "HugoAura-PLS.exe"); + const PLS_INSTALL_DIR = path.join("C:\\Program Files", "HugoAura PLS", "bin"); + const PLS_BIN_PATH = path.join(PLS_INSTALL_DIR, "HugoAura-PLS.exe"); const PLS_SVC_NAME = "HugoAuraPLS"; const isPlsDetached = process.argv.includes("--pls-detach"); @@ -106,6 +206,48 @@ const applyPlsIpcHandler = (ipcMain) => { } ); + ipcMain.handle( + `${methodBase}.ensurePlsInstallDir`, + /** + * + * @param {import("electron").IpcMainInvokeEvent} _event + * @param {any} _arg + * @returns {{ success: boolean; data: { alreadyExists: boolean; createdDir: string; }; error?: Error }} + */ + (_event, _arg) => { + const alreadyExists = fs.existsSync(PLS_INSTALL_DIR); + if (alreadyExists) { + return { + success: true, + data: { + alreadyExists: true, + createdDir: PLS_INSTALL_DIR, + }, + }; + } + + try { + fs.mkdirSync(PLS_INSTALL_DIR); + return { + success: true, + data: { + alreadyExists: false, + createdDir: PLS_INSTALL_DIR, + }, + }; + } catch (error) { + return { + success: false, + data: { + alreadyExists: false, + createdDir: PLS_INSTALL_DIR, + }, + error: error, + }; + } + } + ); + ipcMain.handle( `${methodBase}.getPlsStats`, /** @@ -294,7 +436,7 @@ const applyPlsIpcHandler = (ipcMain) => { return await functions.execCommand( logHeader, PLS_BIN_PATH, - "install" + "--startup auto install" ); case "rmSvc": return await functions.execCommand(logHeader, PLS_BIN_PATH, "remove"); @@ -302,12 +444,59 @@ const applyPlsIpcHandler = (ipcMain) => { return await functions.execCommand(logHeader, PLS_BIN_PATH, "start"); case "stopSvc": return await functions.execCommand(logHeader, PLS_BIN_PATH, "stop"); + case "rmBin": + const unlinkPromise = new Promise((resolve) => { + fs.unlink(PLS_BIN_PATH, (error) => { + if (error) { + resolve({ + success: false, + errorObj: error, + }); + return false; + } + + resolve({ + success: true, + errorObj: null, + }); + return true; + }); + }); + + const unlinkRet = await unlinkPromise; + + return unlinkRet; default: return { success: false, errorObj: new Error("Method not found") }; } } ); + ipcMain.handle( + `${methodBase}.downloadPls`, + /** + * + * @param {import("electron").IpcMainInvokeEvent} _evt + * @param {{channel?: "stable" | "alpha", reportTo?: import("../../../types/main/core").WindowName}} arg + * @returns {void} + */ + (_evt, arg) => { + const channel = arg.channel ? arg.channel : "stable"; + const reportWin = arg.reportTo ? arg.reportTo : "assistant"; + functions.handlePLSDownload( + channel, + (status) => { + ipcMain.send( + reportWin, + `${methodBase}.post.reportPlsDownloadStatus`, + status + ); + }, + PLS_BIN_PATH + ); + } + ); + ipcMain.handle( `${methodBase}.retryPlsConnect`, /** diff --git a/src/aura/types/main/ipc/fs.d.ts b/src/aura/types/main/ipc/fs.d.ts new file mode 100644 index 0000000..ff509d1 --- /dev/null +++ b/src/aura/types/main/ipc/fs.d.ts @@ -0,0 +1,21 @@ +type DownloadTaskID = string; +type DownloadTaskStatus = "waiting" | "progressing" | "done" | "failed" | "cancelled"; + +interface DownloadTask { + id: DownloadTaskID; + progress: float; + curBytes?: number; + totalBytes?: number; + status: DownloadTaskStatus; + dlUrl: string | null; + savePath: string | null; + message?: string; + errorObj?: Error; +} + +interface FSTasks { + downloadTasks: Map< + DownloadTaskID, + { status: DownloadTaskStatus; cancelReq: any } + >; +} diff --git a/src/aura/types/shared/global.d.ts b/src/aura/types/shared/global.d.ts index f4f2be4..8ef256f 100755 --- a/src/aura/types/shared/global.d.ts +++ b/src/aura/types/shared/global.d.ts @@ -11,6 +11,7 @@ type RendererProcessOnlyVal = T; interface GlobalHugoAuraInfo { central?: MainProcessOnlyVal<(...args: any) => any>; configInit: boolean; + fsTasks?: MainProcessOnlyVal; hookedWindows?: MainProcessOnlyVal; ipcInit?: MainProcessOnlyVal; plsRules?: Record | null; @@ -22,12 +23,19 @@ interface GlobalHugoAuraInfo { version: RendererProcessOnlyVal; } +interface GlobalHugoAuraApiInfo { + baseUrl: string; + plsUpdate: string; + auraUpdate: string; +} + type GlobalHugoAuraConfig = AuraConfig; declare global { var ipcRenderer: RendererProcessOnlyVal; var _ACCEPT_DATA: any; var __HUGO_AURA__: GlobalHugoAuraInfo; + var __HUGO_AURA_API__: GlobalHugoAuraApiInfo; var __HUGO_AURA_CONFIG__: GlobalHugoAuraConfig; var __HUGO_AURA_CONFIG_MGR__: ConfigManager; var __HUGO_AURA_EVENT_BUS__: EventBus; diff --git a/src/aura/types/shared/pls/status.d.ts b/src/aura/types/shared/pls/status.d.ts index 0227676..113d2d8 100755 --- a/src/aura/types/shared/pls/status.d.ts +++ b/src/aura/types/shared/pls/status.d.ts @@ -12,4 +12,4 @@ interface PLSStatus { type PLSLifecycleType = "isDetached" | "isSvcInstalled" | "isSvcStart"; -type PLSLifecycleControlType = "instSvc" | "rmSvc" | "startSvc" | "stopSvc"; +type PLSLifecycleControlType = "instSvc" | "rmSvc" | "startSvc" | "stopSvc" | "rmBin" | "dlBin"; diff --git a/src/aura/ui/composables/plsConfigManager.js b/src/aura/ui/composables/plsConfigManager.js index 2154c75..6555300 100755 --- a/src/aura/ui/composables/plsConfigManager.js +++ b/src/aura/ui/composables/plsConfigManager.js @@ -28,18 +28,7 @@ const updatePlsRulesFromLocal = async () => { return plsRules; }; -/** - * - * @returns {string} - */ -const genRandomHex = () => { - let result = ""; - for (let i = 0; i < 8; i++) { - const randomNum = Math.floor(Math.random() * 0x10000); - result += randomNum.toString(16).padStart(4, "0"); - } - return result; -}; +const { genRandomHex } = require("../../utils/crypto"); /** * diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js index ff13849..4720b94 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/plsStatus.js @@ -96,11 +96,19 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) }; break; case "Download": + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.downloadPLSBin(); + }; break; // ↓ 这边的确可以把这些全都合并到一个可复用 fn 里去, 但没必要 + // 如果后续要引入错误视觉反馈, 合并到单个 fn 反而会增加实现复杂度 case "Install": btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateOperationBtnStatus( + "Install", + true + ); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( "info", "正在请求安装", @@ -137,44 +145,116 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) }; break; case "Uninstall": - btnEl.onclick = async () => { - global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( - "info", - "正在请求卸载", - null, - false, - false, - null - ); - const ret = await ipcRenderer.invoke( - `${IPC_METHOD_BASE}.plsLifecycleControl`, - { target: "rmSvc" } - ); - if (ret.success) { - lifecycleStatus.svcInstalled = false; - global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( - "success", - "服务卸载成功", - null, - true, - true, - 2000 + if (btnContent === "删除内核") { + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateOperationBtnStatus( + "Uninstall", + true ); - } else { global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( - "error", - "服务卸载失败", - "

检查日志以获取详细信息

", - true, + "warning", + "正在删除内核", + null, + false, false, null ); - } - global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); - }; + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "rmBin" } + ); + if (ret.success) { + lifecycleStatus.installed = false; + lifecycleStatus.svcInstalled = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "内核已删除", + null, + true, + true, + 2000 + ); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "内核删除失败", + "

检查日志以获取详细信息

", + true, + false, + null + ); + } + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + }; + } else { + btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateOperationBtnStatus( + "Uninstall", + true + ); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "info", + lifecycleStatus.svcRunning + ? "正在停止服务并卸载" + : "正在请求卸载", + null, + false, + false, + null + ); + if (lifecycleStatus.svcRunning) { + const stopRet = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "stopSvc" } + ); + if (!stopRet.success) { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "服务卸载失败: 无法停止服务", + "

检查日志以获取详细信息

您可以尝试手动停止 PLS 服务

", + true, + false, + null + ); + } else { + lifecycleStatus.svcRunning = false; + } + } + const ret = await ipcRenderer.invoke( + `${IPC_METHOD_BASE}.plsLifecycleControl`, + { target: "rmSvc" } + ); + if (ret.success) { + lifecycleStatus.svcInstalled = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "服务卸载成功", + null, + true, + true, + 2000 + ); + } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "服务卸载失败", + "

检查日志以获取详细信息

", + true, + false, + null + ); + } + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); + }; + } + break; case "Start": btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateOperationBtnStatus( + "Start", + true + ); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( "info", "正在请求启动", @@ -202,6 +282,7 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) await ipcRenderer.invoke(`${IPC_METHOD_BASE}.retryPlsConnect`); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); } else { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( "error", "PLS 启动失败", @@ -215,6 +296,10 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) break; case "Stop": btnEl.onclick = async () => { + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateOperationBtnStatus( + "Stop", + true + ); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( "info", "正在请求停止", @@ -229,7 +314,6 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) ); if (ret.success) { lifecycleStatus.svcRunning = false; - global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( "success", "PLS 已停止", @@ -247,6 +331,7 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) false, null ); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); } }; break; @@ -267,17 +352,26 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) if (!lifecycleStatus.svcInstalled) { updateStatusEl(acIdInst, atIdInst, "WARNING", "已下载, 服务未安装"); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", false); - GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", true); + GLOBAL_FUNCTIONS.updateOperationBtnStatus( + "Uninstall", + false, + "删除内核" + ); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Stop", true); } else { updateStatusEl(acIdInst, atIdInst, "SUCCESS", "已安装"); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true); - GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", false); + GLOBAL_FUNCTIONS.updateOperationBtnStatus( + "Uninstall", + false, + "卸载服务" + ); } break; case false: updateStatusEl(acIdInst, atIdInst, "PENDING", "未下载"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Download", false); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Uninstall", true); GLOBAL_FUNCTIONS.updateOperationBtnStatus("Start", true); @@ -343,6 +437,14 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) lifecycleStatus.installed = true; } else { lifecycleStatus.installed = false; + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "error", + "请下载 PLS 内核以继续", + null, + true, + true, + 3000 + ); global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateStatusContent(); return; } @@ -410,8 +512,93 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) ); } else if (result.success && result.status === "Already") { updateOperationBtnStatus("Refresh", false, "刷新状态"); + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus.updateToast( + "success", + "更新成功", + null, + true, + true, + 3000 + ); } }, + + downloadPLSBin: async () => { + const GLOBAL_FUNCTIONS = + global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.plsStatus; + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Download", true, "正在检查"); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Refresh", true); + const CUR_CHANNEL = `${IPC_METHOD_BASE}.post.reportPlsDownloadStatus`; + await ipcRenderer.invoke(`${IPC_METHOD_BASE}.ensurePlsInstallDir`); + GLOBAL_FUNCTIONS.updateToast( + "info", + "准备开始下载...", + null, + true, + true, + 2000 + ); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Download", true, "等待下载"); + + const callbackFn = (_evt, info) => { + switch (info.status) { + case "failed": + GLOBAL_FUNCTIONS.updateToast( + "error", + "下载失败", + `

${ + info.message ? info.message : "检查日志以获取错误信息" + }

`, + true, + true, + 5000 + ); + ipcRenderer.off(CUR_CHANNEL, callbackFn); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Refresh", false); + break; + case "done": + GLOBAL_FUNCTIONS.updateToast( + "success", + "下载成功", + null, + true, + true, + 2500 + ); + ipcRenderer.off(CUR_CHANNEL, callbackFn); + GLOBAL_FUNCTIONS.updateOperationBtnStatus("Refresh", false); + GLOBAL_FUNCTIONS.updateOperationBtnStatus( + "Download", + true, + "下载内核" + ); + lifecycleStatus.installed = true; + GLOBAL_FUNCTIONS.updateStatusContent(); + break; + case "waiting": + GLOBAL_FUNCTIONS.updateOperationBtnStatus( + "Download", + true, + "等待中..." + ); + break; + case "progressing": + GLOBAL_FUNCTIONS.updateOperationBtnStatus( + "Download", + true, + `下载 ${Math.round(info.progress)}%` + ); + break; + } + }; + + ipcRenderer.on(CUR_CHANNEL, callbackFn); + + ipcRenderer.invoke(`$aura.pls.downloadPls`, { + channel: "stable", + reportTo: "assistant", + }); + }, }; const GLOBAL_FUNCTIONS = diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js b/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js index 257ae6f..f0ed0f6 100755 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/settings/basic.js @@ -83,7 +83,7 @@ const basicSettings = [ restartPLS: true, tip: true, tipTitle: - '路径相对于 "%USERPROFILE%\\Documents\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', + '路径相对于 "%PROGRAMDATA%\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', associateVal: null, auraIf: () => true, defaultValue: "", @@ -118,7 +118,7 @@ const basicSettings = [ restartPLS: true, tip: true, tipTitle: - '路径相对于 "%USERPROFILE%\\Documents\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', + '路径相对于 "%PROGRAMDATA%\\HugoAura\\Aura-PLS\\", 使用 "/" 作为路径符', warning: true, warningContent: "请使用 PEM 格式的密钥", associateVal: null, diff --git a/src/aura/utils/crypto.js b/src/aura/utils/crypto.js new file mode 100644 index 0000000..0779e55 --- /dev/null +++ b/src/aura/utils/crypto.js @@ -0,0 +1,14 @@ +/** + * + * @returns {string} + */ +const genRandomHex = () => { + let result = ""; + for (let i = 0; i < 8; i++) { + const randomNum = Math.floor(Math.random() * 0x10000); + result += randomNum.toString(16).padStart(4, "0"); + } + return result; +}; + +module.exports = { genRandomHex }; diff --git a/src/core/hook.js b/src/core/hook.js index a4c450d..2bde0ae 100755 --- a/src/core/hook.js +++ b/src/core/hook.js @@ -16,6 +16,16 @@ if (!global.__HUGO_AURA__) { global.__HUGO_AURA__ = __HUGO_AURA__; } +if (!global.__HUGO_AURA_API__) { + /** @type {import("../aura/types/shared/global").GlobalHugoAuraApiInfo} */ + const __HUGO_AURA_API__ = { + baseUrl: "https://api-aura.xwx.li", + plsUpdate: "/api/v1/getPLSLatestVersion", + auraUpdate: "/api/v1/getAuraLatestVersion", + }; + global.__HUGO_AURA_API__ = __HUGO_AURA_API__; +} + if (!global.__HUGO_AURA_CONFIG__) { global.__HUGO_AURA_CONFIG__ = {}; } @@ -134,7 +144,8 @@ const launcher = ({ central, windowName, config }) => { configManager.ensureConfigExists(); const loadedConfig = configManager.loadConfig(); if (!global.__HUGO_AURA__.configInit) global.__HUGO_AURA__.configInit = true; - if (!global.__HUGO_AURA_CONFIG_MGR__) global.__HUGO_AURA_CONFIG_MGR__ = configManager; + if (!global.__HUGO_AURA_CONFIG_MGR__) + global.__HUGO_AURA_CONFIG_MGR__ = configManager; global.__HUGO_AURA_CONFIG__ = loadedConfig; diff --git a/src/core/preload.js b/src/core/preload.js index d06f97e..3b1766a 100755 --- a/src/core/preload.js +++ b/src/core/preload.js @@ -1,6 +1,6 @@ // @ts-check -const __AURA_VERSION__ = "0.1.1-pre-III"; +const __AURA_VERSION__ = "0.1.1-pre-IV"; (() => { if (require.main) return; // 如果只是导入 Aura Version, 不运行闭包逻辑