结合electron-updater包与downloadItem类实现
主要流程:
1.通过electron-updater检查更新,返回升级
2.判断是否执行静默升级(检查本地安装包的完整性)
3.通过downloadItem执行下载,它可以实行暂停与恢复
4.下载完成后通过execSync执行安装包,关闭应用
electron-updater中逻辑不变,可参考:【electron】应用在线升级_渐墨深的博客-CSDN博客_electron 在线更新
主流程新增download代码
win.webContents.session.on('will-download', (event, item) => {
// 设置下载安装包存放的路径,此处为electron安装包默认下载更新的路径,windows和mac略有不同
const path = os.platform() === 'win32'
? `${app.getPath('home')}\\AppData\\Local\\${app.name}\\pending`
: `${app.getPath('appData')}/Caches/${app.name}/pending`
const fileName = item.getFilename()
// 是否可恢复下载
if (item.canResume()) {
item.resume()
}
if (!item.canResume() && (existsSync(`${path}/temp-${fileName}`) || existsSync(`${path}/${fileName}`))) {
if (existsSync(`${path}/${fileName}`)) {
win?.webContents.send('downloadUrl-finish')
}
item.cancel()
return
}
// 无需对话框提示, 直接将文件保存到路径,
// 未下载完成文件名前加上temp-,下载完成后修改,保证下载安装包的完整
item.setSavePath(`${path}/temp-${fileName}`)
item.on('updated', (e, state) => {
if (state === 'interrupted') {
console.log('Download is interrupted but can be resumed')
item.resume()
} else if (state === 'progressing') {
if (item.isPaused()) {
console.log('Download is paused')
} else {
//const speedBytes = offset - lastBytes;//下载速度
// item.getReceivedBytes() // 已经下载
// item.getTotalBytes() // 总内容大小,
// 可以结合已下载内容和总内容大小计算下载进度,
// 不过根据资源的不同,可能获取到的total为0,
}
}
})
// 下载结束
item.once('done', (e, state) => {
if (state === 'completed') {
// 发送下载完成事件
win?.webContents.send('downloadUrl-finish')
} else {
console.log(`Download failed: ${state}`)
}
})
// 暂停下载
ipcMain.on('pause-download', () => {
item.pause()
})
// 取消下载
ipcMain.on('cancel-download', () => {
item.cancel()
})
//
// 恢复下载
ipcMain.on('resume-download', () => {
if (item.canResume()) {
item.resume()
}
})
})
}
渲染进程新增通用的方法:
/**
* 用户手动退出并安装
* @param path:安装包目录
*/
export const quitAndInstallByUser = (path: string) => {
// 处理退出前需要执行的内容
beforeClose()
ipcRenderer.send('close-app')
// 设置安装路径 安装参数,自定义操作
execSync(`"${path}" -InstallType=userUpdate`)
}
/**
* 退出app
*/
const exitApp = () => {
ipcRenderer.send(UpdateChannel.ExitApp)
}
// 暂停下载
const pauseDownload = () => {
ipcRenderer.send('pause-download')
}
// 恢复下载
const resumeDownload = () => {
ipcRenderer.send('resume-download')
}
// 恢复下载
const cancelDownloadUrl = () => {
ipcRenderer.send('cancel-download')
}
/**
* 开始下载新的安装包
* @param url:安装包链接
*/
const startDownloadUrl = (url: string) => {
// test download and pause
ipcRenderer.send('start_download', url)
}
渲染进程 useUpdater中新增代码:
/**
* 下载完成后将版本信息保存到update-info.json文件中
*/
const saveUpdateInfo = useCallback(() => {
// 下载完成后将 temp文件改名
if (existsSync(`${storagePath}/temp-${newVersionNames}`)) {
renameSync(`${storagePath}/temp-${newVersionNames}`, `${storagePath}/${newVersionNames}`)
}
// 下载完成后加安装包的信息保存在json文件中
writeFileSync(
`${storagePath}/update-info.json`,
JSON.stringify({
fileName: newVersionNames,
isAdminRightsRequired: true,
}),
(err: any) => {
console.log(err)
},
)
}, [])
// 下载完成,保存下载安装包的信息
ipcRenderer.on('downloadUrl-finish', () => {
saveUpdateInfo()
})
渲染进程检查更新结果处理
/**
* 展示更新信息弹窗,如果是强制更新,则只能退出和更新
* 点击立即更新则开始下载
* path 为安装文件存储路径,主进程获取并传递回渲染进程
*/
const versionInfoDialog = useCallback(
(updateInfo: UpdateInfo, force: boolean, path: string) => {
forceUpdate = force
const newName = updateInfo?.path?.split('/').pop()
newVersionNames = newName ?? ''
const logs = Array.isArray(updateInfo.changeLogs)
? updateInfo.changeLogs
: [updateInfo.changeLogs]
// 更新内容换行处理
const lists = logs.map((i: string) => (i.includes('\n') ? i.split('\n') : i))
const changeLogs = [
`${String(updateInfo.version)}版本更新了以下内容:`,
].concat(lists.flat(2).map((i: string) => `-${i}`))
let originUpdateInfo: any
// 判断是否存在安装包的记录文件
if (existsSync(`${path}/update-info.json`)) {
const fileBuffer = readFileSync(`${path}/update-info.json`)
originUpdateInfo = JSON.parse(fileBuffer.toString())
}
// 已下载完整的安装包且和最新需要升级的版本一致,直接提醒安装
if (!force && !userAction
&& newName
&& newName === originUpdateInfo?.fileName
&& existsSync(`${path}/${originUpdateInfo?.fileName}`)
) {
// 最新版本 已下载
const modal = confirm({
title: '发现新版本,安装过程需要1分钟,是否立即安装?',
content: changeLogs,
cancelText: '稍后',
okText: '立即安装',
onOk() {
modal.destroy()
// 执行安装
quitAndInstallByUser(`${path}/${newName}`)
},
onCancel() {
if (force) {
// 强制更新的时候不安装就退出app
exitApp()
} else {
modal.destroy()
}
},
})
return
}
setUpdateVersion(String(updateInfo.version))
// 主动检查更新或者强更的时候
if (force || userAction) {
const title = force ? '发现新版本,请立即更新。否则将无法正常使用' : '发现新版本,是否立即更新?'
pauseDownload()
const modal = confirm({
title,
content: changeLogs,
cancelText: force ? '退出' : '稍后',
okText: '立即更新',
onOk() {
if (
newName
&& newName === originUpdateInfo?.fileName
&& existsSync(`${path}/${originUpdateInfo?.fileName}`)
) {
// 如果最新安装包和已下载的安装包一致,则直接安装新的包
quitAndInstallByUser(`${path}/${newName}`)
} else {
cancelDownloadUrl()
modal.destroy()
// 开始主动下载,依旧使用electron-updater包中的下载方法
startDownload()
}
},
onCancel() {
if (force) {
// 非强制更新不更新的话就退出app
exitApp()
} else {
// 不主动下载就恢复静默下载
resumeDownload()
modal.destroy()
}
},
})
} else {
// 开始静默下载
startDownloadUrl(updateInfo.path ?? '')
}
},
[
startDownload,
setUpdateVersion,
],
)