mirror of https://github.com/keeweb/keeweb.git
logging sync
This commit is contained in:
parent
f59cdeb10c
commit
3dab9af066
|
@ -88,8 +88,6 @@
|
|||
"globals" : {
|
||||
"require": true,
|
||||
"module": true,
|
||||
"console": true,
|
||||
"performance": true,
|
||||
"$": true,
|
||||
"_": true
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ var FileCollection = Backbone.Collection.extend({
|
|||
return this.some(function(file) { return file.get('modified'); });
|
||||
},
|
||||
|
||||
hasDirtyFiles: function() {
|
||||
return this.some(function(file) { return file.get('dirty'); });
|
||||
},
|
||||
|
||||
getByName: function(name) {
|
||||
return this.find(function(file) { return file.get('name') === name; });
|
||||
},
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
var Dropbox = require('dropbox'),
|
||||
Alerts = require('./alerts'),
|
||||
Launcher = require('./launcher'),
|
||||
Logger = require('../util/logger'),
|
||||
Links = require('../const/links');
|
||||
|
||||
var logger = new Logger('dropbox');
|
||||
|
||||
var DropboxKeys = {
|
||||
AppFolder: 'qp7ctun6qt5n9d6',
|
||||
AppFolderKeyParts: ['qp7ctun6', 'qt5n9d6'] // to allow replace key by sed, compare in this way
|
||||
|
@ -152,22 +155,29 @@ var DropboxLink = {
|
|||
|
||||
_handleUiError: function(err, alertCallback, callback) {
|
||||
if (!alertCallback) {
|
||||
alertCallback = Alerts.error.bind(Alerts);
|
||||
if (!Alerts.alertDisplayed) {
|
||||
alertCallback = Alerts.error.bind(Alerts);
|
||||
}
|
||||
}
|
||||
console.error('Dropbox error', err);
|
||||
logger.error('Dropbox error', err);
|
||||
switch (err.status) {
|
||||
case Dropbox.ApiError.INVALID_TOKEN:
|
||||
Alerts.yesno({
|
||||
icon: 'dropbox',
|
||||
header: 'Dropbox Login',
|
||||
body: 'To continue, you have to sign in to Dropbox.',
|
||||
buttons: [{result: 'yes', title: 'Sign In'}, {result: '', title: 'Cancel'}],
|
||||
success: (function() {
|
||||
this.authenticate(function(err) { callback(!err); });
|
||||
}).bind(this),
|
||||
cancel: function() { callback(false); }
|
||||
});
|
||||
return;
|
||||
if (!Alerts.alertDisplayed) {
|
||||
Alerts.yesno({
|
||||
icon: 'dropbox',
|
||||
header: 'Dropbox Login',
|
||||
body: 'To continue, you have to sign in to Dropbox.',
|
||||
buttons: [{result: 'yes', title: 'Sign In'}, {result: '', title: 'Cancel'}],
|
||||
success: (function () {
|
||||
this.authenticate(function (err) { callback(!err); });
|
||||
}).bind(this),
|
||||
cancel: function () {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Dropbox.ApiError.NOT_FOUND:
|
||||
alertCallback({
|
||||
header: 'Dropbox Sync Error',
|
||||
|
@ -218,7 +228,10 @@ var DropboxLink = {
|
|||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var ts = logger.ts();
|
||||
logger.debug('Call', callName);
|
||||
client[callName].apply(client, args.concat(function(err) {
|
||||
logger.debug('Result', callName, logger.ts(ts), arguments);
|
||||
if (err) {
|
||||
that._handleUiError(err, errorAlertCallback, function(repeat) {
|
||||
if (repeat) {
|
||||
|
@ -242,10 +255,10 @@ var DropboxLink = {
|
|||
Dropbox.AuthDriver.Popup.oauthReceiver();
|
||||
},
|
||||
|
||||
saveFile: function(fileName, data, rev, complete) {
|
||||
saveFile: function(fileName, data, rev, complete, alertCallback) {
|
||||
if (rev) {
|
||||
var opts = typeof rev === 'string' ? { lastVersionTag: rev, noOverwrite: true, noAutoRename: true } : undefined;
|
||||
this._callAndHandleError('writeFile', [fileName, data, opts], complete);
|
||||
this._callAndHandleError('writeFile', [fileName, data, opts], complete, alertCallback);
|
||||
} else {
|
||||
this.getFileList((function(err, files) {
|
||||
if (err) { return complete(err); }
|
||||
|
@ -260,8 +273,8 @@ var DropboxLink = {
|
|||
this._callAndHandleError('readFile', [fileName, { arrayBuffer: true }], complete, errorAlertCallback);
|
||||
},
|
||||
|
||||
stat: function(fileName, complete) {
|
||||
this._callAndHandleError('stat', [fileName], complete);
|
||||
stat: function(fileName, complete, errorAlertCallback) {
|
||||
this._callAndHandleError('stat', [fileName], complete, errorAlertCallback);
|
||||
},
|
||||
|
||||
getFileList: function(complete) {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('./launcher'),
|
||||
StringUtil = require('../util/string-util');
|
||||
StringUtil = require('../util/string-util'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('settings');
|
||||
|
||||
var SettingsStore = {
|
||||
fileName: function(key) {
|
||||
|
@ -20,7 +23,7 @@ var SettingsStore = {
|
|||
return data ? JSON.parse(data) : undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading ' + key, e);
|
||||
logger.error('Error loading ' + key, e);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
@ -33,7 +36,7 @@ var SettingsStore = {
|
|||
localStorage[StringUtil.camelCase(key)] = JSON.stringify(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error saving ' + key, e);
|
||||
logger.error('Error saving ' + key, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('./launcher');
|
||||
var Launcher = require('./launcher'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('transport');
|
||||
|
||||
var Transport = {
|
||||
httpGet: function(config) {
|
||||
|
@ -11,7 +14,7 @@ var Transport = {
|
|||
if (fs.existsSync(tmpFile)) {
|
||||
try {
|
||||
if (config.cache && fs.statSync(tmpFile).size > 0) {
|
||||
console.log('File already downloaded ' + config.url);
|
||||
logger.info('File already downloaded ' + config.url);
|
||||
return config.success(tmpFile);
|
||||
} else {
|
||||
fs.unlinkSync(tmpFile);
|
||||
|
@ -22,11 +25,11 @@ var Transport = {
|
|||
}
|
||||
}
|
||||
var proto = config.url.split(':')[0];
|
||||
console.log('GET ' + config.url);
|
||||
logger.info('GET ' + config.url);
|
||||
var opts = Launcher.req('url').parse(config.url);
|
||||
opts.headers = { 'User-Agent': navigator.userAgent };
|
||||
Launcher.req(proto).get(opts, function(res) {
|
||||
console.log('Response from ' + config.url + ': ' + res.statusCode);
|
||||
logger.info('Response from ' + config.url + ': ' + res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
if (config.file) {
|
||||
var file = fs.createWriteStream(tmpFile);
|
||||
|
@ -57,7 +60,7 @@ var Transport = {
|
|||
config.error('HTTP status ' + res.statusCode);
|
||||
}
|
||||
}).on('error', function(e) {
|
||||
console.error('Cannot GET ' + config.url, e);
|
||||
logger.error('Cannot GET ' + config.url, e);
|
||||
if (tmpFile) {
|
||||
fs.unlink(tmpFile);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ var Backbone = require('backbone'),
|
|||
Launcher = require('../comp/launcher'),
|
||||
AppSettingsModel = require('../models/app-settings-model'),
|
||||
UpdateModel = require('../models/update-model'),
|
||||
Transport = require('../comp/transport');
|
||||
Transport = require('../comp/transport'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('updater');
|
||||
|
||||
var Updater = {
|
||||
UpdateInterval: 1000*60*60*24,
|
||||
|
@ -57,7 +60,7 @@ 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('Next update check will happen in ' + Math.round(timeDiff / 1000) + 's');
|
||||
logger.info('Next update check will happen in ' + Math.round(timeDiff / 1000) + 's');
|
||||
return timeDiff === this.MinUpdateTimeout;
|
||||
},
|
||||
|
||||
|
@ -74,20 +77,20 @@ var Updater = {
|
|||
// additional protection from broken program logic, to ensure that auto-checks are not performed more than once an hour
|
||||
var diffMs = new Date() - this.updateCheckDate;
|
||||
if (isNaN(diffMs) || diffMs < 1000 * 60 * 60) {
|
||||
console.error('Prevented update check; last check was performed at ' + this.updateCheckDate);
|
||||
logger.error('Prevented update check; last check was performed at ' + this.updateCheckDate);
|
||||
that.scheduleNextCheck();
|
||||
return;
|
||||
}
|
||||
this.updateCheckDate = new Date();
|
||||
}
|
||||
console.log('Checking for update...');
|
||||
logger.info('Checking for update...');
|
||||
Transport.httpGet({
|
||||
url: Links.WebApp + 'manifest.appcache',
|
||||
utf8: true,
|
||||
success: function(data) {
|
||||
var dt = new Date();
|
||||
var match = data.match(/#\s*(\d+\-\d+\-\d+):v([\d+\.\w]+)/);
|
||||
console.log('Update check: ' + (match ? match[0] : 'unknown'));
|
||||
logger.info('Update check: ' + (match ? match[0] : 'unknown'));
|
||||
if (!match) {
|
||||
var errMsg = 'No version info found';
|
||||
UpdateModel.instance.set({ status: 'error', lastCheckDate: dt, lastCheckError: errMsg });
|
||||
|
@ -108,7 +111,7 @@ var Updater = {
|
|||
that.scheduleNextCheck();
|
||||
if (prevLastVersion === UpdateModel.instance.get('lastVersion') &&
|
||||
UpdateModel.instance.get('updateStatus') === 'ready') {
|
||||
console.log('Waiting for the user to apply downloaded update');
|
||||
logger.info('Waiting for the user to apply downloaded update');
|
||||
return;
|
||||
}
|
||||
if (!startedByUser && that.getAutoUpdateType() === 'install') {
|
||||
|
@ -118,7 +121,7 @@ var Updater = {
|
|||
}
|
||||
},
|
||||
error: function(e) {
|
||||
console.error('Update check error', e);
|
||||
logger.error('Update check error', e);
|
||||
UpdateModel.instance.set({
|
||||
status: 'error',
|
||||
lastCheckDate: new Date(),
|
||||
|
@ -140,22 +143,22 @@ var Updater = {
|
|||
update: function(startedByUser, successCallback) {
|
||||
var ver = UpdateModel.instance.get('lastVersion');
|
||||
if (!Launcher || ver === RuntimeInfo.version) {
|
||||
console.log('You are using the latest version');
|
||||
logger.info('You are using the latest version');
|
||||
return;
|
||||
}
|
||||
UpdateModel.instance.set({ updateStatus: 'downloading', updateError: null });
|
||||
var that = this;
|
||||
console.log('Downloading update', ver);
|
||||
logger.info('Downloading update', ver);
|
||||
Transport.httpGet({
|
||||
url: Links.UpdateDesktop.replace('{ver}', ver),
|
||||
file: 'KeeWeb-' + ver + '.zip',
|
||||
cache: !startedByUser,
|
||||
success: function(filePath) {
|
||||
UpdateModel.instance.set('updateStatus', 'extracting');
|
||||
console.log('Extracting update file', that.UpdateCheckFiles, filePath);
|
||||
logger.info('Extracting update file', that.UpdateCheckFiles, filePath);
|
||||
that.extractAppUpdate(filePath, function(err) {
|
||||
if (err) {
|
||||
console.error('Error extracting update', err);
|
||||
logger.error('Error extracting update', err);
|
||||
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error extracting update' });
|
||||
} else {
|
||||
UpdateModel.instance.set({ updateStatus: 'ready', updateError: null });
|
||||
|
@ -169,7 +172,7 @@ var Updater = {
|
|||
});
|
||||
},
|
||||
error: function(e) {
|
||||
console.error('Error downloading update', e);
|
||||
logger.error('Error downloading update', e);
|
||||
UpdateModel.instance.set({ updateStatus: 'error', updateError: 'Error downloading update' });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var Timeouts = {
|
||||
AutoSync: 15000
|
||||
AutoSync: 15 * 1000 * 60
|
||||
};
|
||||
|
||||
module.exports = Timeouts;
|
||||
|
|
|
@ -11,7 +11,8 @@ var Backbone = require('backbone'),
|
|||
FileModel = require('./file-model'),
|
||||
FileInfoModel = require('./file-info-model'),
|
||||
Storage = require('../storage'),
|
||||
IdGenerator = require('../util/id-generator');
|
||||
IdGenerator = require('../util/id-generator'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var AppModel = Backbone.Model.extend({
|
||||
defaults: {},
|
||||
|
@ -30,6 +31,8 @@ var AppModel = Backbone.Model.extend({
|
|||
this.listenTo(Backbone, 'add-filter', this.addFilter);
|
||||
this.listenTo(Backbone, 'set-sort', this.setSort);
|
||||
this.listenTo(Backbone, 'empty-trash', this.emptyTrash);
|
||||
|
||||
this.appLogger = new Logger('app');
|
||||
},
|
||||
|
||||
addFile: function(file) {
|
||||
|
@ -244,30 +247,40 @@ var AppModel = Backbone.Model.extend({
|
|||
},
|
||||
|
||||
openFile: function(params, callback) {
|
||||
var logger = new Logger('open', params.name);
|
||||
logger.info('File open request');
|
||||
var that = this;
|
||||
var fileInfo = params.id ? this.fileInfos.get(params.id) : this.fileInfos.getMatch(params.storage, params.name, params.path);
|
||||
if (fileInfo && fileInfo.get('modified')) {
|
||||
// modified offline, cannot overwrite: load from cache
|
||||
logger.info('Open file from cache because it is modified');
|
||||
this.openFileFromCache(params, callback, fileInfo);
|
||||
} else if (params.fileData) {
|
||||
// has user content: load it
|
||||
logger.info('Open file from supplied content');
|
||||
this.openFileWithData(params, callback, fileInfo, params.fileData, true);
|
||||
} else if (fileInfo && fileInfo.get('rev') === params.rev) {
|
||||
// already latest in cache: use it
|
||||
logger.info('Open file from cache because it is latest');
|
||||
this.openFileFromCache(params, callback, fileInfo);
|
||||
} else {
|
||||
// try to load from storage and update cache
|
||||
logger.info('Open file from storage', params.storage);
|
||||
var storage = Storage[params.storage];
|
||||
var storageLoad = function() {
|
||||
logger.info('Load from storage');
|
||||
storage.load(params.path, function(err, data, stat) {
|
||||
if (err) {
|
||||
// failed to load from storage: fallback to cache if we can
|
||||
if (fileInfo) {
|
||||
logger.info('Open file from cache because of storage load error', err);
|
||||
that.openFileFromCache(params, callback, fileInfo);
|
||||
} else {
|
||||
logger.info('Storage load error', err);
|
||||
callback(err);
|
||||
}
|
||||
} else {
|
||||
logger.info('Open file from content loaded from storage');
|
||||
params.fileData = data;
|
||||
params.rev = stat && stat.rev || null;
|
||||
that.openFileWithData(params, callback, fileInfo, data, true);
|
||||
|
@ -276,12 +289,16 @@ var AppModel = Backbone.Model.extend({
|
|||
};
|
||||
var cacheRev = fileInfo && fileInfo.get('rev') || null;
|
||||
if (cacheRev && storage.stat) {
|
||||
logger.info('Stat file');
|
||||
storage.stat(params.path, function(err, stat) {
|
||||
if (fileInfo && (err || stat && stat.rev === cacheRev)) {
|
||||
logger.info('Open file from cache because it is latest or stat error', err);
|
||||
that.openFileFromCache(params, callback, fileInfo);
|
||||
} else if (stat) {
|
||||
logger.info('Open file from storage');
|
||||
storageLoad();
|
||||
} else {
|
||||
logger.info('Stat error', err);
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
|
@ -294,6 +311,7 @@ var AppModel = Backbone.Model.extend({
|
|||
openFileFromCache: function(params, callback, fileInfo) {
|
||||
var that = this;
|
||||
Storage.cache.load(fileInfo.id, function(err, data) {
|
||||
new Logger('open', params.name).info('Loaded file from cache', params.name, err);
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
|
@ -303,6 +321,7 @@ var AppModel = Backbone.Model.extend({
|
|||
},
|
||||
|
||||
openFileWithData: function(params, callback, fileInfo, data, updateCacheOnSuccess) {
|
||||
var logger = new Logger('open', params.name);
|
||||
var file = new FileModel({
|
||||
name: params.name,
|
||||
storage: params.storage,
|
||||
|
@ -319,8 +338,10 @@ var AppModel = Backbone.Model.extend({
|
|||
}
|
||||
if (fileInfo && fileInfo.get('modified')) {
|
||||
if (fileInfo.get('editState')) {
|
||||
logger.info('Loaded local edit state');
|
||||
file.setLocalEditState(fileInfo.get('editState'));
|
||||
}
|
||||
logger.info('Mark file as modified and schedule sync');
|
||||
file.set('modified', true);
|
||||
setTimeout(that.syncFile.bind(that, file), 0);
|
||||
}
|
||||
|
@ -329,6 +350,7 @@ var AppModel = Backbone.Model.extend({
|
|||
}
|
||||
var cacheId = fileInfo && fileInfo.id || IdGenerator.uuid();
|
||||
if (updateCacheOnSuccess && params.storage !== 'file') {
|
||||
logger.info('Save loaded file to cache');
|
||||
Storage.cache.save(cacheId, params.fileData, function(err) {
|
||||
if (err && !params.storage) {
|
||||
return;
|
||||
|
@ -344,6 +366,7 @@ var AppModel = Backbone.Model.extend({
|
|||
},
|
||||
|
||||
addToLastOpenFiles: function(file, id, rev) {
|
||||
this.appLogger.debug('Add last open file', id, file.get('name'), file.get('storage'), file.get('path'));
|
||||
var dt = new Date();
|
||||
var fileInfo = new FileInfoModel({
|
||||
id: id,
|
||||
|
@ -378,8 +401,13 @@ var AppModel = Backbone.Model.extend({
|
|||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
var logger = new Logger('sync', file.get('name'));
|
||||
var storage = options.storage || file.get('storage');
|
||||
var path = options.path || file.get('path');
|
||||
logger.info('Sync started', storage, path, options);
|
||||
var fileInfo = this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path'));
|
||||
if (!fileInfo) {
|
||||
logger.info('Create new file info');
|
||||
var dt = new Date();
|
||||
fileInfo = new FileInfoModel({
|
||||
id: IdGenerator.uuid(),
|
||||
|
@ -393,11 +421,10 @@ var AppModel = Backbone.Model.extend({
|
|||
openDate: dt
|
||||
});
|
||||
}
|
||||
var storage = options.storage || file.get('storage');
|
||||
var path = options.path || file.get('path');
|
||||
file.setSyncProgress();
|
||||
var complete = function(err, savedToCache) {
|
||||
if (!err) { savedToCache = true; }
|
||||
logger.info('Sync finished', savedToCache ? 'saved to cache' : '', err);
|
||||
file.setSyncComplete(path, storage, err ? err.toString() : null, savedToCache);
|
||||
fileInfo.set({
|
||||
storage: storage,
|
||||
|
@ -414,103 +441,132 @@ var AppModel = Backbone.Model.extend({
|
|||
};
|
||||
if (!storage) {
|
||||
if (!file.get('modified')) {
|
||||
logger.info('Local, not modified');
|
||||
return complete();
|
||||
}
|
||||
logger.info('Local, save to cache');
|
||||
file.getData(function(data, err) {
|
||||
if (err) { return complete(err); }
|
||||
Storage.cache.save(fileInfo.id, data, function(err) {
|
||||
logger.info('Saved to cache', err);
|
||||
complete(err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
var maxLoadLoops = 3, loadLoops = 0;
|
||||
var loadFromStorageAndMerge = function() {
|
||||
logger.info('Load from storage, attempt ' + loadLoops);
|
||||
if (++loadLoops === maxLoadLoops) {
|
||||
return complete('Too many load attempts, please try again later');
|
||||
}
|
||||
Storage[storage].load(path, function(err, data, stat) {
|
||||
logger.info('Load from storage', stat, err);
|
||||
if (err) { return complete(err); }
|
||||
file.mergeOrUpdate(data, function(err) {
|
||||
logger.info('Merge complete', err);
|
||||
if (err) { return complete(err); }
|
||||
if (stat && stat.rev) {
|
||||
logger.info('Update rev');
|
||||
fileInfo.set('rev', stat.rev);
|
||||
}
|
||||
file.set('syncDate', new Date());
|
||||
if (file.get('modified')) {
|
||||
logger.info('Updated sync date, saving modified file to cache and storage');
|
||||
saveToCacheAndStorage();
|
||||
} else if (file.get('dirty') && storage !== 'file') {
|
||||
logger.info('Saving not modified dirty file to cache');
|
||||
Storage.cache.save(fileInfo.id, data, function (err) {
|
||||
if (err) { return complete(err); }
|
||||
file.set('dirty', false);
|
||||
logger.info('Complete, remove dirty flag');
|
||||
complete();
|
||||
});
|
||||
} else {
|
||||
logger.info('Complete, no changes');
|
||||
complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
var saveToCacheAndStorage = function() {
|
||||
logger.info('Save to cache and storage');
|
||||
file.getData(function(data, err) {
|
||||
if (err) { return complete(err); }
|
||||
if (!file.get('dirty') || storage === 'file') {
|
||||
logger.info('Save to storage, skip cache because not dirty or file storage');
|
||||
saveToStorage(data);
|
||||
} else {
|
||||
logger.info('Saving to cache');
|
||||
Storage.cache.save(fileInfo.id, data, function (err) {
|
||||
if (err) { return complete(err); }
|
||||
file.set('dirty', false);
|
||||
logger.info('Saved to cache, saving to storage');
|
||||
saveToStorage(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
var saveToStorage = function(data) {
|
||||
logger.info('Save data to storage');
|
||||
Storage[storage].save(path, data, function(err) {
|
||||
if (err && err.revConflict) {
|
||||
logger.info('Save rev conflict, reloading from storage');
|
||||
loadFromStorageAndMerge();
|
||||
} else if (err) {
|
||||
logger.info('Error saving data to storage');
|
||||
complete(err);
|
||||
} else {
|
||||
if (storage === 'file') {
|
||||
Storage.cache.remove(fileInfo.id);
|
||||
}
|
||||
file.set('syncDate', new Date());
|
||||
logger.info('Save to storage complete, update sync date');
|
||||
complete();
|
||||
}
|
||||
}, fileInfo.get('rev'));
|
||||
};
|
||||
if (options.reload) {
|
||||
logger.info('Saved to cache');
|
||||
loadFromStorageAndMerge();
|
||||
} else if (storage === 'file') {
|
||||
if (file.get('modified')) {
|
||||
logger.info('Save modified file to storage');
|
||||
saveToCacheAndStorage();
|
||||
} else {
|
||||
logger.info('Skip not modified file');
|
||||
complete();
|
||||
}
|
||||
} else {
|
||||
logger.info('Stat file');
|
||||
Storage[storage].stat(path, function (err, stat) {
|
||||
if (err) {
|
||||
if (file.get('dirty')) {
|
||||
logger.info('Stat error, dirty, save to cache', err);
|
||||
file.getData(function (data) {
|
||||
if (data) {
|
||||
Storage.cache.save(fileInfo.id, data, function (e) {
|
||||
if (!e) {
|
||||
file.set('dirty', false);
|
||||
}
|
||||
logger.info('Saved to cache, exit with error', err);
|
||||
complete(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.info('Stat error, not dirty', err);
|
||||
complete(err);
|
||||
}
|
||||
} else if (stat.rev === fileInfo.get('rev')) {
|
||||
if (file.get('modified')) {
|
||||
logger.info('Stat found same version, modified, saving to cache and storage');
|
||||
saveToCacheAndStorage();
|
||||
} else {
|
||||
logger.info('Stat found same version, not modified');
|
||||
complete();
|
||||
}
|
||||
} else {
|
||||
logger.info('Found new version, loading from storage');
|
||||
loadFromStorageAndMerge();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,9 +4,12 @@ var Backbone = require('backbone'),
|
|||
GroupCollection = require('../collections/group-collection'),
|
||||
GroupModel = require('./group-model'),
|
||||
IconUrl = require('../util/icon-url'),
|
||||
Logger = require('../util/logger'),
|
||||
kdbxweb = require('kdbxweb'),
|
||||
demoFileData = require('base64!../../resources/Demo.kdbx');
|
||||
|
||||
var logger = new Logger('file');
|
||||
|
||||
var FileModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
id: '',
|
||||
|
@ -56,10 +59,10 @@ var FileModel = Backbone.Model.extend({
|
|||
password = new kdbxweb.ProtectedValue(value.buffer.slice(0, byteLength), salt.buffer.slice(0, byteLength));
|
||||
try {
|
||||
var credentials = new kdbxweb.Credentials(password, keyFileData);
|
||||
var start = performance.now();
|
||||
var ts = logger.ts();
|
||||
kdbxweb.Kdbx.load(fileData, credentials, (function(db, err) {
|
||||
if (err) {
|
||||
console.error('Error opening file', err.code, err.message, err);
|
||||
logger.error('Error opening file', err.code, err.message, err);
|
||||
callback(err);
|
||||
} else {
|
||||
this.db = db;
|
||||
|
@ -68,13 +71,13 @@ var FileModel = Backbone.Model.extend({
|
|||
if (keyFileData) {
|
||||
kdbxweb.ByteUtils.zeroBuffer(keyFileData);
|
||||
}
|
||||
console.log('Opened file ' + this.get('name') + ': ' + Math.round(performance.now() - start) + 'ms, ' +
|
||||
logger.info('Opened file ' + this.get('name') + ': ' + logger.ts(ts) + ', ' +
|
||||
db.header.keyEncryptionRounds + ' rounds, ' + Math.round(fileData.byteLength / 1024) + ' kB');
|
||||
callback();
|
||||
}
|
||||
}).bind(this));
|
||||
} catch (e) {
|
||||
console.error('Error opening file', e, e.code, e.message, e);
|
||||
logger.error('Error opening file', e, e.code, e.message, e);
|
||||
callback(e);
|
||||
}
|
||||
},
|
||||
|
@ -161,14 +164,14 @@ var FileModel = Backbone.Model.extend({
|
|||
mergeOrUpdate: function(fileData, callback) {
|
||||
kdbxweb.Kdbx.load(fileData, this.db.credentials, (function(remoteDb, err) {
|
||||
if (err) {
|
||||
console.error('Error opening file to merge', err.code, err.message, err);
|
||||
logger.error('Error opening file to merge', err.code, err.message, err);
|
||||
} else {
|
||||
if (this.get('modified')) {
|
||||
try {
|
||||
this.db.merge(remoteDb);
|
||||
this.set('dirty', true);
|
||||
} catch (e) {
|
||||
console.error('File merge error', e);
|
||||
logger.error('File merge error', e);
|
||||
return callback(e);
|
||||
}
|
||||
} else {
|
||||
|
@ -256,7 +259,7 @@ var FileModel = Backbone.Model.extend({
|
|||
var that = this;
|
||||
this.db.save(function(data, err) {
|
||||
if (err) {
|
||||
console.error('Error saving file', that.get('name'), err);
|
||||
logger.error('Error saving file', that.get('name'), err);
|
||||
}
|
||||
cb(data, err);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
var Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('storage-cache');
|
||||
var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
|
||||
var StorageCache = {
|
||||
|
@ -17,7 +20,7 @@ var StorageCache = {
|
|||
try {
|
||||
var req = idb.open('FilesCache');
|
||||
req.onerror = function (e) {
|
||||
console.error('Error opening indexed db', e);
|
||||
logger.error('Error opening indexed db', e);
|
||||
that.errorOpening = e;
|
||||
if (callback) { callback(e); }
|
||||
};
|
||||
|
@ -30,69 +33,78 @@ var StorageCache = {
|
|||
db.createObjectStore('files');
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error opening indexed db', e);
|
||||
logger.error('Error opening indexed db', e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
},
|
||||
|
||||
save: function(id, data, callback) {
|
||||
logger.debug('Save', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
try {
|
||||
var ts = logger.ts();
|
||||
var req = this.db.transaction(['files'], 'readwrite').objectStore('files').put(data, id);
|
||||
req.onsuccess = function () {
|
||||
logger.debug('Saved', id, logger.ts(ts));
|
||||
if (callback) { callback(); }
|
||||
};
|
||||
req.onerror = function () {
|
||||
console.error('Error saving to cache', id, req.error);
|
||||
logger.error('Error saving to cache', id, req.error);
|
||||
if (callback) { callback(req.error); }
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error saving to cache', id, e);
|
||||
logger.error('Error saving to cache', id, e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
load: function(id, callback) {
|
||||
logger.debug('Load', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(err, null);
|
||||
}
|
||||
try {
|
||||
var ts = logger.ts();
|
||||
var req = this.db.transaction(['files'], 'readonly').objectStore('files').get(id);
|
||||
req.onsuccess = function () {
|
||||
logger.debug('Loaded', id, logger.ts(ts));
|
||||
if (callback) { callback(null, req.result); }
|
||||
};
|
||||
req.onerror = function () {
|
||||
console.error('Error loading from cache', id, req.error);
|
||||
logger.error('Error loading from cache', id, req.error);
|
||||
if (callback) { callback(req.error); }
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error loading from cache', id, e);
|
||||
logger.error('Error loading from cache', id, e);
|
||||
if (callback) { callback(e, null); }
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
remove: function(id, callback) {
|
||||
logger.debug('Remove', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
try {
|
||||
var ts = logger.ts();
|
||||
var req = this.db.transaction(['files'], 'readwrite').objectStore('files').delete(id);
|
||||
req.onsuccess = function () {
|
||||
logger.debug('Removed', id, logger.ts(ts));
|
||||
if (callback) { callback(); }
|
||||
};
|
||||
req.onerror = function () {
|
||||
console.error('Error removing from cache', id, req.error);
|
||||
logger.error('Error removing from cache', id, req.error);
|
||||
if (callback) { callback(req.error); }
|
||||
};
|
||||
} catch(e) {
|
||||
console.error('Error removing from cache', id, e);
|
||||
logger.error('Error removing from cache', id, e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
}).bind(this));
|
||||
|
|
|
@ -1,31 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
var DropboxLink = require('../comp/dropbox-link');
|
||||
var DropboxLink = require('../comp/dropbox-link'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('storage-dropbox');
|
||||
|
||||
var StorageDropbox = {
|
||||
name: 'dropbox',
|
||||
enabled: true,
|
||||
|
||||
load: function(path, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.openFile(path, function(err, data, stat) {
|
||||
logger.debug('Loaded', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
if (callback) { callback(err, data, stat ? { rev: stat.versionTag } : null); }
|
||||
});
|
||||
}, _.noop);
|
||||
},
|
||||
|
||||
stat: function(path, callback) {
|
||||
logger.debug('Stat', path);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.stat(path, function(err, stat) {
|
||||
logger.debug('Stated', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
if (callback) { callback(err, stat ? { rev: stat.versionTag } : null); }
|
||||
});
|
||||
}, _.noop);
|
||||
},
|
||||
|
||||
save: function(path, data, callback, rev) {
|
||||
logger.debug('Save', path, rev);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.saveFile(path, data, rev, function(err) {
|
||||
logger.debug('Saved', path, logger.ts(ts));
|
||||
if (!callback) { return; }
|
||||
if (err && err.status === DropboxLink.ERROR_CONFLICT) {
|
||||
err = { revConflict: true };
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
}, _.noop);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('../comp/launcher');
|
||||
var Launcher = require('../comp/launcher'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('storage-file-cache');
|
||||
|
||||
var StorageFileCache = {
|
||||
name: 'cache',
|
||||
|
@ -24,54 +27,63 @@ var StorageFileCache = {
|
|||
}
|
||||
this.path = path;
|
||||
} catch (e) {
|
||||
console.error('Error opening local offline storage', e);
|
||||
logger.error('Error opening local offline storage', e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
},
|
||||
|
||||
save: function(id, data, callback) {
|
||||
logger.debug('Save', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
Launcher.writeFile(this.getPath(id), data);
|
||||
logger.debug('Saved', id, logger.ts(ts));
|
||||
if (callback) { callback(); }
|
||||
} catch (e) {
|
||||
console.error('Error saving to cache', id, e);
|
||||
logger.error('Error saving to cache', id, e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
load: function(id, callback) {
|
||||
logger.debug('Load', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(null, err);
|
||||
}
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
var data = Launcher.readFile(this.getPath(id));
|
||||
logger.debug('Loaded', id, logger.ts(ts));
|
||||
if (callback) { callback(null, data.buffer); }
|
||||
} catch (e) {
|
||||
console.error('Error loading from cache', id, e);
|
||||
logger.error('Error loading from cache', id, e);
|
||||
if (callback) { callback(e, null); }
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
remove: function(id, callback) {
|
||||
logger.debug('Remove', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
return callback && callback(err);
|
||||
}
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
var path = this.getPath(id);
|
||||
if (Launcher.fileExists(path)) {
|
||||
Launcher.deleteFile(path);
|
||||
}
|
||||
logger.debug('Removed', id, logger.ts(ts));
|
||||
if (callback) { callback(); }
|
||||
} catch(e) {
|
||||
console.error('Error removing from cache', id, e);
|
||||
logger.error('Error removing from cache', id, e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
}).bind(this));
|
||||
|
|
|
@ -1,27 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('../comp/launcher');
|
||||
var Launcher = require('../comp/launcher'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('storage-file');
|
||||
|
||||
var StorageFile = {
|
||||
name: 'file',
|
||||
enabled: !!Launcher,
|
||||
|
||||
load: function(path, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
var data = Launcher.readFile(path);
|
||||
logger.debug('Loaded', path, logger.ts(ts));
|
||||
if (callback) { callback(null, data.buffer); }
|
||||
} catch (e) {
|
||||
console.error('Error reading local file', path, e);
|
||||
logger.error('Error reading local file', path, e);
|
||||
if (callback) { callback(e, null); }
|
||||
}
|
||||
},
|
||||
|
||||
save: function(path, data, callback) {
|
||||
logger.debug('Save', path);
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
Launcher.writeFile(path, data);
|
||||
logger.debug('Saved', path, logger.ts(ts));
|
||||
if (callback) { callback(); }
|
||||
} catch (e) {
|
||||
console.error('Error writing local file', path, e);
|
||||
logger.error('Error writing local file', path, e);
|
||||
if (callback) { callback(e); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
'use strict';
|
||||
|
||||
/* globals console */
|
||||
/* globals performance */
|
||||
|
||||
var Logger = function(name, id) {
|
||||
this.prefix = (name ? name + (id ? ':' + id : '') : 'default');
|
||||
};
|
||||
|
||||
Logger.prototype.ts = function(ts) {
|
||||
if (ts) {
|
||||
return Math.round(performance.now() - ts) + 'ms';
|
||||
} else {
|
||||
return performance.now();
|
||||
}
|
||||
};
|
||||
|
||||
Logger.prototype.getPrefix = function() {
|
||||
return new Date().toISOString() + ' [' + this.prefix + '] ';
|
||||
};
|
||||
|
||||
Logger.prototype.debug = function() {
|
||||
arguments[0] = this.getPrefix() + arguments[0];
|
||||
console.debug.apply(console, arguments);
|
||||
};
|
||||
|
||||
Logger.prototype.info = function() {
|
||||
arguments[0] = this.getPrefix() + arguments[0];
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
|
||||
Logger.prototype.warn = function() {
|
||||
arguments[0] = this.getPrefix() + arguments[0];
|
||||
console.warn.apply(console, arguments);
|
||||
};
|
||||
|
||||
Logger.prototype.error = function() {
|
||||
arguments[0] = this.getPrefix() + arguments[0];
|
||||
console.error.apply(console, arguments);
|
||||
};
|
||||
|
||||
module.exports = Logger;
|
|
@ -218,7 +218,7 @@ var AppView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
beforeUnload: function(e) {
|
||||
if (this.model.files.hasUnsavedFiles()) {
|
||||
if (this.model.files.hasDirtyFiles()) {
|
||||
if (Launcher && !Launcher.exitRequested) {
|
||||
if (!this.exitAlertShown) {
|
||||
var that = this;
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
var Backbone = require('backbone'),
|
||||
IconMap = require('../const/icon-map'),
|
||||
Launcher = require('../comp/launcher');
|
||||
Launcher = require('../comp/launcher'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('icon-select-view');
|
||||
|
||||
var IconSelectView = Backbone.View.extend({
|
||||
template: require('templates/icon-select.html'),
|
||||
|
@ -67,7 +70,7 @@ var IconSelectView = Backbone.View.extend({
|
|||
that.downloadingFavicon = false;
|
||||
};
|
||||
img.onerror = function (e) {
|
||||
console.error('Favicon download error: ' + url, e);
|
||||
logger.error('Favicon download error: ' + url, e);
|
||||
that.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin');
|
||||
that.$el.find('.icon-select__icon-download').removeClass('icon-select__icon--custom-selected');
|
||||
that.downloadingFavicon = false;
|
||||
|
|
|
@ -4,7 +4,10 @@ var Backbone = require('backbone'),
|
|||
Keys = require('../const/keys'),
|
||||
Alerts = require('../comp/alerts'),
|
||||
SecureInput = require('../comp/secure-input'),
|
||||
DropboxLink = require('../comp/dropbox-link');
|
||||
DropboxLink = require('../comp/dropbox-link'),
|
||||
Logger = require('../util/logger');
|
||||
|
||||
var logger = new Logger('open-view');
|
||||
|
||||
var OpenView = Backbone.View.extend({
|
||||
template: require('templates/open.html'),
|
||||
|
@ -383,7 +386,7 @@ var OpenView = Backbone.View.extend({
|
|||
this.$el.toggleClass('open--opening', false);
|
||||
this.inputEl.removeAttr('disabled').toggleClass('input--error', !!err);
|
||||
if (err) {
|
||||
console.error('Error opening file', err);
|
||||
logger.error('Error opening file', err);
|
||||
this.inputEl.focus();
|
||||
this.inputEl[0].selectionStart = 0;
|
||||
this.inputEl[0].selectionEnd = this.inputEl.val().length;
|
||||
|
|
|
@ -8,6 +8,7 @@ var Backbone = require('backbone'),
|
|||
Storage = require('../../storage'),
|
||||
Links = require('../../const/links'),
|
||||
DropboxLink = require('../../comp/dropbox-link'),
|
||||
Format = require('../../util/format'),
|
||||
kdbxweb = require('kdbxweb'),
|
||||
FileSaver = require('filesaver');
|
||||
|
||||
|
@ -48,6 +49,8 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
path: this.model.get('path'),
|
||||
storage: this.model.get('storage'),
|
||||
syncing: this.model.get('syncing'),
|
||||
syncError: this.model.get('syncError'),
|
||||
syncDate: Format.dtStr(this.model.get('syncDate')),
|
||||
password: PasswordGenerator.present(this.model.get('passwordLength')),
|
||||
defaultUser: this.model.get('defaultUser'),
|
||||
recycleBinEnabled: this.model.get('recycleBinEnabled'),
|
||||
|
@ -120,9 +123,7 @@ var SettingsAboutView = Backbone.View.extend({
|
|||
}
|
||||
}
|
||||
this.appModel.syncFile(this.model, arg, function(err) {
|
||||
if (err) {
|
||||
console.error('Error saving file', err);
|
||||
} else {
|
||||
if (!err) {
|
||||
that.passwordChanged = false;
|
||||
}
|
||||
that.render();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="footer__db footer__db-item <%= file.get('open') ? '' : 'footer__db--dimmed' %>" data-file-id="<%= file.cid %>">
|
||||
<i class="fa fa-<%= file.get('open') ? 'unlock' : 'lock' %>"></i> <%- file.get('name') %>
|
||||
<% if (file.get('syncing')) { %><i class="fa fa-refresh fa-spin footer__db-sign"></i><% }
|
||||
else if (file.get('syncError')) { %><i class="fa fa-circle footer__db-sign footer__db-sign--error"
|
||||
else if (file.get('syncError')) { %><i class="fa <%= file.get('modified') ? 'fa-circle' : 'fa-circle-thin' %> footer__db-sign footer__db-sign--error"
|
||||
title="Sync error: <%- file.get('syncError') %>"></i><% }
|
||||
else if (file.get('modified')) { %><i class="fa fa-circle footer__db-sign"></i><% } %>
|
||||
</div>
|
||||
|
|
|
@ -16,12 +16,18 @@
|
|||
<div class="settings__file-buttons">
|
||||
<% if (!storage || storage === 'file') { %><button class="settings__file-button-save-default">Save</button><% } %>
|
||||
<button class="settings__file-button-save-dropbox <%= storage !== 'dropbox' ? 'btn-silent' : '' %>"
|
||||
<%= syncing ? 'disabled' : '' %>>Sync with Dropbox <%= syncing ? '(working...)' : '' %></button>
|
||||
<%= syncing ? 'disabled' : '' %>>Sync with Dropbox</button>
|
||||
<% if (storage !== 'file') { %><button class="settings__file-button-save-file btn-silent">Save to file</button><% } %>
|
||||
<button class="settings__file-button-export-xml btn-silent">Export to XML</button>
|
||||
<button class="settings__file-button-close btn-silent">Close</button>
|
||||
</div>
|
||||
|
||||
<% if (storage) { %>
|
||||
<h2>Sync</h2>
|
||||
<div>Last sync: <%= syncDate || 'unknown' %> <%= syncing ? '(sync in progress...)' : '' %></div>
|
||||
<% if (syncError) { %><div>Sync error: <%- syncError %></div><% } %>
|
||||
<% } %>
|
||||
|
||||
<h2>Settings</h2>
|
||||
<label for="settings__file-master-pass" class="settings__file-master-pass-label input-base">Master password:
|
||||
<span class="settings__file-master-pass-warning">
|
||||
|
|
Loading…
Reference in New Issue