Electron incremental update (II)

Time:2021-9-13

Sorry, it’s been a long time. We used it in the last issueapp.asar.unpackedThe incremental update of electron has been completed. In this issue, we will introduce how to replace it with exeasarTo achieve incremental updates.
The content of this issue is based on the content of the previous issue, and is mainly aimed at the windows system (the MAC system can modify app.asar)

Replacement difficulty

  1. In useasarAfter, after the electron application of windows system is started, its app.asar will be occupied and cannot be modified or deleted. It can be modified only after the process of electron application is completed.
  2. Due to the UAC limitation of windows, if it is installed on Disk C, there will be permission problems when modifying app.asar.

Solution ideas

In fact, the woolen thread can also run node, but since the main process is over, we don’t have a node environment to run node commands, so this method doesn’t work. Of course, you can also add a compiled node running sub thread JS, but the volume problem is not worth the loss.
Under windows, we can use batch file bat to process files, but bat still has UAC and CMD window during execution. We can convert the written bat file into exe file to solve these problems.

  1. We can use node to derive an independent sub process, separate the sub process from the main process, end the process of electron application and replace it with this sub process.
  2. UAC restrictions can be handled using the EXE file to obtain administrator privileges.

Solution

1. Package modification

Let’s get rid of the previous ones hereapp.asar.unpackedPackage, comment out the previous vue.config, so that we only have ASAR file in package

// 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"
// ],

2. Build incremental zip

The afterpack hook was used to build the incremental zip in the last issue. Here we modify it and rename the new version of app.asar toupdate.asar, put it into the incremental app.zip package. If you don’t know, you can see the contents of the previous issue

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'))
}

3. Analog interface

As in the previous issue, we modified updateurl and updateexe here. Updateurl is an incremental zip, and updateexe is the EXE file we use 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"
  }
}

4. Loading policy modification

Here, we modify the loading policy to load the file in app.asar

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

5. Incremental update of rendering process

This is unchanged, as in the previous issue

6. Main process processing

Let’s download and modify the previous main process and judge firstupdate.exeWhether it exists. If it does not exist, download it firstupdate.exePutapp.getPath('userData')Li:

Win: C: \ users \ administrator (your user) \ appdata \ roaming \ < app name >\
Mac: / users / (your users) / library / Application Support / < app name >

app.getPath('userData')This path is quite special. It exists after installation. It is a data file. Your indexdb and localstorage exist here. The full update and uninstall of the software will not change the contents of this file,
We putupdate.exePut it here to avoid reinstallation after full update. Then download the incremental update package and unzip it into resources (update. ASAR), that is, the same level directory as app. ASAR. Delete the zip package and runapp.exit(0)Close 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)
    })
}

7. Sub process processing

Triggered when the main process closesquitEvent, we detect in this eventupdate.exeas well asupdate.asarWhether they exist at the same time,
If both exist, we use spawn to start a child process to run ourupdate.exe, and pass inresourcesPath(directory path of app.asar),app.getPath('exe')(the startup path of our software),
usechild.unref()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()
  }
})

That is, after the parent process exits, the child process executes our exe. Replacing app.asar, out and err redirects the execution log of the child process toapp.getPath('logs')In, this path andelectron-logNot the same (you can also set it to the same electron log path)

Win: C: \ users \ administrator (your user) \ appdata \ roaming \ < app name > \ < app product name > \ logs
mac: ~/Library/Logs/<app name> ? It should be under this. I didn't verify this

8. Build exe

The preparation work is completed. Here we write exe. In fact, it’s not difficult. We can use bat script to package it into exe.
update.bat

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

Briefly explain the parameters passed in by% 1 and% 2 to run the script, such asupdate.bat aaa bbb, then% 1 is AAA and% 2 is BBB, which we passed in when running exe with spawn,
That is,% 1 is resourcesspath and% 2 is the software startup exe. We run the bat script, pause for 1 second to ensure that the main process exits, then delete app.asar, rename update.asar to app.asar, and start the software exe.
A simple bat replacement is completed. We download bat to exe converter, convert update.bat to update.exe, and then put update.exe into our HTTP server directory. Run the software to detect the update and see if the update is complete.
Electron incremental update (II)

Supplementary notes

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

Some students may have questions about why we need to add a “” in several paths. This is because if the path name of the node running script contains spaces, quotation marks need to be added. The same is true for bat processing. For example, our software is installed on disk C,
C:\Program Files\electronVueDEV, the most common problem isProgram FilesThere is a space here. If there is such a path in the bat command, the processing will fail, so our paths are quoted.

This series of updates can only be sorted out on weekends and after work time. If there are more contents, the update will be slow. I hope it can be helpful to you. Please give more star or like collection support

Address:https://xuxin123.com/electron/increment-update2
GitHub address:link

Recommended Today

Java Engineer Interview Questions

The content covers: Java, mybatis, zookeeper, Dubbo, elasticsearch, memcached, redis, mysql, spring, spring boot, springcloud, rabbitmq, Kafka, Linux, etcMybatis interview questions1. What is mybatis?1. Mybatis is a semi ORM (object relational mapping) framework. It encapsulates JDBC internally. During development, you only need to pay attention to the SQL statement itself, and you don’t need to […]