You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

118 lines
3.4 KiB

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 download from "./download"
import extract from "extract-zip"
import _logger from "logger/main"
import { emit, EventEnum } from "../handler"
const logger = _logger.createNamespace("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) {
if (isReadyUpdate) return
// 清除临时目录
clearUpdateTempDir()
// 创建临时目录
if (!fs.existsSync(updateTempDirPath)) {
fs.mkdirSync(updateTempDirPath, { recursive: true })
}
// 下载文件的本地保存路径
const downloadPath = path.join(updateTempDirPath, "update.zip")
try {
// 使用 fetch 下载更新包
const arrayBuffer = await download({
url: updatePackageUrl,
onprocess(now, all) {
logger.debug(`下载进度: ${((now / all) * 100).toFixed(2)}%`)
emit(EventEnum.UPDATE_PROGRESS, { percent: (now / all) * 100, now, all })
},
})
fs.writeFileSync(downloadPath, Buffer.from(arrayBuffer))
// 解压更新包
await extract(downloadPath, { dir: updateTempDirPath })
// 删除下载的zip文件
fs.unlinkSync(downloadPath)
isReadyUpdate = true
} catch (error) {
logger.debug("热更新包下载失败:", error)
throw error
}
}
function clearUpdateTempDir() {
if (!fs.existsSync(updateTempDirPath)) return
fs.rmSync(updateTempDirPath, { recursive: true })
}
export function flagNeedUpdate() {
shouldPerformHotUpdate = true
}