diff --git a/app/scripts/comp/dropbox-link.js b/app/scripts/comp/dropbox-link.js index 6b514810..d26d8698 100644 --- a/app/scripts/comp/dropbox-link.js +++ b/app/scripts/comp/dropbox-link.js @@ -239,9 +239,10 @@ var DropboxLink = { Dropbox.AuthDriver.Popup.oauthReceiver(); }, - saveFile: function(fileName, data, overwrite, complete) { - if (overwrite) { - this._callAndHandleError('writeFile', [fileName, data], complete); + saveFile: function(fileName, data, rev, complete) { + if (rev) { + var opts = typeof rev === 'string' ? { lastVersionTag: rev } : undefined; + this._callAndHandleError('writeFile', [fileName, data, opts], complete); } else { this.getFileList((function(err, files) { if (err) { return complete(err); } diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index 2f55dadd..bdbc0fb4 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -318,7 +318,7 @@ var AppModel = Backbone.Model.extend({ return callback('Duplicate file id'); } var cacheId = fileInfo && fileInfo.id || IdGenerator.uuid(); - if (updateCacheOnSuccess) { + if (updateCacheOnSuccess && params.storage !== 'file') { Storage.cache.save(cacheId, params.fileData, function(err) { if (err && !params.storage) { return; @@ -361,6 +361,89 @@ var AppModel = Backbone.Model.extend({ if (file.get('syncing')) { return callback('Sync in progress'); } + // todo: save to cache + var fileInfo = this.fileInfos.getMatch(file.get('storage'), file.get('name'), file.get('path')); + if (!fileInfo) { + var dt = new Date(); + fileInfo = new FileInfoModel({ + id: IdGenerator.uuid(), + name: file.get('name'), + storage: file.get('storage'), + path: file.get('path'), + modified: file.get('modified'), + editState: null, + rev: null, + pullDate: dt, + openDate: dt + }); + } + var storage = Storage[options.storage || file.get('storage')]; + if (!storage) { + if (!file.get('modified')) { + return callback(); + } + file.getData(function(data, err) { + if (err) { return callback(err); } + Storage.cache.save(fileInfo.id, data, function(err) { + callback(err); + }); + }); + } else { + var maxLoadLoops = 3, loadLoops = 0; + var loadFromStorageAndMerge = function() { + if (++loadLoops === maxLoadLoops) { + return callback('Too many load attempts, please try again later'); + } + storage.load(file.get('path'), function(err, data, stat) { + if (err) { return callback(err); } + file.merge(data, function(err) { + if (err) { return callback(err); } + if (stat && stat.rev) { + fileInfo.set('rev', stat.rev); + } + if (file.get('modified')) { + saveToStorage(); + } else { + callback(); + } + }); + }); + }; + var saveToStorage = function() { + file.getData(function(data, err) { + if (err) { return callback(err); } + storage.save(file.get('path'), data, function(err) { + if (err && err.revConflict) { + loadFromStorageAndMerge(); + } else if (err) { + callback(err); + } else { + if (storage === Storage.file) { + Storage.cache.remove(fileInfo.id); + } + callback(); + } + }, fileInfo.get('rev')); + }); + }; + if (options.reload) { + loadFromStorageAndMerge(); + } else if (storage === Storage.file) { + if (file.get('modified')) { + saveToStorage(); + } else { + callback(); + } + } else { + storage.stat(file.get('path'), function (err, stat) { + if (stat.rev === fileInfo.get('rev')) { + saveToStorage(); + } else { + loadFromStorageAndMerge(); + } + }); + } + } } }); diff --git a/app/scripts/models/file-model.js b/app/scripts/models/file-model.js index 0555ee90..5471c760 100644 --- a/app/scripts/models/file-model.js +++ b/app/scripts/models/file-model.js @@ -3,7 +3,6 @@ var Backbone = require('backbone'), GroupCollection = require('../collections/group-collection'), GroupModel = require('./group-model'), - Storage = require('../storage'), IconUrl = require('../util/icon-url'), kdbxweb = require('kdbxweb'), demoFileData = require('base64!../../resources/Demo.kdbx'); @@ -29,7 +28,6 @@ var FileModel = Backbone.Model.extend({ }, db: null, - data: null, entryMap: null, groupMap: null, @@ -157,6 +155,18 @@ var FileModel = Backbone.Model.extend({ this.trigger('reload', this); }, + merge: 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); + } else { + this.db.merge(remoteDb); + this.reload(); + } + callback(err); + }).bind(this)); + }, + close: function() { this.set({ keyFileName: '', @@ -221,8 +231,13 @@ var FileModel = Backbone.Model.extend({ historyRules: true, customIcons: true }); - this.data = this.db.save(cb); - return this.data; + var that = this; + this.db.save(function(data, err) { + if (err) { + console.error('Error saving file', that.get('name'), err); + } + cb(data, err); + }); }, getXml: function(cb) { diff --git a/app/scripts/storage/storage-dropbox.js b/app/scripts/storage/storage-dropbox.js index a79d3d72..5ad01695 100644 --- a/app/scripts/storage/storage-dropbox.js +++ b/app/scripts/storage/storage-dropbox.js @@ -18,8 +18,8 @@ var StorageDropbox = { }); }, - save: function(path, data, callback) { - DropboxLink.saveFile(path, data, true, callback || _.noop); + save: function(path, data, callback, rev) { + DropboxLink.saveFile(path, data, rev, callback || _.noop); } };