这里文章都是从个人的github博客直接复制过来的,排版可能有点乱. 原始地址
http://benq.im/2015/05/12/hexomd-06/
上一篇我们实现了粘贴上传图片
功能.
今天将实现自动更新
的功能,有了这个功能以后我再发博客就不需要每次都把最新的程序重新打包上传了.
对于不想看如何实现的朋友,直接下载打包好的程序就行,以后更新可以点击软件右上角的第一个按钮即可(手动).
自动更新方案
在做上一个软件Gungnir的时候,为了可以显示更新进度,自动更新的方案是列出所有需要更新的文件,然后自动下载每个文件并覆盖,但是在需要更新一些node模块(文件一般都很多)时就相当麻烦了,有一个文件传输失败就会导致更新出错.实现起来相当麻烦,而且也并不能带来什么优势.
所以做这个软件的自动更新的时候,我用了更为简单粗暴的方案:将需要更新的文件打包成zip文件,直接下载并解压覆盖即可.
实现
自动更新作为较单独的功能模块,我把全部代码放在modules/updater.js,这里就不把全部代码贴出来了,需要的自己点链接看,里面有注释. 我只讲一些实现细节.
安装依赖模块
首先是安装两个新增了的node模块依赖when
和bufferhelper
,第一个是promise
模块,第二个看名字就知道,无须解释.
1
|
npm install bufferhelper
|
将7z.exe
放到软件根目录备用
增加了updater配置节点,配置最新的版本号version
和对应的补丁文件地址package
,由于我这个软件功能很少,代码并不多,因此我现在每次更新都是包含之前所有补丁的文件打包,加起来也才1m多,这样实现比较简单,只要下载最新的包即可.
1 2 3 4 5
|
... "updater":{ "version":"0.6.0.1", "package":"http://7xit5q.com1.z0.glb.clouddn.com/update.zip" }
|
updater.js
updater.js
里实现了hmd.updater
模块,包含4个方法
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
|
var packageFile = 'https://raw.githubusercontent.com/benqy/hexomd/master/package.json',
execPath = require('path').dirname(process.execPath),
updatePath = execPath + '\update', fs = require('fs'), util = require('./helpers/util'), when = require('./node_modules/when'); var checkUpdateTimer; hmd.updater = { get: function (url) { ... }, checkUpdate: function () { ... }, update:function(packageUrl){ ... }, install: function () { ... } };
|
更新的流程为 : checkUpdate
检查是否有更新 > update
下载补丁包 > install
安装补丁包
get
方法里要注意的是下载下来的内容要判断是否经过GZIP
压缩,如果是,则要用node自带的zlib
解压.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
... req = protocolModule.get(urlOpt, function (res) { var isGzip = !!res.headers['content-encoding'] && !!~res.headers['content-encoding'].indexOf('gzip'); ... if (isGzip) { require('zlib').unzip(buffer, function (err, buffer) { gzipDeferred.resolve(buffer); }); } else { gzipDeferred.resolve(buffer); } ... return deferred.promise;
|
checkUpdate
方法里先下载线上的package.json
文件与本地进行比较,如果版本号不一致,则提示用户更新.如果用户选择更新,则下载package.json到本地,然后调用update
方法下载补丁
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
|
checkUpdate: function () { hmd.msg('===正在检查更新==='); checkUpdateTimer =setTimeout(function(){ hmd.msg('===更新失败,可能github被墙了===', hmd.MSG_LEVEL.error); }, 10000); var locPackage = require('nw.gui').App.manifest; hmd.updater.get(packageFile) .then(function (packageData) { clearTimeout(checkUpdateTimer); packageData.text = packageData.buffer.toString(); if (!packageData.text) return; var remotePackage = JSON.parse(packageData.text); if (remotePackage.updater.version != locPackage.updater.version){ if (confirm('是否更新到最新版本:' + remotePackage.updater.version)) { if (!fs.existsSync(updatePath)) { util.mkdir(updatePath, true); } fs.writeFileSync(updatePath + '\package.json', packageData.buffer); hmd.updater.update(remotePackage.updater.package); } } else { hmd.msg('当前版本:' + remotePackage.updater.version + ',已经是最新版'); } }); }
|
update
方法下载补丁包到update
目录,然后调用install
安装补丁
1 2 3 4 5 6 7 8
|
update:function(packageUrl){ hmd.msg('===正在下载更新文件===', hmd.MSG_LEVEL.warnings); hmd.updater.get(packageUrl + '?' + new Date() * 1) .then(function (data) { fs.writeFileSync(updatePath + '\update.zip',data.buffer); hmd.updater.install(); }); },
|
install
将补丁包通过7z.exe
解压覆盖到程序目录,然后提示用户重启软件.
1 2 3 4 5 6 7 8 9
|
install: function () { require("child_process").exec('xcopy "' + updatePath + '\package.json" "' + execPath + '\package.json" /s /e /y'); var unzip = execPath + '\7z.exe x '+ updatePath +'\update.zip -y'; require("child_process").exec(unzip,function(){ hmd.msg('===更新完成,重启后生效==='); }); }
|
这里我直接用7z.exe,反正也不大,也可以使用一些开源的node压缩模块.
绑定更新按钮
更新模块完成了,现在将功能绑定到按钮上.
先在modules/directives增加新的directivehmdUpdate
1 2 3 4 5 6 7 8
|
angular.module('hmd.directives', []) .directive('hmdUpdate', [function () { return function (scope, elem) { $(elem[0]).on('click', function () { hmd.updater.checkUpdate(); }); }; }])
|
然后将index.html上的更新按钮与directive绑定
1
|
<a class="btn rectbtn" href="javascript://" title="点击检查更新" hmd-update>...</a>
|
别忘了引用updater.js
1 2 3
|
<script src="modules/app.js"></script> <script src="modules/updater.js"></script> <script src="modules/directives.js"></script>
|
总结
基本的自动更新的功能比图片上传更为简单,但是今天做的这个功能还有很多细节问题,比如:
- 无法自动删除新版本不需要的文件
- 以后如果程序大了,更新补丁每次都全量打包会导致更新很慢
- 更新后不会自动重启软件.
- 更好的方案是自动根据git的提交信息生成更新列表,并且根据版本号管理.
接下来的计划:
- 云同步.
- 插件机制
- 表情插件.
附件
本篇程序打包.
项目地址