electron 下载exe文件进行全量更新

519 阅读2分钟

下载更新

思路:

  1. 使用通信方案进行main层和renderer层的通信
  2. main层实现下载逻辑
  3. renderer实现渲染逻辑,并在完成后通过electron的shell对象打开文件进行安装

通信方案在之前文章有提到过。不赘述

代码实现逻辑

main层

  • 向renderer层的通信

src/main/services/ipcMain

//* 用于管理main层的监听工具类
import { ipcMain } from "electron"//* 引入通信后需要使用的工具类
import DownloadFile from "./downloadFile"//* 引入工具类
import { getMainWindowByIpcEvent, getDownloadPath, getHistoryPath } from '../utils/MainWindow'//* 通信后需要使用的对象
let appUpdate = null
​
​
function onEvent() { //* 监听从rendener层传送过来的数据
    //开始下载事件监听
    ipcMain.handle('start-download', (event, msg) => {
        new DownloadFile(getMainWindowByIpcEvent(event), getDownloadPath(), getHistoryPath()).start()
    })
}
​
export function Mainfunc() {
    //* 监听事件
    onEvent()
}
​
//* 用于管理ipc通信
export default  {
    
}

src/main/utils/MainWindow.ts 工具类

//* 关于mainWindow的工具类import { app, BrowserWindow, IpcMainInvokeEvent } from "electron";
import path from "path";
// import { arch, platform } from "os";
// import packageInfo from '../../../package.json'//* 根据Ipc获取mainWindow
export function getMainWindowByIpcEvent(event: IpcMainInvokeEvent) {
  return BrowserWindow.fromWebContents(event.sender);
}
​
//* 发送信息给渲染层(vue)
export function sendToRender(
  mainWindow: BrowserWindow | undefined | null,
  code: string,
  type: number,
  data?: string
) {
  if(!mainWindow) {
    return
  }
  const senddata = {
    state: type,
    msg: data || "",
  };
   mainWindow.webContents.send(code, senddata);
}
​
const fileName = 'electron-vite-demo Setup 1.0.0.exe'
// 获取下载路径
export function getDownloadPath() {
  // const version: string = packageInfo.version //* 获取版本
  // const Sysarch: string = arch().includes('64') ? 'win64' : 'win32' //* 获取window版本
  // const isWin: boolean = platform().includes('win32')
​
  return 'http://127.0.0.1:5500/' + fileName
}
​
export function getHistoryPath() {
  return path.join(app.getPath('downloads'), fileName)
}

src/main/services/downloadFile.ts 下载工具类

通过外部传过来的url和历史路径进行下载操作

import { app, BrowserWindow, dialog } from "electron";
import { stat, remove } from "fs-extra";
import { join } from "path";
//* 下载文件 用于下载更新功能 如果有两处需要用到该文件 需要把提示逻辑等给抽出来class DownloadFile {
  public downloadUrl: string; //* 下载的路径
  public historyFilePath: string; //* 历史路径 用于判断该文件是否存在,是否需要删除
  public mainWindow: BrowserWindow|null; //* 使用mainWindow自带方法进行下载
​
  constructor(
    mainWindow: BrowserWindow,
    downloadUrl: string,
    historyFilePath: string
  ) {
    this.mainWindow = mainWindow;
    this.downloadUrl = downloadUrl;
    this.historyFilePath = historyFilePath;
  }
​
  start() {
    //* 开始下载
    this.startDownload();
​
    //* 监听事件 设置下载路径
    this.onEvent()
  }
  startDownload() {
    //* 开始下载
    //* 判断是否存在同名文件 存在则删除 如果没有就开始下载
    stat(this.historyFilePath, async (err, stats) => {
      try {
        if (stats) {
          await remove(this.historyFilePath);
        }
        console.log('开始下载')
        this.mainWindow.webContents.downloadURL(this.downloadUrl);
      } catch (error) {
        console.log(error);
      }
    });
  }
  onEvent() {
    //* 监听下载过程中的事件
    this.mainWindow.webContents.session.on(
      "will-download",
      (event: any, item: any, webContents: any) => {
        const filePath = join(app.getPath("downloads"), item.getFilename());
        item.setSavePath(filePath);
        item.on("updated", (event: any, state: string) => {
          console.log(state, '文件下载中途')
          switch (state) {
            case "progressing":
              this.mainWindow.webContents.send(
                "download-progress",
                (
                  (item.getReceivedBytes() / item.getTotalBytes()) *
                  100
                ).toFixed(0)
              );
              break;
            default:
              this.mainWindow.webContents.send("download-error", true);
              dialog.showErrorBox(
                "下载出错",
                "由于网络或其他未知原因导致下载出错"
              );
              break;
          }
        });
        item.once("done", (event: any, state: string) => {
          console.log('文件下载完成', state)
          switch (state) {
            case "completed":
              const data = {
                filePath,
              };
              this.mainWindow.webContents.send("download-done", data);
              break;
            case "interrupted":
              this.mainWindow.webContents.send("download-error", true);
              dialog.showErrorBox(
                "下载出错",
                "由于网络或其他未知原因导致下载出错."
              );
              break;
            default:
              break;
          }
        });
      }
    );
  }
}
​
export default DownloadFile

renderer:和main层进行通信通知更新,更新完成后使用shell打开下载的exe文件进行安装

src/renderer/pages/index/index.vue

<template>
  <h1>更新界面</h1>
  文件地址{{ filePath }}
  <div class="actions">
    <el-button @click="checkUpdate">
      检查更新,下载更新
    </el-button>
  </div>
  <el-dialog
    v-model="dialogVisible"
    title="进度"
    center
    width="14%"
    top="45vh"
  >
    <div class="conten">
      <el-progress
        type="dashboard"
        :percentage="percentage"
        :color="colors"
        :status="progressStaus"
      />
    </div>
  </el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
​
import { send, on } from '@renderer/utils/ipcRenderer'
//* element-plus 工具类封装
import {  ElMessageBox } from "element-plus";
const { shell } = require("electron");
​
let percentage = ref(0);
let colors = ref([
  { color: "#f56c6c", percentage: 20 },
  { color: "#e6a23c", percentage: 40 },
  { color: "#6f7ad3", percentage: 60 },
  { color: "#1989fa", percentage: 80 },
  { color: "#5cb87a", percentage: 100 },
]);
let filePath = ref('')
​
let dialogVisible = ref(false);
let progressStaus = ref('');
​
function checkUpdate() {
  //* 检查更新方法 发送通信
  send('start-download') //通信到main层
  dialogVisible.value = true
}
​
on("download-progress", (event, arg) => {//* 进度条
  percentage.value = Number(arg);
});
on("download-error", (event, arg) => {
  if (arg) {
    progressStaus.value = "exception";
    percentage.value = 40;
    colors.value = "#d81e06";
  }
});
on("download-paused", (event, arg) => {
  if (arg) {
    progressStaus.value = "warning";
    ElMessageBox.alert("下载由于未知原因被中断!", "提示", {
      confirmButtonText: "重试",
      callback: (action) => {
        checkUpdate();
      },
    });
  }
});
on("download-done", (event, age) => {
  filePath.value = age.filePath;
  progressStaus.value = "success";
  ElMessageBox.alert("更新下载完成!", "提示", {
    confirmButtonText: "确定",
    callback: (action) => {
      shell.openPath(filePath.value);
    },
  });
});
</script>

源码地址