Last time we used app.asar. Unpacked to complete the incremental update of electron. This time we will introduce how to use exe to replace ASar for incremental update. The content of this issue is based on the content of the last issue, and mainly for Windows system (MAC system can modify app.asar).

Replace the difficulty

  1. In the use of theasarAfter the Electron application in Windows is started, app.asar is occupied and cannot be modified or deleted. You must end the Electron application process before modifying the app.asar.
  2. Due to Windows UAC restrictions, if installed on C disk, modify app.asar will have permission issues.

solution

The thread can also run node, but since the main process has ended and we don’t have a Node environment to run the node command, this method doesn’t work. Of course, you can add a compiled Node to run the child thread JS, but the size is not worth the loss. Under Windows, we can use the batch file BAT to process files, but BAT still has UAC and there is a CMD window when executing, we can convert the written BAT file to exe file to solve these problems.

  1. We can use Node to spawn independent child processes, separate the child process from the main process, terminate the Electron process and replace it with the child process.
  2. Uac restrictions can be handled by using exe files to obtain administrator permissions.

The solution

1. Pack the modification

Here we remove the previous app.asar. Unpacked, comment out the previous vue.config, so that we pack only asar files

// extraResources: [{
//   from: "dist_electron/bundled",
//   to: "app.asar.unpacked",
//   filter: [
//     "!**/icons",
//     "!**/preload.js",
//     "!**/node_modules",
//     "!**/background.js"
//   ]
// }],
// files: [
//   "**/icons/*",
//   "**/preload.js",
//   "**/node_modules/**/*",
//   "**/background.js"
// ],
Copy the code

2. Build an incremental ZIP

The afterPack hook was used to build incremental zip. Here we modify it by renaming the new version of app.asar to update.asar and putting it in the incremental app.zip package

const path = require('path')
const AdmZip = require('adm-zip')
const fse = require('fs-extra')

exports.default = async function(context) {
  let targetPath
  if(context.packager.platform.nodeName === 'darwin') {
    targetPath = path.join(context.appOutDir, `${context.packager.appInfo.productName}.app/Contents/Resources`)
  } else {
    targetPath = path.join(context.appOutDir, './resources')
  }
  const asar = path.join(targetPath, './app.asar')
  fse.copySync(asar, path.join(context.outDir, './update.asar'))
  var zip = new AdmZip()
  zip.addLocalFile(path.join(context.outDir, './update.asar'))
  zip.writeZip(path.join(context.outDir, 'app.zip'))
  fse.removeSync(path.join(context.outDir, './update.asar'))
}
Copy the code

3. Analog interface

As in the previous installment, we modified upDateUrl and upDateExe, where upDateUrl is incremental ZIP and upDateExe is the exe file we used to replace ASAR.

{ "code": 200, "success": true, "data": { "forceUpdate": false, "fullUpdate": false, "upDateUrl": "Http://127.0.0.1:4000/app.zip", "upDateExe" : "http://127.0.0.1:4000/update.exe", "restart" : false, "message" : "I want to upgrade to 0.0.2", "version": "0.0.2"}}Copy the code

4. Modify the load policy

Here we change the loading strategy to load files in app.asar

// createProtocol('app', path.join(resources, './app.asar.unpacked'))
createProtocol('app')
Copy the code

5. Incremental renderer updates

This hasn’t changed. Same as last time

6. Main process processing

Update. exe (‘userData’) : app.getPath(‘userData’);

Win: C:\Users\Administrator \AppData\Roaming\< App Name >\ MAC: /Users/ /Library/Application Support/<app Name >Copy the code

App.getpath (‘userData’) is a special path that exists after installation. It is a data file. Your indexDB, localStorage, etc., are stored in this path. We put update.exe here to avoid reinstalling after full update. Then download the incremental update package and decompress it to Resourcesh (update.asar), which is the same directory as app.asar, delete the zip package and run app.exit(0) to shut down the main process

import downloadFile from './downloadFile' import { app } from 'electron' const fse = require('fs-extra') const path = require('path') const AdmZip = require('adm-zip') export default async (data) => { const resourcesPath = process.resourcesPath if (! fse.pathExistsSync(path.join(app.getPath('userData'), './update.exe'))) { await downloadFile({ url: data.upDateExe, targetPath: app.getPath('userData') }) } downloadFile({ url: data.upDateUrl, targetPath: resourcesPath }).then(async (filePath) => { const zip = new AdmZip(filePath) zip.extractAllToAsync(resourcesPath, true, (err) => { if (err) { console.error(err) return } fse.removeSync(filePath) app.exit(0) }) }).catch(err => { console.log(err) }) }Copy the code

7. Sub-process processing

Exe and update.asar exist at the same time. If they exist at the same time, we use spawn to start a child process to run update.exe. And pass resourcesPath (app.asar directory path), app.getPath(‘exe’) (our software startup path), use child.unref() to separate the child process from the parent process, you can exit the parent process without exiting the child process.

const { spawn } = require('child_process')
const fse = require('fs-extra')
const fs = require('fs')
const resourcesPath = process.resourcesPath

app.on('quit', () => {
  console.log('quit')
  if (fse.pathExistsSync(path.join(app.getPath('userData'), './')) && fse.pathExistsSync(path.join(resourcesPath, './update.asar'))) {
    const logPath = app.getPath('logs')
    const out = fs.openSync(path.join(logPath, './out.log'), 'a')
    const err = fs.openSync(path.join(logPath, './err.log'), 'a')
    const child = spawn(`"${path.join(app.getPath('userData'), './update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
      detached: true,
      shell: true,
      stdio: ['ignore', out, err]
    })
    child.unref()
  }
})
Copy the code

App.asar, out and err redirects the child’s logs to app.getPath(‘logs’). This path is different from the electron log path (you can also set it to the electron log path yourself)

Win: C:\Users\Administrator \AppData\ Folder \<app Name> <app productName>\logs MAC: ~/Library/ logs /<app Name>? I think it's underneath this one, which I didn't checkCopy the code

8. Build exe

The preparation work is complete, here we write exe, in fact, this is not difficult, we use bat script package exe on the line. update.bat

@echo off
timeout /T 1 /NOBREAK
del /f /q /a %1\app.asar
ren %1\update.asar app.asar
start "" %2
Copy the code

For a brief explanation, %1 and %2 are the arguments passed in to run the script, for exampleupdate.bat aaa bbb%1 is resourcesPath, %2 is the start exe of the software, we run the bat script, pause for 1 second to make sure that the main process exits, then delete app.asar. Rename update.asar to app.asar and start exe.

A simple bat replacement is done. We download bat To Exe Converter, convert update.bat To update. Exe, and put update. Exe in our HTTP-server directory. Run software to detect updates to see if they are complete.

added

spawn(`"${path.join(app.getPath('userData'), './update.exe')}"`, [`"${resourcesPath}"`, `"${app.getPath('exe')}"`], {
  detached: true,
  shell: true,
  stdio: ['ignore', out, err]
})
Copy the code

C:\Program Files\electronVueDEV, c :\Program Files\electronVueDEV, c :\Program Files\electronVueDEV The most common problem is that there is a space in Program Files, which will cause the bat command to fail to process such a path, so our paths are quoted.

This series of updates can only be arranged during weekends and off-duty hours. If there are too many contents, the update will be slow. I hope it will be helpful to you

Address: xuxin123.com/electron/in… This article github address: link