Electron实现应用更新的坑及路径的最佳实践

前言

应用内更新的逻辑是,应用内检测受否需要更新,如果需要更新,安装包下载到本地后,进行打开安装,同时关闭当前应用。

旧版本的API为

1
2
const {shell} = window.require("electron");
shell.openItem(filepath);

旧版本是没有问题的。

新版本变更为

1
2
const {shell} = window.require("electron");
shell.openPath(filepath);

但是这样就出现问题了,新版本打开是在子进程中,安装应用安装包时,应用进程是必须要关闭,如果应用进程关闭,安装包对应的子进程也会关闭,导致安装终止。

node-cmd

所以只能更换一种方式

1
npm install node-cmd -s

在Electron中

1
2
const cmd = window.require('node-cmd');
cmd.run('start "" "' + filepath + '"');

注意启动应用应该这样

1
start "" "D:\Project\myapp.exe"

注意

  1. 路径要添加双引号,否则路径中有空格就无法打开应用。
  2. start命令后要添加"",否则打不开应用,第一个参数会被当做标题,第二个才是文件路径。

node-cmd简介

node-cmd模块中主要有run和get两类命令,其中run是执行cmd命令,get命令除了异步执行cmd命令外,在执行完毕后还会执行回调函数,返回命令行窗口的输出。

1
2
3
4
5
6
7
8
9
10
var cmd = require('node-cmd');

cmd.run('touch example.created.file');

cmd.get(
'ls',
function(data){
console.log('the current dir contains these files :\n\n',data)
}
);

完整示例

完整的下载应用代码示例:

渲染进程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
const path = window.require("path");
const {app} = window.require("electron").remote;
const cmd = window.require('node-cmd');

async update_app_action() {
let versionpath = this.version_obj["versionpath"];
let versioncode = this.version_obj["versioncode"];
let file_url = `${filedownloadUrl}${versionpath}`;
let temp_path = remote.getGlobal("sharedObject").temp_path;
try {
if (!fs.existsSync(temp_path)) {
fs.mkdirSync(temp_path, {recursive: true});
}
} catch (e) {
}
let filename = versioncode + "_" + versionpath.substring(versionpath.lastIndexOf("/") + 1);
let temp_filename = "temp_" + filename;

const filepath = path.join(
temp_path,
filename
);

const temp_filepath = path.join(
temp_path,
temp_filename
);
//文件存在直接打开
if (fs.existsSync(filepath)) {
cmd.run('start "" "' + filepath + '"');
} else {
// 不存在下载后打开
if (fs.existsSync(temp_filepath)) {
fs.unlinkSync(temp_filepath);
}
this.version_down = true;
try {
await this.download_file(file_url, temp_filepath);
this.version_down = false;
this.version_flag = false;
fs.renameSync(temp_filepath, filepath)
cmd.run('start "" "' + filepath + '"');
} catch (e) {
console.info("更新失败!")
}
}
}

download_file(file_url, path) {
return new Promise((resolve, reject)=>{
const url = require("url");
const {http, https} = require("follow-redirects");
let myhttp = http;
if (file_url.indexOf("https:") !== -1) {
myhttp = https;
}
const options = url.parse(file_url);
let filldiv = document.getElementById("filldiv");
try {
const request = myhttp.request(options, (response)=>{
const file_length = response.headers["content-length"];
let downd_length = 0;
let m_stream = fs.createWriteStream(path);
response.on("data", (chunk)=>{
downd_length += chunk.length;
let down_progress = Math.ceil((downd_length * 100) / file_length);
this.version_progress = down_progress;
filldiv.style.width = down_progress * 4 + "px";
//filldiv添加一个随机背景颜色
filldiv.style.background = "#33C5B3";
m_stream.write(chunk);
});
response.on("end", function() {
m_stream.end();
m_stream.on('close', ()=>{
resolve(path);
})
});
});
request.end();
} catch (e) {
reject("下载失败!");
}
})
},

Electron路径最佳实践

如果渲染进程太多,不建议在渲染进程中获取路径,建议在主进程中设置。

主进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const {app} = require("electron");
const path = require("path");
const fs = require("fs");

global.sharedObject = {
temp_path: "",
}

let basepath = "";

try {
basepath = app.getPath("downloads");
} catch (e) {
basepath = path.dirname(app.getPath("exe"));
}

if (/.*[\u4e00-\u9fa5 ]+.*$/.test(basepath)) {
basepath = "C:\\";
}

let temp_path = path.join(
basepath,
"school_live_temp"
)

try {
if (!fs.existsSync(temp_path)) {
fs.mkdirSync(temp_path, {recursive: true});
}
} catch (e) {
}

console.info("temp_path", temp_path);
global.sharedObject.temp_path = temp_path;

注意

在有些电脑上竟然无法使用app.getPath("downloads")获取路径,所以这里进行异常捕获。

有些电脑用户名是中文,而某些SDK不支持路径中包含中文和空格,这里也做了判断。

渲染进程

1
2
const remote = window.require("electron").remote;
let temp_path = remote.getGlobal("sharedObject").temp_path;