我正在使用 Electron 为 Windows 构建一个应用程序。要打包和分发它,我正在使用electron-builder。Electron-builder 依赖于许多包,并且对于自动更新,它使用Squirrel-windows。
我一直在与 Windows 上的自动更新作斗争 3 天,最后我想出了一个似乎没有问题的可行解决方案。
我不会详细介绍我尝试过的方法,但都失败了。相反,我将在这里发布我提出的解决方案。
我正在与你们分享它,看看你们是否可以向我指出任何会使我的系统失败的缺陷,或者,如果它确实是一个可靠的解决方案,以帮助那些像我一样挣扎的人。出于后一个原因,我发布了一些不必要的代码,希望对其他人有所帮助。
逻辑如下:
- 如果当前可执行文件路径内的子文件夹
fullupdate
不存在(见后文,会澄清),我们连接在线服务器并通过发送当前应用程序版本来检查是否有更新; - 如果没有更新,什么也不做。
- 如果有更新,我们指示服务器返回一个
json
包含 url 的字符串,我们可以从中下载.exe
由electron-builder
. 注意:不是.nupkg
(未提供服务器代码:-)
)。 - 我们下载文件并将其保存
fullupdate
在当前保存我们的应用程序的本地文件夹中的子文件夹中。这应该是“安全的”,因为electron-builder
将应用程序保存在当前用户文件夹AppData
中,所以我们不应该有权限问题。 - 在下载结束时,我们
update
在文件夹中创建一个新文件fullupdate
,以确保下载成功完成。我们也可以重命名文件,但我更喜欢这种方式。 - 下次打开应用程序时:
- 如果文件夹
fullupdate
存在,我们检查文件是否update
存在。如果不存在,说明下载没有完成,所以我们删除文件夹fullupdate
并再次调用远程服务器重新开始。 - 否则,如果文件
update
存在,我们启动.exe
我们下载的文件,并返回 true。这将阻止应用程序打开主窗口。很酷的是,更新程序将删除保存的整个旧版本的应用程序AppData
(同时保留本地用户数据)并用新版本替换它。这样,我们也将摆脱文件夹fullupdate
。
- 如果文件夹
现在代码:
// we want to run this only on windows
var handleStartupEvent = function() {
if (process.platform !== 'win32') {
return false;
}
/////////////////
// MANUAL UPDATER
/////////////////
var appFolder = 'app-' + appVersion;
var pathApp = path.dirname(process.execPath);
var pathUpdate = pathApp + '\\fullupdate';
var checkupdateurl = 'https://api.mysite.com/getjson/' + appVersion.split('.').join('-');
function checkIfDownloaded(){
if (!fs.existsSync(pathUpdate)) checkUpdate();
else return checkIfInstallLocal();
}
function checkIfInstallLocal(){
if(fileExists('fullupdate\\update')) return installLocal();
else {
deleteFolderRecursive(pathUpdate);
checkUpdate();
}
}
function installLocal(){
cp.exec('fullupdate\\Update.exe', function( error, stdout, stderr){
if ( error != null ) {
console.log(stderr);
}
});
return true;
}
// from http://www.geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/
var deleteFolderRecursive = function(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) deleteFolderRecursive(curPath);
else fs.unlinkSync(curPath);
});
fs.rmdirSync(path);
}
};
// from http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js
function fileExists(path) {
try {
return fs.statSync(path).isFile();
}
catch (e) {
if (e.code == 'ENOENT') { // no such file or directory. File really does not exist
return false;
}
throw e; // something else went wrong, we don't have rights, ...
}
}
function checkUpdate(){
https.get('https://api.mysite.com/getjson/' + app.getVersion().split('.').join('-'), (res) => {
res.setEncoding('utf8');
res.on('data', function(chunk) {
if(chunk) thereIsUpdate(chunk);
});
}).on('error', (e) => {
console.log(e);
});
}
function thereIsUpdate(chunk){
var data = JSON.parse(chunk);
if(data && data.url) getNewUpdate(data.urlsetup);
}
function getNewUpdate(url){
fs.mkdirSync(pathUpdate);
var file = fs.createWriteStream(pathUpdate + '/Update.exe');
var responseSent = false; // flag to make sure that response is sent only once.
var request = https.get(url, function(response) {
response.pipe(file);
file.on('finish', () =>{
file.close(() => {
if(responseSent) return;
responseSent = true;
});
fs.closeSync(fs.openSync(pathUpdate + '/update', 'w'));
});
});
}
if(checkIfDownloaded()) return true;
/////////////////////////
// SQUIRREL EVENTS HANDLER
//////////////////////////
// see http://stackoverflow.com/questions/30105150/handle-squirrels-event-on-an-electron-app
};
// here we call the function. It is before the opening of the window, so that we prevent the opening if we are updating, or if there is a Squirrel event going on (see SO question, link above)
if (handleStartupEvent()) {
return;
}