Browse Source
添加热更新功能,包括下载更新包、解压、标记更新状态及触发更新流程。优化命令处理逻辑,支持返回命令是否存在及执行结果。更新本地化配置,添加热更新相关文案。删除不再使用的热更新生成脚本。feat/icon
14 changed files with 237 additions and 146 deletions
@ -1,8 +1,8 @@ |
|||||
import { broadcast } from "main/utils" |
import { broadcast } from "main/utils" |
||||
import { AllKeys } from "../common" |
import { AllKeys } from "../common" |
||||
|
|
||||
function emitProgress(...argu) { |
function emitHotUpdateReady(...argu) { |
||||
broadcast<AllKeys>("progress", ...argu) |
broadcast<AllKeys>("hot-update-ready", ...argu) |
||||
} |
} |
||||
|
|
||||
export { emitProgress } |
export { emitHotUpdateReady } |
||||
|
@ -0,0 +1,10 @@ |
|||||
|
import { inject } from "inversify" |
||||
|
import Updater from "main/modules/updater" |
||||
|
|
||||
|
export default class BasicCommand { |
||||
|
constructor(@inject(Updater) private _Updater: Updater) {} |
||||
|
|
||||
|
async triggerHotUpdate() { |
||||
|
await this._Updater.triggerHotUpdate() |
||||
|
} |
||||
|
} |
@ -1,62 +0,0 @@ |
|||||
import { spawn } from "node:child_process" |
|
||||
import fs from "node:fs" |
|
||||
import path from "node:path" |
|
||||
import os from "node:os" |
|
||||
import { app } from "electron" |
|
||||
|
|
||||
function getUpdateScriptTemplate() { |
|
||||
return process.platform === "win32" |
|
||||
? ` |
|
||||
@echo off |
|
||||
timeout /t 2 |
|
||||
taskkill /IM "{{EXE_NAME}}" /F |
|
||||
xcopy /Y /E "{{UPDATE_DIR}}\\*" "{{APP_PATH}}" |
|
||||
start "" "{{EXE_PATH}}" |
|
||||
` |
|
||||
: ` |
|
||||
#!/bin/bash |
|
||||
sleep 2 |
|
||||
pkill -f "{{EXE_NAME}}" |
|
||||
cp -Rf "{{UPDATE_DIR}}/*" "{{APP_PATH}}/" |
|
||||
open "{{EXE_PATH}}" |
|
||||
` |
|
||||
} |
|
||||
|
|
||||
function generateUpdateScript() { |
|
||||
const scriptContent = getUpdateScriptTemplate() |
|
||||
.replace(/{{APP_PATH}}/g, process.platform === "win32" ? "%APP_PATH%" : "$APP_PATH") |
|
||||
.replace(/{{UPDATE_DIR}}/g, process.platform === "win32" ? "%UPDATE_DIR%" : "$UPDATE_DIR") |
|
||||
.replace(/{{EXE_PATH}}/g, process.platform === "win32" ? "%EXE_PATH%" : "$EXE_PATH") |
|
||||
.replace(/{{EXE_NAME}}/g, process.platform === "win32" ? "%EXE_NAME%" : "$EXE_NAME") |
|
||||
|
|
||||
const scriptPath = path.join(os.tmpdir(), `update.${process.platform === "win32" ? "bat" : "sh"}`) |
|
||||
fs.writeFileSync(scriptPath, scriptContent) |
|
||||
return scriptPath |
|
||||
} |
|
||||
|
|
||||
app.on("will-quit", event => { |
|
||||
event.preventDefault() |
|
||||
|
|
||||
// 假设已下载更新到临时目录
|
|
||||
const updateTempDir = path.join(os.tmpdir(), "app-update") |
|
||||
const appPath = app.getAppPath() |
|
||||
const appExePath = process.execPath |
|
||||
|
|
||||
// 生成动态脚本
|
|
||||
const scriptPath = generateUpdateScript() |
|
||||
|
|
||||
fs.chmodSync(scriptPath, 0o755) |
|
||||
|
|
||||
// 执行脚本
|
|
||||
const child = spawn(scriptPath, [], { |
|
||||
detached: true, |
|
||||
shell: true, |
|
||||
env: { |
|
||||
APP_PATH: appPath, |
|
||||
UPDATE_DIR: updateTempDir, |
|
||||
EXE_PATH: appExePath, |
|
||||
}, |
|
||||
}) |
|
||||
child.unref() |
|
||||
app.exit() |
|
||||
}) |
|
@ -0,0 +1,118 @@ |
|||||
|
import { spawn } from "node:child_process" |
||||
|
import fs from "node:fs" |
||||
|
import path from "node:path" |
||||
|
import os from "node:os" |
||||
|
import { app } from "electron" |
||||
|
import extract from "extract-zip" |
||||
|
import { emitHotUpdateReady } from "common/event/Update/main" |
||||
|
|
||||
|
import _debug from "debug" |
||||
|
const debug = _debug("app:hot-updater") |
||||
|
|
||||
|
function getUpdateScriptTemplate() { |
||||
|
return process.platform === "win32" |
||||
|
? ` |
||||
|
@echo off |
||||
|
timeout /t 2 |
||||
|
taskkill /IM "{{EXE_NAME}}" /F |
||||
|
xcopy /Y /E "{{UPDATE_DIR}}\\*" "{{APP_PATH}}" |
||||
|
start "" "{{EXE_PATH}}" |
||||
|
` |
||||
|
: ` |
||||
|
#!/bin/bash |
||||
|
sleep 2 |
||||
|
pkill -f "{{EXE_NAME}}" |
||||
|
cp -Rf "{{UPDATE_DIR}}/*" "{{APP_PATH}}/" |
||||
|
open "{{EXE_PATH}}" |
||||
|
` |
||||
|
} |
||||
|
|
||||
|
function generateUpdateScript() { |
||||
|
const scriptContent = getUpdateScriptTemplate() |
||||
|
.replace(/{{APP_PATH}}/g, process.platform === "win32" ? "%APP_PATH%" : "$APP_PATH") |
||||
|
.replace(/{{UPDATE_DIR}}/g, process.platform === "win32" ? "%UPDATE_DIR%" : "$UPDATE_DIR") |
||||
|
.replace(/{{EXE_PATH}}/g, process.platform === "win32" ? "%EXE_PATH%" : "$EXE_PATH") |
||||
|
.replace(/{{EXE_NAME}}/g, process.platform === "win32" ? "%EXE_NAME%" : "$EXE_NAME") |
||||
|
|
||||
|
const scriptPath = path.join(os.tmpdir(), `update.${process.platform === "win32" ? "bat" : "sh"}`) |
||||
|
fs.writeFileSync(scriptPath, scriptContent) |
||||
|
return scriptPath |
||||
|
} |
||||
|
// 标记是否需要热更新
|
||||
|
let shouldPerformHotUpdate = false |
||||
|
let isReadyUpdate = false |
||||
|
// 更新临时目录路径
|
||||
|
// 使用应用名称和随机字符串创建唯一的临时目录
|
||||
|
const updateTempDirPath = path.join(os.tmpdir(), `${app.getName()}-update-${Math.random().toString(36).substring(2, 15)}`) |
||||
|
app.once("will-quit", event => { |
||||
|
if (!shouldPerformHotUpdate) return |
||||
|
event.preventDefault() |
||||
|
const appPath = app.getAppPath() |
||||
|
const appExePath = process.execPath |
||||
|
const exeName = path.basename(appExePath) |
||||
|
// 生成动态脚本
|
||||
|
const scriptPath = generateUpdateScript() |
||||
|
|
||||
|
fs.chmodSync(scriptPath, 0o755) |
||||
|
|
||||
|
// 执行脚本
|
||||
|
const child = spawn(scriptPath, [], { |
||||
|
detached: true, |
||||
|
shell: true, |
||||
|
env: { |
||||
|
APP_PATH: appPath, |
||||
|
UPDATE_DIR: updateTempDirPath, |
||||
|
EXE_PATH: appExePath, |
||||
|
EXE_NAME: exeName, |
||||
|
}, |
||||
|
}) |
||||
|
child.unref() |
||||
|
app.exit() |
||||
|
}) |
||||
|
|
||||
|
// 下载热更新包
|
||||
|
export async function fetchHotUpdatePackage(updatePackageUrl: string = "https://example.com/updates/latest.zip") { |
||||
|
if (isReadyUpdate) return |
||||
|
|
||||
|
// 清除临时目录
|
||||
|
clearUpdateTempDir() |
||||
|
// 创建临时目录
|
||||
|
if (!fs.existsSync(updateTempDirPath)) { |
||||
|
fs.mkdirSync(updateTempDirPath, { recursive: true }) |
||||
|
} |
||||
|
|
||||
|
// 下载文件的本地保存路径
|
||||
|
const downloadPath = path.join(updateTempDirPath, "update.zip") |
||||
|
|
||||
|
try { |
||||
|
// 使用 fetch 下载更新包
|
||||
|
const response = await fetch(updatePackageUrl) |
||||
|
if (!response.ok) { |
||||
|
throw new Error(`下载失败: ${response.status} ${response.statusText}`) |
||||
|
} |
||||
|
|
||||
|
// 将下载内容写入文件
|
||||
|
const arrayBuffer = await response.arrayBuffer() |
||||
|
fs.writeFileSync(downloadPath, Buffer.from(arrayBuffer)) |
||||
|
|
||||
|
// 解压更新包
|
||||
|
await extract(downloadPath, { dir: updateTempDirPath }) |
||||
|
|
||||
|
// 删除下载的zip文件
|
||||
|
fs.unlinkSync(downloadPath) |
||||
|
isReadyUpdate = true |
||||
|
emitHotUpdateReady() |
||||
|
} catch (error) { |
||||
|
debug("热更新包下载失败:", error) |
||||
|
throw error |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function clearUpdateTempDir() { |
||||
|
if (!fs.existsSync(updateTempDirPath)) return |
||||
|
fs.rmSync(updateTempDirPath, { recursive: true }) |
||||
|
} |
||||
|
|
||||
|
export function flagNeedUpdate() { |
||||
|
shouldPerformHotUpdate = true |
||||
|
} |
Loading…
Reference in new issue