mirror of https://github.com/keeweb/keeweb.git
Merge branch 'alex-shpak-develop' into develop
This commit is contained in:
commit
1438ae5642
22
Gruntfile.js
22
Gruntfile.js
|
@ -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'
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue