app restart on update

This commit is contained in:
Antelle 2015-11-14 14:09:36 +03:00
parent a32fc8ef45
commit 9ec30ff533
12 changed files with 138 additions and 39 deletions

View File

@ -3,6 +3,9 @@
var fs = require('fs'),
path = require('path');
/* jshint node:true */
/* jshint browser:false */
var StringReplacePlugin = require('string-replace-webpack-plugin');
module.exports = function(grunt) {
@ -55,8 +58,8 @@ module.exports = function(grunt) {
},
clean: {
dist: ['dist', 'tmp'],
desktop_dist: ['dist/desktop'],
desktop_tmp: ['tmp/desktop']
'desktop_dist': ['dist/desktop'],
'desktop_tmp': ['tmp/desktop']
},
copy: {
html: {
@ -338,10 +341,12 @@ module.exports = function(grunt) {
},
compress: {
linux: {
options: {
archive: 'tmp/desktop/KeeWeb.linux.x64.zip'
},
options: { archive: 'tmp/desktop/KeeWeb.linux.x64.zip' },
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }]
},
'desktop_update': {
options: { archive: 'dist/desktop/UpdateDesktop.zip' },
files: [{ cwd: 'tmp/desktop/app', src: '**', expand: true }]
}
}
});
@ -364,11 +369,13 @@ module.exports = function(grunt) {
]);
grunt.registerTask('desktop', [
'default',
'gitinfo',
'clean:desktop_tmp',
'clean:desktop_dist',
'copy:desktop_app_content',
'string-replace:desktop_html',
'compress:desktop_update',
'electron',
'electron_builder',
'compress:linux',

View File

@ -46,9 +46,28 @@ if (window.process && window.process.versions && window.process.versions.electro
fileExists: function(path) {
return this.req('fs').existsSync(path);
},
preventExit: function(e) {
e.returnValue = false;
return false;
},
exit: function() {
Launcher.exitRequested = true;
this.remReq('app').quit();
this.requestExit();
},
requestExit: function() {
var app = this.remReq('app');
if (this.restartPending) {
app.quitAndRestart();
} else {
app.quit();
}
},
requestRestart: function() {
this.restartPending = true;
this.requestExit();
},
cancelRestart: function() {
this.restartPending = false;
}
};
window.launcherOpen = function(path) {

View File

@ -10,7 +10,7 @@ var Backbone = require('backbone'),
var Updater = {
UpdateInterval: 1000*60*60*24,
MinUpdateTimeout: 500*10,
MinUpdateTimeout: 500,
MinUpdateSize: 10000,
UpdateCheckFiles: ['index.html', 'app.js'],
nextCheckTimeout: null,
@ -18,6 +18,10 @@ var Updater = {
enabledAutoUpdate: function() {
return Launcher && AppSettingsModel.instance.get('autoUpdate');
},
updateInProgress: function() {
return UpdateModel.instance.get('status') === 'checking' ||
['downloading', 'extracting'].indexOf(UpdateModel.instance.get('updateStatus')) >= 0;
},
init: function() {
var willCheckNow = this.scheduleNextCheck();
if (!willCheckNow && this.enabledAutoUpdate()) {
@ -38,11 +42,11 @@ var Updater = {
timeDiff = Math.min(Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout), this.UpdateInterval);
}
this.nextCheckTimeout = setTimeout(this.check.bind(this), timeDiff);
console.log('Update check will happen in ' + Math.round(timeDiff / 1000) + ' s');
console.log('Update check will happen in ' + Math.round(timeDiff / 1000) + 's');
return timeDiff === this.MinUpdateTimeout;
},
check: function(startedByUser) {
if (!Launcher) {
if (!Launcher || this.updateInProgress()) {
return;
}
UpdateModel.instance.set('status', 'checking');
@ -72,6 +76,7 @@ var Updater = {
that.scheduleNextCheck();
return;
}
var prevLastVersion = UpdateModel.instance.get('lastVersion');
UpdateModel.instance.set({
status: 'ok',
lastCheckDate: dt,
@ -82,6 +87,11 @@ var Updater = {
});
UpdateModel.instance.save();
that.scheduleNextCheck();
if (prevLastVersion === UpdateModel.instance.get('lastVersion') &&
UpdateModel.instance.get('updateStatus') === 'ready') {
console.log('Waiting for the user to apply downloaded update');
return;
}
that.update(startedByUser);
},
error: function(e) {
@ -98,7 +108,7 @@ var Updater = {
},
update: function(startedByUser) {
var ver = UpdateModel.instance.get('lastVersion');
if (!Launcher || ver === RuntimeInfo.version || UpdateModel.instance.get('updateStatus')) {
if (!Launcher || ver === RuntimeInfo.version) {
console.log('You are using the latest version');
return;
}
@ -110,15 +120,17 @@ var Updater = {
file: 'KeeWeb-' + ver + '.zip',
cache: !startedByUser,
success: function(filePath) {
UpdateModel.instance.set('updateStatus', 'downloaded');
console.error('Extracting update file', that.UpdateCheckFiles, filePath);
UpdateModel.instance.set('updateStatus', 'extracting');
console.log('Extracting update file', that.UpdateCheckFiles, filePath);
that.extractAppUpdate(filePath, function(err) {
if (err) {
console.error('Error extracting update', err);
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error extracting update' });
} else {
UpdateModel.instance.set({ updateStatus: 'ready', updateError: null });
Backbone.trigger('update-app');
if (!startedByUser) {
Backbone.trigger('update-app');
}
}
});
},
@ -129,7 +141,8 @@ var Updater = {
});
},
extractAppUpdate: function(updateFile, expectedFiles, cb) {
extractAppUpdate: function(updateFile, cb) {
var expectedFiles = this.UpdateCheckFiles;
var appPath = Launcher.getUserDataPath();
var StreamZip = Launcher.req('node-stream-zip');
var zip = new StreamZip({ file: updateFile, storeEntries: true });

View File

@ -3,9 +3,9 @@
var Links = {
Repo: 'https://github.com/antelle/keeweb',
Desktop: 'https://github.com/antelle/keeweb/releases/latest',
WebApp: 'https://antelle.github.io/keeweb/',
WebApp: 'http://localhost:8088/',
License: 'https://github.com/antelle/keeweb/blob/master/MIT-LICENSE.txt',
UpdateDesktop: 'https://github.com/antelle/keeweb/releases/download/{ver}/UpdateDesktop.zip'
UpdateDesktop: 'http://localhost:8088/releases/download/{ver}/UpdateDesktop.zip'
};
module.exports = Links;

View File

@ -22,7 +22,10 @@ var AppSettingsModel = Backbone.Model.extend({
try {
var data;
if (Launcher) {
data = JSON.parse(Launcher.readFile(Launcher.getUserDataPath(FileName), 'utf8'));
var settingsFile = Launcher.getUserDataPath(FileName);
if (Launcher.fileExists(settingsFile)) {
data = JSON.parse(Launcher.readFile(settingsFile, 'utf8'));
}
} else if (typeof localStorage !== 'undefined' && localStorage.appSettings) {
data = JSON.parse(localStorage.appSettings);
}

View File

@ -198,16 +198,25 @@ var AppView = Backbone.View.extend({
beforeUnload: function(e) {
if (this.model.files.hasUnsavedFiles()) {
if (Launcher && !Launcher.exitRequested) {
Alerts.yesno({
header: 'Unsaved changes!',
body: 'You have unsaved files, all changes will be lost.',
buttons: [{result: 'yes', title: 'Exit and discard unsaved changes'}, {result: '', title: 'Don\'t exit'}],
success: function() {
Launcher.exit();
}
});
e.returnValue = false;
return false;
if (!this.exitAlertShown) {
var that = this;
that.exitAlertShown = true;
Alerts.yesno({
header: 'Unsaved changes!',
body: 'You have unsaved files, all changes will be lost.',
buttons: [{result: 'yes', title: 'Exit and discard unsaved changes'}, {result: '', title: 'Don\'t exit'}],
success: function () {
Launcher.exit();
},
cancel: function() {
Launcher.cancelRestart(false);
},
complete: function () {
that.exitAlertShown = false;
}
});
}
return Launcher.preventExit(e);
}
return 'You have unsaved files, all changes will be lost.';
}

View File

@ -15,6 +15,8 @@ var SettingsGeneralView = Backbone.View.extend({
'change .settings__general-theme': 'changeTheme',
'change .settings__general-expand': 'changeExpandGroups',
'change .settings__general-auto-update': 'changeAutoUpdate',
'click .settings__general-update-btn': 'checkUpdate',
'click .settings__general-restart-btn': 'restartApp',
'click .settings__general-dev-tools-link': 'openDevTools'
},
@ -26,6 +28,7 @@ var SettingsGeneralView = Backbone.View.extend({
initialize: function() {
this.listenTo(UpdateModel.instance, 'change:status', this.render, this);
this.listenTo(UpdateModel.instance, 'change:updateStatus', this.render, this);
},
render: function() {
@ -36,19 +39,13 @@ var SettingsGeneralView = Backbone.View.extend({
devTools: Launcher && Launcher.devTools,
canAutoUpdate: !!Launcher,
autoUpdate: Updater.enabledAutoUpdate(),
updateInfo: this.getUpdateInfo()
updateInProgress: Updater.updateInProgress(),
updateInfo: this.getUpdateInfo(),
updateReady: UpdateModel.instance.get('updateStatus') === 'ready'
});
},
getUpdateInfo: function() {
switch (UpdateModel.instance.get('updateStatus')) {
case 'downloading':
return 'Downloading update...';
case 'downloaded':
return 'Downloaded new version';
case 'error':
return 'Error downloading new version';
}
switch (UpdateModel.instance.get('status')) {
case 'checking':
return 'Checking for updates...';
@ -70,6 +67,14 @@ var SettingsGeneralView = Backbone.View.extend({
msg += 'new version ' + UpdateModel.instance.get('lastVersion') + ' available, released at ' +
Format.dStr(UpdateModel.instance.get('lastVersionReleaseDate'));
}
switch (UpdateModel.instance.get('updateStatus')) {
case 'downloading':
return msg + '. Downloading update...';
case 'extracting':
return msg + '. Extracting update...';
case 'error':
return msg + '. There was an error downloading new version';
}
return msg;
default:
return 'Never checked for updates';
@ -89,6 +94,14 @@ var SettingsGeneralView = Backbone.View.extend({
}
},
checkUpdate: function() {
Updater.check(true);
},
restartApp: function() {
Launcher.requestRestart();
},
changeExpandGroups: function(e) {
var expand = e.target.checked;
AppSettingsModel.instance.set('expandGroups', expand);

View File

@ -71,4 +71,14 @@
float: right;
display: none;
}
&__general-update-buttons {
margin-top: $base-spacing;
}
&__general-update-btn {
width: 15em;
}
&__general-restart-btn {
margin-left: $small-spacing;
}
}

View File

@ -20,6 +20,16 @@
<label for="settings__general-auto-update">Automatic updates</label>
<div><%- updateInfo %></div>
</div>
<div class="settings__general-update-buttons">
<% if (updateInProgress) { %>
<button class="settings__general-update-btn btn-silent" disabled>Checking for updates</button>
<% } else { %>
<button class="settings__general-update-btn btn-silent">Check for updates</button>
<% } %>
<% if (updateReady) { %>
<button class="settings__general-restart-btn">Restart to update</button>
<% } %>
</div>
<% } %>
<% if (devTools) { %>
<h2>Advanced</h2>

View File

@ -11,6 +11,7 @@ var app = require('app'),
var mainWindow = null,
openFile = process.argv.filter(function(arg) { return /\.kdbx$/i.test(arg); })[0],
ready = false,
restartPending = false,
htmlPath = path.join(__dirname, 'index.html');
process.argv.forEach(function(arg) {
@ -19,7 +20,9 @@ process.argv.forEach(function(arg) {
}
});
app.on('window-all-closed', function() { app.quit(); });
app.on('window-all-closed', function() {
app.quit();
});
app.on('ready', function() {
mainWindow = new BrowserWindow({
show: false,
@ -35,13 +38,25 @@ app.on('ready', function() {
notifyOpenFile();
}, 50);
});
mainWindow.on('closed', function() { mainWindow = null; });
mainWindow.on('closed', function() {
mainWindow = null;
});
});
app.on('open-file', function(e, path) {
e.preventDefault();
openFile = path;
notifyOpenFile();
});
app.on('quit', function() {
if (restartPending) {
require('child_process').exec(process.execPath);
}
});
app.quitAndRestart = function() {
restartPending = true;
app.quit();
setTimeout(function() { restartPending = false; }, 1000);
};
function setMenu() {
if (process.platform === 'darwin') {

View File

@ -2,7 +2,7 @@
"name": "KeeWeb",
"version": "0.2.1",
"description": "KeePass web app",
"main": "launcher.js",
"main": "main.js",
"repository": "https://github.com/antelle/keeweb",
"author": "Antelle",
"license": "MIT",