logging sync

This commit is contained in:
Antelle 2015-12-12 11:53:50 +03:00
parent f59cdeb10c
commit 3dab9af066
20 changed files with 263 additions and 80 deletions

View File

@ -88,8 +88,6 @@
"globals" : {
"require": true,
"module": true,
"console": true,
"performance": true,
"$": true,
"_": true
}

View File

@ -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; });
},

View File

@ -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) {

View File

@ -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);
}
}
};

View File

@ -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);
}

View File

@ -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' });
}
});

View File

@ -1,7 +1,7 @@
'use strict';
var Timeouts = {
AutoSync: 15000
AutoSync: 15 * 1000 * 60
};
module.exports = Timeouts;

View File

@ -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();
}
});

View File

@ -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);
});

View File

@ -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));

View File

@ -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);
}
};

View File

@ -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));

View File

@ -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); }
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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>

View File

@ -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">