Commit eceddda3 authored by antoor's avatar antoor

update to v1.1.1. // new online update feature

parent a5a2d612
......@@ -5,10 +5,11 @@ const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
// 导入模块
const Cache = require('./modules/cache');
const Update = require('./modules/update');
const Menubar = require('./modules/menubar');
const Request = require('./modules/request');
const Database = require('./modules/database');
const Cache = require('./modules/cache');
// electron.crashReporter.start();
......@@ -59,4 +60,7 @@ app
// 初始化缓存模块
new Cache(electron);
// 监听更新请求
new Update(electron);
});
\ No newline at end of file
//
// 程序更新模块
//
/* 更新流程:
-------
1. 获取远程github上的package.json信息
2. 和本地版本进行判断,不一致则提示更新
3. 下载用户选择的更新源文件到临时目录`.antSword-{now}`
4. 替换程序中的`resources/app.asar`文件
5. 提示用户手动重启,关闭应用
*/
'use strict';
const os = require('os'),
fs = require('fs'),
path = require('path'),
unzip = require('extract-zip'),
crypto = require('crypto'),
nugget = require('nugget'),
logger = require('log4js').getLogger('Update'),
superagent = require('superagent');
class Update {
constructor(electron) {
const ipcMain = electron.ipcMain;
this.info = {};
ipcMain
.on('update-check', (event, arg) => {
this.check(arg['local_ver'], (hasUpdate, retVal) => {
logger.debug('check-result', hasUpdate, retVal);
event.sender.send('update-check', {
hasUpdate: hasUpdate,
retVal: retVal
});
});
})
.on('update-download', (event, source) => {
logger.debug('update-download', source);
const info = this.info['update'];
const downloadUrl = info['sources'][source];
this.download(downloadUrl, info['md5'], (done, retVal) => {
event.sender.send('update-download', {
done: done,
retVal: retVal
});
});
});
}
// 检查是否有更新
// 参数{localVer: 本地版本号, callback: 回调函数(是否有更新, 是?更新信息:错误信息)}
check(localVer, callback) {
logger.debug('check', localVer);
superagent
.get('https://raw.githubusercontent.com/antoor/antSword/v1.1-dev/package.json')
.timeout(9527)
.end((err, res) => {
if (err) { return callback(false, err.toString()) };
try {
const info = JSON.parse(res.text);
this.info = info;
callback(info['version'] !== localVer, info);
} catch (e) {
return callback(false, e.toString());
}
});
}
// 下载更新
// 参数{downloadUrl: 下载地址, md5: 校验MD5, callback: 回调(成功?(true, null):(false, err))}
download(downloadUrl, md5, callback) {
// 创建临时文件
const tmpDir = os.tmpDir();
const fileName = '.antSword-' + (+new Date);
const tmpFileName = path.join(tmpDir, fileName);
// 当前目录环境
const curDir = path.join(__dirname, '../../');
// 开始下载文件
nugget(
downloadUrl,
{
target: fileName,
dir: tmpDir,
resume: true,
verbose: true,
strictSSL: downloadUrl.startsWith('https')
},
(err) => {
if (err) { return callback(false, err.toString()) };
// 校验MD5
const _md5 = crypto.createHash('md5').update(fs.readFileSync(tmpFileName)).digest('hex');
if (_md5 !== md5) { return callback(false, { type: 'md5', err: _md5 }) };
// ZIP解压
unzip(tmpFileName, {
dir: tmpDir
}, (e) => {
if (e) { return (callback(false, { type: 'unzip', err: e })) };
// 删除旧asar
// fs.unlinkSync(path.join(curDir, 'app.asar'));
// 移动新asar
fs.rename(
path.join(tmpDir, 'antSword.update'),
path.join(curDir, 'app.asar'),
(_e) => {
_e ? callback(false, _e.toString()) : callback(true);
}
);
});
}
);
}
}
module.exports = Update;
\ No newline at end of file
{
"name": "antsword",
"version": "1.1.2",
"version": "1.1.1",
"description": "中国蚁剑是一款跨平台的开源网站管理工具",
"main": "app.js",
"dependencies": {
......@@ -8,12 +8,13 @@
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-stage-0": "^6.5.0",
"electron-prebuilt": "^0.36.10",
"electron-prebuilt": "^0.37.3",
"extract-zip": "^1.5.0",
"iconv-lite": "^0.4.13",
"lib-qqwry": "^0.0.5",
"log4js": "^0.6.29",
"nedb": "^1.5.1",
"nugget": "^2.0.0",
"superagent": "^1.6.1",
"superagent-proxy": "^1.0.0",
"webpack": "^1.12.14"
......@@ -28,14 +29,11 @@
"type": "git",
"url": "https://github.com/antoor/antSword"
},
"debug": true,
"debug": false,
"update": {
"md5": "f7287aa765941d1e039f5b654344ccc5",
"logs": "测试更新日志1\n测试更新日志2\n新增在线更新功能\n新增文件管理模块双击文件打开编辑窗口(如果文件大小<100kb)",
"sources": {
"test": "http://192.168.1.6:8000/1.1.0/update.zip",
"github": "https://github.com/antoor/antSword/releases/download/1.1.0/app.asar.zip"
}
"md5": "",
"logs": "",
"sources": {}
},
"bugs": {
"url": "https://github.com/antoor/antSword/issues"
......
......@@ -403,8 +403,33 @@ module.exports = {
},
update: {
title: 'Check update',
current: 'Current version',
toolbar: {
check: 'Check'
},
check: {
ing: 'Check for updates..',
fail: (err) => `Check for update failed!<br/>${err}`,
none: (ver) => `After examination, no update![v${ver}]`,
found: (ver) => `Found a new version [v${ver}]`
},
prompt: {
btns: {
ok: 'Update',
no: 'Cancel'
},
title: 'Update to version',
changelog: 'Change Logs: ',
sources: 'Download source: ',
fail: {
md5: 'File MD5 value check failed!',
unzip: (err) => `Unzip the file failed! [${err}]`
}
},
message: {
ing: 'Downloading..',
fail: (err) => `Update failed! [${err}]`,
success: 'Update success! Please manually restart the application later!'
}
},
aproxy: {
......
......@@ -404,8 +404,33 @@ module.exports = {
},
update: {
title: '检查更新',
current: '当前版本',
toolbar: {
check: '检查'
},
check: {
ing: '检查更新中。。',
fail: (err) => `检查更新失败!<br/>${err}`,
none: (ver) => `检查完毕,暂无更新!【v${ver}】`,
found: (ver) => `发现新版本【v${ver}】`
},
prompt: {
btns: {
ok: '更新',
no: '取消'
},
title: '版本更新',
changelog: '更新日志:',
sources: '更新来源:',
fail: {
md5: '文件MD5值校验失败!',
unzip: (err) => `解压文件失败!【${err}】`
}
},
message: {
ing: '努力更新中。。',
fail: (err) => `更新失败!【${err}】`,
success: '更新成功!请稍后手动重启应用!'
}
},
aproxy: {
......
......@@ -3,6 +3,7 @@
//
const LANG = antSword['language']['settings']['update'];
const LANG_T = antSword['language']['toastr'];
class Update {
constructor(sidebar) {
......@@ -15,19 +16,152 @@ class Update {
// toolbar
const toolbar = cell.attachToolbar();
toolbar.loadStruct([
{ id: 'check', type: 'button', text: LANG['toolbar']['check'], disabled: true, icon: 'check-square-o' },
{ type: 'separator' }
{
id: 'check',
type: 'button',
// 调试或者windows平台不支持更新
disabled: antSword['package']['debug'] || process.platform === 'win',
text: LANG['toolbar']['check'], icon: 'check-square-o'
}, { type: 'separator' }
]);
// toolbar点击事件
toolbar.attachEvent('onClick', (id) => {
switch(id) {
case 'check':
this.checkUpdate();
break;
}
});
// status
cell.attachHTMLString(`
${LANG['current']}: ${antSword['package']['version']}
`);
this.cell = cell;
}
// 检查更新
checkUpdate() {
this.cell.progressOn();
toastr.info(LANG['check']['ing'], LANG_T['info']);
// 后台检查更新
antSword['ipcRenderer']
.on('update-check', (event, ret) => {
this.cell.progressOff();
const info = ret['retVal'];
// 木有更新
if (!ret['hasUpdate']) {
return typeof info === 'string'
? toastr.error(LANG['check']['fail'](info), LANG_T['error'])
: toastr.info(LANG['check']['none'](info['version']), LANG_T['info']);
}
// 发现更新
toastr.success(LANG['check']['found'](info['version']), LANG_T['success']);
// 更新来源html
let sources_html = `<select id="ant-update-source">`;
for (let s in info['update']['sources']) {
sources_html += `<option value="${s}">${s}</option>`;
}
sources_html += `</select>`;
// 提示更新
layer.open({
type: 1,
shift: 2,
skin: 'ant-update',
btn: [LANG['prompt']['btns']['ok'], LANG['prompt']['btns']['no']],
closeBtn: 0,
title: `<i class="fa fa-cloud-download"></i> ${LANG['prompt']['title']}[v${info['version']}]`,
content: `
<strong>${LANG['prompt']['changelog']}</strong>
<ol>
<li>${info['update']['logs'].split('\n').join('</li><li>')}
</ol>
<strong>${LANG['prompt']['sources']}</strong>${sources_html}
`,
yes: () => {
// 获取更新选择地址
const download_source = $('#ant-update-source').val();
// 开始更新
// 更新动画
this.updateLoading();
// 通知后台
antSword['ipcRenderer']
.on('update-download', (event, ret) => {
// 下载失败
console.log(ret);
if (!ret['done']) {
if (typeof ret['retVal'] === 'object') {
switch(ret['retVal']['type']) {
case 'md5':
this.updateFail(LANG['prompt']['fail']['md5']);
break;
case 'unzip':
this.updateFail(LANG['prompt']['fail']['unzip'](ret['retVal']['err']));
break;
default:
this.updateFail(ret['retVal']);
}
} else {
this.updateFail(ret['retVal']);
}
return;
}
this.updateSuccess();
})
.send('update-download', download_source);
}
});
})
.send('update-check', {
local_ver: antSword['package']['version']
});
}
// 更新动画
updateLoading() {
// 删除按钮
$('.layui-layer-btn').remove();
// 加载动画
$('.layui-layer-content').html(`
<div class="pacman">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<p align="center"><strong>${LANG['message']['ing']}</strong></p>
`);
}
// 更新失败提示界面
updateFail(tip) {
$('.layui-layer-content').html(`
<div align="center" style="color: red">
<i class="fa fa-times-circle update-icon" />
<p><strong>${LANG['message']['fail'](tip)}</strong></p>
</div>
`);
toastr.error(LANG['message']['fail'](tip), LANG_T['error']);
setTimeout(layer.closeAll, 1024 * 5);
}
当前版本:${antSword['package']['version']}
<br/>
暂不支持在线更新!
<br />
请访问<strong style="color:#0099FF">${antSword['package']['repository']['url']}</strong>获取最新版本!
// 更新成功提示界面
updateSuccess() {
$('.layui-layer-content').html(`
<div align="center" style="color: green">
<i class="fa fa-check-circle update-icon" />
<p><strong>${LANG['message']['success']}</strong></p>
</div>
`);
toastr.success(LANG['message']['success'], LANG_T['success']);
setTimeout(() => {
antSword['ipcRenderer'].send('quit');
}, 1024 * 3);
}
}
......
......@@ -22,3 +22,145 @@ html, body, #container, #loading {
padding: 0 5px;
background-color: #666 !important;
}
/*update*/
.ant-update {
font-family: sans-serif;
font-size: 14px;
}
.ant-update > .layui-layer-content {
padding: 10px 10px;
min-width: 300px;
}
.ant-update > .layui-layer-content > h3 {
margin: 0;
}
.ant-update > .layui-layer-content > ol {
color: #666;
}
.update-icon {
font-size: 100px;
text-align: center;
}
/*update-loading*/
@-webkit-keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); }
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); } }
@keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); }
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); } }
@-webkit-keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); } }
@keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); } }
@-webkit-keyframes pacman-balls {
75% {
opacity: 0.7; }
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px); } }
@keyframes pacman-balls {
75% {
opacity: 0.7; }
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px); } }
.pacman {
position: relative;
margin-left: 30%;
}
.pacman > div:nth-child(2) {
-webkit-animation: pacman-balls 1s 0s infinite linear;
animation: pacman-balls 1s 0s infinite linear; }
.pacman > div:nth-child(3) {
-webkit-animation: pacman-balls 1s 0.33s infinite linear;
animation: pacman-balls 1s 0.33s infinite linear; }
.pacman > div:nth-child(4) {
-webkit-animation: pacman-balls 1s 0.66s infinite linear;
animation: pacman-balls 1s 0.66s infinite linear; }
.pacman > div:nth-child(5) {
-webkit-animation: pacman-balls 1s 0.99s infinite linear;
animation: pacman-balls 1s 0.99s infinite linear; }
.pacman > div:first-of-type {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #666;
border-left: 25px solid #666;
border-bottom: 25px solid #666;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up 0.5s 0s infinite;
animation: rotate_pacman_half_up 0.5s 0s infinite; }
.pacman > div:nth-child(2) {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #666;
border-left: 25px solid #666;
border-bottom: 25px solid #666;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down 0.5s 0s infinite;
animation: rotate_pacman_half_down 0.5s 0s infinite;
margin-top: -50px; }
.pacman > div:nth-child(3), .pacman > div:nth-child(4), .pacman > div:nth-child(5), .pacman > div:nth-child(6) {
background-color: #666;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
-webkit-transform: translate(0, -6.25px);
-ms-transform: translate(0, -6.25px);
transform: translate(0, -6.25px);
top: 25px;
left: 100px; }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment