Merge branch 'alex-shpak-develop' into develop

This commit is contained in:
antelle 2017-02-20 23:38:52 +01:00
commit 1438ae5642
17 changed files with 596 additions and 362 deletions

View File

@ -149,7 +149,8 @@ module.exports = function(grunt) {
},
clean: {
dist: ['dist', 'tmp'],
desktop: ['tmp/desktop', 'dist/desktop']
desktop: ['tmp/desktop', 'dist/desktop'],
cordova: ['tmp/cordova', 'dist/cordova']
},
copy: {
html: {
@ -270,6 +271,10 @@ module.exports = function(grunt) {
'desktop-html': {
options: { replacements: [{ pattern: ' manifest="manifest.appcache"', replacement: '' }] },
files: { 'tmp/desktop/app/index.html': 'dist/index.html' }
},
'cordova-html': {
options: { replacements: [{ pattern: '<script', replacement: '<script src="cordova.js"></script><script' }] },
files: { 'tmp/cordova/app/index.html': 'dist/index.html' }
}
},
webpack: {
@ -682,6 +687,16 @@ module.exports = function(grunt) {
'build-desktop-dist'
]);
grunt.registerTask('build-cordova-app-content', [
'string-replace:cordova-html'
]);
grunt.registerTask('build-cordova', [
'gitinfo',
'clean:cordova',
'build-cordova-app-content'
]);
// entry point tasks
grunt.registerTask('default', 'Default: build web app', [
@ -701,4 +716,9 @@ module.exports = function(grunt) {
'default',
'build-desktop'
]);
grunt.registerTask('cordova', 'Build cordova app', [
'default',
'build-cordova'
]);
};

View File

@ -2,6 +2,10 @@
const AppModel = require('./models/app-model');
const AppView = require('./views/app-view');
const AppSettingsModel = require('./models/app-settings-model');
const UpdateModel = require('./models/update-model');
const RuntimeDataModel = require('./models/runtime-data-model');
const FileInfoCollection = require('./collections/file-info-collection');
const KeyHandler = require('./comp/key-handler');
const IdleTracker = require('./comp/idle-tracker');
const PopupNotifier = require('./comp/popup-notifier');
@ -12,11 +16,14 @@ const AuthReceiver = require('./comp/auth-receiver');
const ExportApi = require('./comp/export-api');
const SettingsManager = require('./comp/settings-manager');
const PluginManager = require('./plugins/plugin-manager');
const Launcher = require('./comp/launcher');
const KdbxwebInit = require('./util/kdbxweb-init');
const Locale = require('./util/locale');
const Logger = require('./util/logger');
$(() => {
const ready = Launcher && Launcher.ready || $;
ready(() => {
if (isPopup()) {
return AuthReceiver.receive();
}
@ -41,6 +48,33 @@ $(() => {
}
});
const appModel = new AppModel();
SettingsManager.setBySettings(appModel.settings);
const configParam = getConfigParam();
if (configParam) {
appModel.loadConfig(configParam, err => {
SettingsManager.setBySettings(appModel.settings);
if (err && !appModel.settings.get('cacheConfigSettings')) {
showSettingsLoadError();
} else {
showApp();
}
});
} else {
showApp();
}
/* const appModel = new AppModel();
Promise.all([
AppSettingsModel.instance.load(),
UpdateModel.instance.load(),
RuntimeDataModel.instance.load(),
FileInfoCollection.instance.load()
])
.then(loadRemoteConfig())
.then(showApp); */
function isPopup() {
return (window.parent !== window.top) || window.opener;
}
@ -70,6 +104,26 @@ $(() => {
});
}
function loadRemoteConfig() {
return new Promise((resolve, reject) => {
SettingsManager.setBySettings(appModel.settings);
const configParam = getConfigParam();
if (configParam) {
appModel.loadConfig(configParam, err => {
SettingsManager.setBySettings(appModel.settings);
if (err && !appModel.settings.get('cacheConfigSettings')) {
showSettingsLoadError();
reject(err);
} else {
resolve();
}
});
} else {
resolve();
}
});
}
function showApp() {
const skipHttpsWarning = localStorage.skipHttpsWarning || appModel.settings.get('skipHttpsWarning');
const protocolIsInsecure = ['https:', 'file:', 'app:'].indexOf(location.protocol) < 0;

View File

@ -4,11 +4,11 @@ const Launcher = require('../comp/launcher');
const AutoTypeEmitterFactory = {
create: function(callback) {
if (!Launcher) {
return null;
if (Launcher && Launcher.autoTypeSupported) {
const AutoTypeEmitter = require('./emitter/auto-type-emitter-' + Launcher.platform());
return new AutoTypeEmitter(callback);
}
const AutoTypeEmitter = require('./emitter/auto-type-emitter-' + Launcher.platform());
return new AutoTypeEmitter(callback);
return null;
}
};

View File

@ -4,11 +4,11 @@ const Launcher = require('../comp/launcher');
const AutoTypeHelperFactory = {
create: function() {
if (!Launcher) {
return null;
if (Launcher && Launcher.autoTypeSupported) {
const AutoTypeHelper = require('./helper/auto-type-helper-' + Launcher.platform());
return new AutoTypeHelper();
}
const AutoTypeHelper = require('./helper/auto-type-helper-' + Launcher.platform());
return new AutoTypeHelper();
return null;
}
};

View File

@ -21,7 +21,7 @@ const AutoTypeNativeHelper = {
const helperCTime = -1;
possiblePaths.forEach(possiblePath => {
try {
const ctime = Launcher.statFile(possiblePath).ctime;
const ctime = Launcher.statFileSync(possiblePath).ctime;
if (ctime > helperCTime) {
helperPath = possiblePath;
}

View File

@ -16,7 +16,7 @@ const clearTextAutoTypeLog = localStorage.autoTypeDebug;
const AutoType = {
helper: AutoTypeHelperFactory.create(),
enabled: !!Launcher,
enabled: !!(Launcher && Launcher.autoTypeSupported),
selectEntryView: false,
pendingEvent: null,
running: false,

View File

@ -11,9 +11,21 @@ const FileInfoCollection = Backbone.Collection.extend({
},
load: function () {
const data = SettingsStore.load('file-info');
return new Promise((resolve, reject) => {
SettingsStore.load('file-info', (data, err) => {
if (err) {
reject(err);
} else {
this.onLoaded(data);
resolve();
}
});
});
},
onLoaded: function(data) {
if (data) {
this.reset(data, {silent: true});
this.reset(data, { silent: true });
}
},
@ -38,10 +50,6 @@ const FileInfoCollection = Backbone.Collection.extend({
}
});
FileInfoCollection.load = function() {
const coll = new FileInfoCollection();
coll.load();
return coll;
};
FileInfoCollection.instance = new FileInfoCollection();
module.exports = FileInfoCollection;

View File

@ -0,0 +1,265 @@
'use strict';
const Backbone = require('backbone');
const Locale = require('../util/locale');
const Logger = require('../util/logger');
const logger = new Logger('launcher');
const Launcher = {
name: 'electron',
version: window.process.versions.electron,
autoTypeSupported: true,
req: window.require,
ready: function(callback) {
$(callback);
},
platform: function() {
return process.platform;
},
electron: function() {
return this.req('electron');
},
remoteApp: function() {
return this.electron().remote.app;
},
remReq: function(mod) {
return this.electron().remote.require(mod);
},
openLink: function(href) {
this.electron().shell.openExternal(href);
},
devTools: true,
openDevTools: function() {
this.electron().remote.getCurrentWindow().openDevTools();
},
getSaveFileName: function(defaultPath, cb) {
if (defaultPath) {
const homePath = this.remReq('electron').app.getPath('userDesktop');
defaultPath = this.req('path').join(homePath, defaultPath);
}
this.remReq('electron').dialog.showSaveDialog({
title: Locale.launcherSave,
defaultPath: defaultPath,
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
}, cb);
},
getUserDataPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('userData'), fileName || '');
},
getTempPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('temp'), fileName || '');
},
getDocumentsPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('documents'), fileName || '');
},
getAppPath: function(fileName) {
return this.req('path').join(this.remoteApp().getAppPath(), fileName || '');
},
getWorkDirPath: function(fileName) {
return this.req('path').join(process.cwd(), fileName || '');
},
writeFile: function(path, data, callback) {
this.req('fs').writeFile(path, new window.Buffer(data), callback);
},
readFile: function(path, encoding, callback) {
this.req('fs').readFile(path, encoding, (err, contents) => {
const data = typeof contents === 'string' ? contents : new Uint8Array(contents);
callback(data, err);
});
},
fileExists: function(path, callback) {
this.req('fs').exists(path, callback);
},
deleteFile: function(path, callback) {
this.req('fs').unlink(path, callback);
},
statFile: function(path, callback) {
this.req('fs').stat(path, (err, stats) => callback(stats, err));
},
statFileSync: function(path) {
this.req('fs').statSync(path);
},
mkdir: function(dir, callback) {
const fs = this.req('fs');
const path = this.req('path');
const stack = [];
const collect = function(dir, stack, callback) {
fs.exists(dir, exists => {
if (exists) {
return callback();
}
stack.unshift(dir);
const newDir = path.dirname(dir);
if (newDir === dir || !newDir || newDir === '.' || newDir === '/') {
return callback();
}
collect(newDir, stack, callback);
});
};
const create = function(stack, callback) {
if (!stack.length) {
return callback && callback();
}
fs.mkdir(stack.shift(), err =>
err ? callback(err) : create(stack, callback)
);
};
collect(dir, stack, () => create(stack, callback));
},
parsePath: function(fileName) {
const path = this.req('path');
return {
path: fileName,
dir: path.dirname(fileName),
file: path.basename(fileName)
};
},
createFsWatcher: function(path) {
return this.req('fs').watch(path, { persistent: false });
},
ensureRunnable: function(path) {
if (process.platform !== 'win32') {
const fs = this.req('fs');
const stat = fs.statSync(path);
if ((stat.mode & 0o0111) === 0) {
const mode = stat.mode | 0o0100;
logger.info(`chmod 0${mode.toString(8)} ${path}`);
fs.chmodSync(path, mode);
}
}
},
preventExit: function(e) {
e.returnValue = false;
return false;
},
exit: function() {
this.exitRequested = true;
this.requestExit();
},
requestExit: function() {
const app = this.remoteApp();
if (this.restartPending) {
app.restartApp();
} else {
app.quit();
}
},
requestRestart: function() {
this.restartPending = true;
this.requestExit();
},
cancelRestart: function() {
this.restartPending = false;
},
setClipboardText: function(text) {
return this.electron().clipboard.writeText(text);
},
getClipboardText: function() {
return this.electron().clipboard.readText();
},
clearClipboardText: function() {
return this.electron().clipboard.clear();
},
minimizeApp: function() {
this.remoteApp().minimizeApp();
},
canMinimize: function() {
return process.platform !== 'darwin';
},
updaterEnabled: function() {
return this.electron().remote.process.argv.indexOf('--disable-updater') === -1;
},
getMainWindow: function() {
return this.remoteApp().getMainWindow();
},
resolveProxy: function(url, callback) {
const window = this.getMainWindow();
const session = window.webContents.session;
session.resolveProxy(url, proxy => {
const match = /^proxy\s+([\w\.]+):(\d+)+\s*/i.exec(proxy);
proxy = match && match[1] ? { host: match[1], port: +match[2] } : null;
callback(proxy);
});
},
openWindow: function(opts) {
return this.remoteApp().openWindow(opts);
},
hideApp: function() {
const app = this.remoteApp();
if (this.canMinimize()) {
app.getMainWindow().minimize();
} else {
app.hide();
}
},
isAppFocused: function() {
return !!this.electron().remote.BrowserWindow.getFocusedWindow();
},
showMainWindow: function() {
const win = this.getMainWindow();
win.show();
win.restore();
},
spawn: function(config) {
const ts = logger.ts();
let complete = config.complete;
const ps = this.req('child_process').spawn(config.cmd, config.args);
[ps.stdin, ps.stdout, ps.stderr].forEach(s => s.setEncoding('utf-8'));
let stderr = '';
let stdout = '';
ps.stderr.on('data', d => { stderr += d.toString('utf-8'); });
ps.stdout.on('data', d => { stdout += d.toString('utf-8'); });
ps.on('close', code => {
stdout = stdout.trim();
stderr = stderr.trim();
const msg = 'spawn ' + config.cmd + ': ' + code + ', ' + logger.ts(ts);
if (code) {
logger.error(msg + '\n' + stdout + '\n' + stderr);
} else {
logger.info(msg + (stdout ? '\n' + stdout : ''));
}
if (complete) {
complete(code ? 'Exit code ' + code : null, stdout, code);
complete = null;
}
});
ps.on('error', err => {
logger.error('spawn error: ' + config.cmd + ', ' + logger.ts(ts), err);
if (complete) {
complete(err);
complete = null;
}
});
if (config.data) {
try {
ps.stdin.write(config.data);
ps.stdin.end();
} catch (e) {
logger.error('spawn write error', e);
}
}
return ps;
}
};
Backbone.on('launcher-exit-request', () => {
setTimeout(() => Launcher.exit(), 0);
});
Backbone.on('launcher-minimize', () => setTimeout(() => Backbone.trigger('app-minimized'), 0));
window.launcherOpen = function(path) {
Backbone.trigger('launcher-open-file', path);
};
if (window.launcherOpenedFile) {
logger.info('Open file request', window.launcherOpenedFile);
Backbone.trigger('launcher-open-file', window.launcherOpenedFile);
delete window.launcherOpenedFile;
}
module.exports = Launcher;

View File

@ -1,239 +1,11 @@
'use strict';
const Backbone = require('backbone');
const Locale = require('../util/locale');
const Logger = require('../util/logger');
let Launcher;
const logger = new Logger('launcher');
if (window.process && window.process.versions && window.process.versions.electron) {
Launcher = {
name: 'electron',
version: window.process.versions.electron,
req: window.require,
electron: function() {
return this.req('electron');
},
remoteApp: function() {
return this.electron().remote.app;
},
remReq: function(mod) {
return this.electron().remote.require(mod);
},
openLink: function(href) {
this.electron().shell.openExternal(href);
},
devTools: true,
openDevTools: function() {
this.electron().remote.getCurrentWindow().openDevTools();
},
getSaveFileName: function(defaultPath, cb) {
if (defaultPath) {
const homePath = this.remReq('electron').app.getPath('userDesktop');
defaultPath = this.req('path').join(homePath, defaultPath);
}
this.remReq('electron').dialog.showSaveDialog({
title: Locale.launcherSave,
defaultPath: defaultPath,
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
}, cb);
},
getUserDataPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('userData'), fileName || '');
},
getTempPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('temp'), fileName || '');
},
getDocumentsPath: function(fileName) {
return this.req('path').join(this.remoteApp().getPath('documents'), fileName || '');
},
getAppPath: function(fileName) {
return this.req('path').join(this.remoteApp().getAppPath(), fileName || '');
},
getWorkDirPath: function(fileName) {
return this.req('path').join(process.cwd(), fileName || '');
},
writeFile: function(path, data) {
this.req('fs').writeFileSync(path, new window.Buffer(data));
},
readFile: function(path, encoding) {
const contents = this.req('fs').readFileSync(path, encoding);
return typeof contents === 'string' ? contents : new Uint8Array(contents);
},
fileExists: function(path) {
return this.req('fs').existsSync(path);
},
deleteFile: function(path) {
this.req('fs').unlinkSync(path);
},
statFile: function(path) {
return this.req('fs').statSync(path);
},
ensureRunnable: function(path) {
if (process.platform !== 'win32') {
const fs = this.req('fs');
const stat = fs.statSync(path);
if ((stat.mode & 0o0111) === 0) {
const mode = stat.mode | 0o0100;
logger.info(`chmod 0${mode.toString(8)} ${path}`);
fs.chmodSync(path, mode);
}
}
},
mkdir: function(dir) {
const fs = this.req('fs');
const path = this.req('path');
const stack = [];
while (true) {
if (fs.existsSync(dir)) {
break;
}
stack.unshift(dir);
const newDir = path.dirname(dir);
if (newDir === dir || !newDir || newDir === '.' || newDir === '/') {
break;
}
dir = newDir;
}
stack.forEach(dir => fs.mkdirSync(dir));
},
parsePath: function(fileName) {
const path = this.req('path');
return { path: fileName, dir: path.dirname(fileName), file: path.basename(fileName) };
},
createFsWatcher: function(path) {
return this.req('fs').watch(path, { persistent: false });
},
preventExit: function(e) {
e.returnValue = false;
return false;
},
exit: function() {
this.exitRequested = true;
this.requestExit();
},
requestExit: function() {
const app = this.remoteApp();
if (this.restartPending) {
app.restartApp();
} else {
app.quit();
}
},
requestRestart: function() {
this.restartPending = true;
this.requestExit();
},
cancelRestart: function() {
this.restartPending = false;
},
setClipboardText: function(text) {
return this.electron().clipboard.writeText(text);
},
getClipboardText: function() {
return this.electron().clipboard.readText();
},
clearClipboardText: function() {
return this.electron().clipboard.clear();
},
minimizeApp: function() {
this.remoteApp().minimizeApp();
},
canMinimize: function() {
return process.platform !== 'darwin';
},
updaterEnabled: function() {
return this.electron().remote.process.argv.indexOf('--disable-updater') === -1;
},
getMainWindow: function() {
return this.remoteApp().getMainWindow();
},
resolveProxy: function(url, callback) {
const window = this.getMainWindow();
const session = window.webContents.session;
session.resolveProxy(url, proxy => {
const match = /^proxy\s+([\w\.]+):(\d+)+\s*/i.exec(proxy);
proxy = match && match[1] ? { host: match[1], port: +match[2] } : null;
callback(proxy);
});
},
openWindow: function(opts) {
return this.remoteApp().openWindow(opts);
},
hideApp: function() {
const app = this.remoteApp();
if (this.canMinimize()) {
app.getMainWindow().minimize();
} else {
app.hide();
}
},
isAppFocused: function() {
return !!this.electron().remote.BrowserWindow.getFocusedWindow();
},
showMainWindow: function() {
const win = this.getMainWindow();
win.show();
win.restore();
},
spawn: function(config) {
const ts = logger.ts();
let complete = config.complete;
const ps = this.req('child_process').spawn(config.cmd, config.args);
[ps.stdin, ps.stdout, ps.stderr].forEach(s => s.setEncoding('utf-8'));
let stderr = '';
let stdout = '';
ps.stderr.on('data', d => { stderr += d.toString('utf-8'); });
ps.stdout.on('data', d => { stdout += d.toString('utf-8'); });
ps.on('close', code => {
stdout = stdout.trim();
stderr = stderr.trim();
const msg = 'spawn ' + config.cmd + ': ' + code + ', ' + logger.ts(ts);
if (code) {
logger.error(msg + '\n' + stdout + '\n' + stderr);
} else {
logger.info(msg + (stdout ? '\n' + stdout : ''));
}
if (complete) {
complete(code ? 'Exit code ' + code : null, stdout, code);
complete = null;
}
});
ps.on('error', err => {
logger.error('spawn error: ' + config.cmd + ', ' + logger.ts(ts), err);
if (complete) {
complete(err);
complete = null;
}
});
if (config.data) {
try {
ps.stdin.write(config.data);
ps.stdin.end();
} catch (e) {
logger.error('spawn write error', e);
}
}
return ps;
},
platform: function() {
return process.platform;
}
};
Backbone.on('launcher-exit-request', () => {
setTimeout(() => Launcher.exit(), 0);
});
Backbone.on('launcher-minimize', () => setTimeout(() => Backbone.trigger('app-minimized'), 0));
window.launcherOpen = function(path) {
Backbone.trigger('launcher-open-file', path);
};
if (window.launcherOpenedFile) {
logger.info('Open file request', window.launcherOpenedFile);
Backbone.trigger('launcher-open-file', window.launcherOpenedFile);
delete window.launcherOpenedFile;
}
Launcher = require('./launcher-electron');
} else if (window.cordova) {
// Launcher = require('./launcher-cordova');
}
module.exports = Launcher;

View File

@ -7,36 +7,56 @@ const Logger = require('../util/logger');
const logger = new Logger('settings');
const SettingsStore = {
fileName: function(key) {
return key + '.json';
return `${key}.json`;
},
load: function(key) {
load: function(key, callback) {
try {
if (Launcher) {
const settingsFile = Launcher.getUserDataPath(this.fileName(key));
if (Launcher.fileExists(settingsFile)) {
return JSON.parse(Launcher.readFile(settingsFile, 'utf8'));
}
Launcher.fileExists(settingsFile, exists => {
if (exists) {
Launcher.readFile(settingsFile, 'utf8', (data, err) => {
if (data) {
callback(JSON.parse(data));
} else {
logger.error(`Error loading ${key}`, err);
callback(undefined, err);
}
});
} else {
callback();
}
});
} else {
const data = localStorage[StringUtil.camelCase(key)];
return data ? JSON.parse(data) : undefined;
callback(data ? JSON.parse(data) : undefined);
}
} catch (e) {
logger.error('Error loading ' + key, e);
logger.error(`Error loading ${key}`, e);
callback(undefined, e);
}
return null;
},
save: function(key, data) {
save: function(key, data, callback) {
try {
if (Launcher) {
Launcher.writeFile(Launcher.getUserDataPath(this.fileName(key)), JSON.stringify(data));
const settingsFile = Launcher.getUserDataPath(this.fileName(key));
Launcher.writeFile(settingsFile, JSON.stringify(data), err => {
if (err) {
logger.error(`Error saving ${key}`, err);
}
if (callback) { callback(err); }
});
} else if (typeof localStorage !== 'undefined') {
localStorage[StringUtil.camelCase(key)] = JSON.stringify(data);
if (callback) { callback(); }
}
} catch (e) {
logger.error('Error saving ' + key, e);
logger.error(`Error saving ${key}`, e);
if (callback) { callback(e); }
}
}
};

View File

@ -27,7 +27,7 @@ const AppModel = Backbone.Model.extend({
initialize: function() {
this.tags = [];
this.files = new FileCollection();
this.fileInfos = FileInfoCollection.load();
this.fileInfos = FileInfoCollection.instance;
this.menu = new MenuModel();
this.filter = {};
this.sort = 'title';
@ -858,14 +858,14 @@ const AppModel = Backbone.Model.extend({
if (Storage[backup.storage].getPathForName) {
folderPath = Storage[backup.storage].getPathForName(folderPath).replace('.kdbx', '');
}
Storage[backup.storage].stat(folderPath, opts, (err) => {
Storage[backup.storage].stat(folderPath, opts, err => {
if (err) {
if (err.notFound) {
logger.info('Backup folder does not exist');
if (!Storage[backup.storage].mkdir) {
return callback('Mkdir not supported by ' + backup.storage);
}
Storage[backup.storage].mkdir(folderPath, (err) => {
Storage[backup.storage].mkdir(folderPath, err => {
if (err) {
logger.error('Error creating backup folder', err);
callback('Error creating backup folder');

View File

@ -49,12 +49,21 @@ const AppSettingsModel = Backbone.Model.extend({
},
load: function() {
const data = SettingsStore.load('app-settings');
return new Promise((resolve, reject) => {
SettingsStore.load('app-settings', (data, err) => {
if (err) {
reject(err);
} else {
this.onLoaded(data);
resolve();
}
});
});
},
onLoaded: function(data) {
if (data) {
this.upgrade(data);
}
if (data) {
this.set(data, {silent: true});
}
},
@ -74,6 +83,5 @@ const AppSettingsModel = Backbone.Model.extend({
});
AppSettingsModel.instance = new AppSettingsModel();
AppSettingsModel.instance.load();
module.exports = AppSettingsModel;

View File

@ -11,7 +11,19 @@ const RuntimeDataModel = Backbone.Model.extend({
},
load: function() {
const data = SettingsStore.load('runtime-data');
return new Promise((resolve, reject) => {
SettingsStore.load('runtime-data', (data, err) => {
if (err) {
reject(err);
} else {
this.onLoaded(data);
resolve();
}
});
});
},
onLoaded: function(data) {
if (data) {
this.set(data, {silent: true});
}
@ -23,6 +35,5 @@ const RuntimeDataModel = Backbone.Model.extend({
});
RuntimeDataModel.instance = new RuntimeDataModel();
RuntimeDataModel.instance.load();
module.exports = RuntimeDataModel;

View File

@ -21,7 +21,19 @@ const UpdateModel = Backbone.Model.extend({
},
load: function() {
const data = SettingsStore.load('update-info');
return new Promise((resolve, reject) => {
SettingsStore.load('update-info', (data, err) => {
if (err) {
reject(err);
} else {
this.onLoaded(data);
resolve();
}
});
});
},
onLoaded: function(data) {
if (data) {
try {
_.each(data, (val, key) => {
@ -46,6 +58,5 @@ const UpdateModel = Backbone.Model.extend({
});
UpdateModel.instance = new UpdateModel();
UpdateModel.instance.load();
module.exports = UpdateModel;

View File

@ -2,13 +2,16 @@
const Launcher = require('../comp/launcher');
const Storage = {
const BuiltInStorage = {
file: require('./storage-file'),
dropbox: require('./storage-dropbox'),
webdav: require('./storage-webdav'),
gdrive: require('./storage-gdrive'),
onedrive: require('./storage-onedrive'),
cache: Launcher ? require('./storage-file-cache') : require('./storage-cache')
};
module.exports = Storage;
const ThirdPartyStorage = {
dropbox: require('./storage-dropbox'),
webdav: require('./storage-webdav'),
gdrive: require('./storage-gdrive'),
onedrive: require('./storage-onedrive')
};
module.exports = _.extend({}, BuiltInStorage, ThirdPartyStorage);

View File

@ -18,18 +18,21 @@ const StorageFileCache = StorageBase.extend({
if (this.path) {
return callback && callback();
}
try {
const path = Launcher.getUserDataPath('OfflineFiles');
const fs = Launcher.req('fs');
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
const path = Launcher.getUserDataPath('OfflineFiles');
const setPath = (err) => {
this.path = err ? null : path;
return callback && callback(err);
};
Launcher.fileExists(path, exists => {
if (exists) {
setPath();
} else {
Launcher.mkdir(path, setPath());
}
this.path = path;
callback();
} catch (e) {
this.logger.error('Error opening local offline storage', e);
if (callback) { callback(e); }
}
});
},
save: function(id, opts, data, callback) {
@ -39,14 +42,14 @@ const StorageFileCache = StorageBase.extend({
return callback && callback(err);
}
const ts = this.logger.ts();
try {
Launcher.writeFile(this.getPath(id), data);
Launcher.writeFile(this.getPath(id), data, err => {
if (err) {
this.logger.error('Error saving to cache', id, err);
return callback && callback(err);
}
this.logger.debug('Saved', id, this.logger.ts(ts));
if (callback) { callback(); }
} catch (e) {
this.logger.error('Error saving to cache', id, e);
if (callback) { callback(e); }
}
});
});
},
@ -56,15 +59,17 @@ const StorageFileCache = StorageBase.extend({
if (err) {
return callback && callback(null, err);
}
const ts = this.logger.ts();
try {
const data = Launcher.readFile(this.getPath(id));
Launcher.readFile(this.getPath(id), undefined, (data, err) => {
if (err) {
this.logger.error('Error loading from cache', id, err);
return callback && callback(err, null);
}
this.logger.debug('Loaded', id, this.logger.ts(ts));
if (callback) { callback(null, data.buffer); }
} catch (e) {
this.logger.error('Error loading from cache', id, e);
if (callback) { callback(e, null); }
}
return callback && callback(null, data.buffer);
});
});
},
@ -74,18 +79,24 @@ const StorageFileCache = StorageBase.extend({
if (err) {
return callback && callback(err);
}
const ts = this.logger.ts();
try {
const path = this.getPath(id);
if (Launcher.fileExists(path)) {
Launcher.deleteFile(path);
const path = this.getPath(id);
Launcher.fileExists(path, exists => {
if (exists) {
Launcher.deleteFile(path, err => {
if (err) {
this.logger.error('Error removing from cache', id, err);
} else {
this.logger.debug('Removed', id, this.logger.ts(ts));
}
return callback && callback(err);
});
} else if (callback) {
callback();
}
this.logger.debug('Removed', id, this.logger.ts(ts));
if (callback) { callback(); }
} catch (e) {
this.logger.error('Error removing from cache', id, e);
if (callback) { callback(e); }
}
});
});
}
});

View File

@ -15,71 +15,110 @@ const StorageFile = StorageBase.extend({
load: function(path, opts, callback) {
this.logger.debug('Load', path);
const ts = this.logger.ts();
try {
const data = Launcher.readFile(path);
const rev = Launcher.statFile(path).mtime.getTime().toString();
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
if (callback) { callback(null, data.buffer, { rev: rev }); }
} catch (e) {
const onError = e => {
this.logger.error('Error reading local file', path, e);
if (callback) { callback(e, null); }
}
if (callback) {
callback(e, null);
}
};
Launcher.readFile(path, undefined, (data, err) => {
if (err) {
return onError(err);
}
Launcher.statFile(path, (stat, err) => {
if (err) {
return onError(err);
}
const rev = stat.mtime.getTime().toString();
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
if (callback) {
callback(null, data.buffer, { rev: rev });
}
});
});
},
stat: function(path, opts, callback) {
this.logger.debug('Stat', path);
const ts = this.logger.ts();
try {
const stat = Launcher.statFile(path);
this.logger.debug('Stat done', path, this.logger.ts(ts));
if (callback) { callback(null, { rev: stat.mtime.getTime().toString() }); }
} catch (e) {
this.logger.error('Error stat local file', path, e);
if (e.code === 'ENOENT') {
e.notFound = true;
Launcher.statFile(path, (stat, err) => {
if (err) {
this.logger.error('Error stat local file', path, err);
if (err.code === 'ENOENT') {
err.notFound = true;
}
return callback && callback(err, null);
}
if (callback) { callback(e, null); }
}
this.logger.debug('Stat done', path, this.logger.ts(ts));
if (callback) {
const fileRev = stat.mtime.getTime().toString();
callback(null, { rev: fileRev });
}
});
},
save: function(path, opts, data, callback, rev) {
this.logger.debug('Save', path, rev);
const ts = this.logger.ts();
try {
if (rev) {
try {
const stat = Launcher.statFile(path);
const fileRev = stat.mtime.getTime().toString();
if (fileRev !== rev) {
this.logger.debug('Save mtime differs', rev, fileRev);
if (callback) { callback({ revConflict: true }, { rev: fileRev }); }
return;
}
} catch (e) {
// file doesn't exist or we cannot stat it: don't care and overwrite
}
}
Launcher.writeFile(path, data);
const newRev = Launcher.statFile(path).mtime.getTime().toString();
this.logger.debug('Saved', path, this.logger.ts(ts));
if (callback) { callback(undefined, { rev: newRev }); }
} catch (e) {
const onError = e => {
this.logger.error('Error writing local file', path, e);
if (callback) { callback(e); }
if (callback) {
callback(e);
}
};
const write = () => {
Launcher.writeFile(path, data, err => {
if (err) {
return onError(err);
}
Launcher.statFile(path, (stat, err) => {
if (err) {
return onError(err);
}
const newRev = stat.mtime.getTime().toString();
this.logger.debug('Saved', path, this.logger.ts(ts));
if (callback) {
callback(undefined, { rev: newRev });
}
});
});
};
if (rev) {
Launcher.statFile(path, (stat, err) => {
if (err) {
return onError(err);
}
const fileRev = stat.mtime.getTime().toString();
if (fileRev !== rev) {
this.logger.debug('Save mtime differs', rev, fileRev);
return callback && callback({ revConflict: true }, { rev: fileRev });
}
write();
});
} else {
write();
}
},
mkdir: function(path, callback) {
this.logger.debug('Make dir', path);
const ts = this.logger.ts();
try {
Launcher.mkdir(path);
this.logger.debug('Made dir', path, this.logger.ts(ts));
if (callback) { callback(); }
} catch (e) {
this.logger.error('Error making local dir', path, e);
if (callback) { callback(e); }
}
Launcher.mkdir(path, err => {
if (err) {
this.logger.error('Error making local dir', path, err);
if (callback) { callback('Error making local dir'); }
} else {
this.logger.debug('Made dir', path, this.logger.ts(ts));
if (callback) { callback(); }
}
});
},
watch: function(path, callback) {
@ -87,10 +126,22 @@ const StorageFile = StorageBase.extend({
if (!fileWatchers[names.dir]) {
this.logger.debug('Watch dir', names.dir);
const fsWatcher = Launcher.createFsWatcher(names.dir);
fsWatcher.on('change', this.fsWatcherChange.bind(this, names.dir));
fileWatchers[names.dir] = { fsWatcher: fsWatcher, callbacks: [] };
if (fsWatcher) {
fsWatcher.on('change', this.fsWatcherChange.bind(this, names.dir));
fileWatchers[names.dir] = {
fsWatcher: fsWatcher,
callbacks: []
};
}
}
const fsWatcher = fileWatchers[names.dir];
if (fsWatcher) {
fsWatcher.callbacks.push({
file: names.file,
callback: callback
});
}
fileWatchers[names.dir].callbacks.push({ file: names.file, callback: callback });
},
unwatch: function(path) {