【electron】静默升级与主动安装

公孙阳羽
2023-12-01

结合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,
    ],
  )

 类似资料: