mirror of https://github.com/keeweb/keeweb.git
WebDAV
This commit is contained in:
parent
9725f8bb61
commit
3cdc6b2cb9
|
@ -301,7 +301,7 @@ var AppModel = Backbone.Model.extend({
|
|||
var storage = Storage[params.storage];
|
||||
var storageLoad = function() {
|
||||
logger.info('Load from storage');
|
||||
storage.load(params.path, function(err, data, stat) {
|
||||
storage.load(params.path, null, function(err, data, stat) {
|
||||
if (err) {
|
||||
if (fileInfo) {
|
||||
logger.info('Open file from cache because of storage load error', err);
|
||||
|
@ -321,7 +321,7 @@ 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) {
|
||||
storage.stat(params.path, null, function(err, stat) {
|
||||
if (fileInfo && (err || stat && stat.rev === cacheRev)) {
|
||||
logger.info('Open file from cache because ' + (err ? 'stat error' : 'it is latest'), err);
|
||||
that.openFileFromCache(params, callback, fileInfo);
|
||||
|
@ -341,7 +341,7 @@ var AppModel = Backbone.Model.extend({
|
|||
|
||||
openFileFromCache: function(params, callback, fileInfo) {
|
||||
var that = this;
|
||||
Storage.cache.load(fileInfo.id, function(err, data) {
|
||||
Storage.cache.load(fileInfo.id, null, function(err, data) {
|
||||
new Logger('open', params.name).info('Loaded file from cache', err);
|
||||
if (err) {
|
||||
callback(err);
|
||||
|
@ -387,7 +387,7 @@ var AppModel = Backbone.Model.extend({
|
|||
file.set('cacheId', cacheId);
|
||||
if (updateCacheOnSuccess) {
|
||||
logger.info('Save loaded file to cache');
|
||||
Storage.cache.save(cacheId, params.fileData);
|
||||
Storage.cache.save(cacheId, null, params.fileData);
|
||||
}
|
||||
var rev = params.rev || fileInfo && fileInfo.get('rev');
|
||||
that.addToLastOpenFiles(file, rev);
|
||||
|
@ -535,7 +535,7 @@ var AppModel = Backbone.Model.extend({
|
|||
logger.info('Local, save to cache');
|
||||
file.getData(function(data, err) {
|
||||
if (err) { return complete(err); }
|
||||
Storage.cache.save(fileInfo.id, data, function(err) {
|
||||
Storage.cache.save(fileInfo.id, null, data, function(err) {
|
||||
logger.info('Saved to cache', err || 'no error');
|
||||
complete(err);
|
||||
});
|
||||
|
@ -547,7 +547,7 @@ var AppModel = Backbone.Model.extend({
|
|||
return complete('Too many load attempts');
|
||||
}
|
||||
logger.info('Load from storage, attempt ' + loadLoops);
|
||||
Storage[storage].load(path, function(err, data, stat) {
|
||||
Storage[storage].load(path, null, function(err, data, stat) {
|
||||
logger.info('Load from storage', stat, err || 'no error');
|
||||
if (err) { return complete(err); }
|
||||
file.mergeOrUpdate(data, options.remoteKey, function(err) {
|
||||
|
@ -570,7 +570,7 @@ var AppModel = Backbone.Model.extend({
|
|||
saveToCacheAndStorage();
|
||||
} else if (file.get('dirty')) {
|
||||
logger.info('Saving not modified dirty file to cache');
|
||||
Storage.cache.save(fileInfo.id, data, function (err) {
|
||||
Storage.cache.save(fileInfo.id, null, data, function (err) {
|
||||
if (err) { return complete(err); }
|
||||
file.set('dirty', false);
|
||||
logger.info('Complete, remove dirty flag');
|
||||
|
@ -592,7 +592,7 @@ var AppModel = Backbone.Model.extend({
|
|||
saveToStorage(data);
|
||||
} else {
|
||||
logger.info('Saving to cache');
|
||||
Storage.cache.save(fileInfo.id, data, function (err) {
|
||||
Storage.cache.save(fileInfo.id, null, data, function (err) {
|
||||
if (err) { return complete(err); }
|
||||
file.set('dirty', false);
|
||||
logger.info('Saved to cache, saving to storage');
|
||||
|
@ -603,7 +603,7 @@ var AppModel = Backbone.Model.extend({
|
|||
};
|
||||
var saveToStorage = function(data) {
|
||||
logger.info('Save data to storage');
|
||||
Storage[storage].save(path, data, function(err, stat) {
|
||||
Storage[storage].save(path, null, data, function(err, stat) {
|
||||
if (err && err.revConflict) {
|
||||
logger.info('Save rev conflict, reloading from storage');
|
||||
loadFromStorageAndMerge();
|
||||
|
@ -622,7 +622,7 @@ var AppModel = Backbone.Model.extend({
|
|||
}, fileInfo.get('rev'));
|
||||
};
|
||||
logger.info('Stat file');
|
||||
Storage[storage].stat(path, function (err, stat) {
|
||||
Storage[storage].stat(path, null, function (err, stat) {
|
||||
if (err) {
|
||||
if (err.notFound) {
|
||||
logger.info('File does not exist in storage, creating');
|
||||
|
@ -631,7 +631,7 @@ var AppModel = Backbone.Model.extend({
|
|||
logger.info('Stat error, dirty, save to cache', err || 'no error');
|
||||
file.getData(function (data) {
|
||||
if (data) {
|
||||
Storage.cache.save(fileInfo.id, data, function (e) {
|
||||
Storage.cache.save(fileInfo.id, null, data, function (e) {
|
||||
if (!e) {
|
||||
file.set('dirty', false);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ var StorageCache = {
|
|||
}
|
||||
},
|
||||
|
||||
save: function(id, data, callback) {
|
||||
save: function(id, opts, data, callback) {
|
||||
logger.debug('Save', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
@ -62,7 +62,7 @@ var StorageCache = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
load: function(id, callback) {
|
||||
load: function(id, opts, callback) {
|
||||
logger.debug('Load', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
@ -86,7 +86,7 @@ var StorageCache = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
remove: function(id, callback) {
|
||||
remove: function(id, opts, callback) {
|
||||
logger.debug('Remove', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
|
|
@ -27,7 +27,7 @@ var StorageDropbox = {
|
|||
return '/' + fileName + '.kdbx';
|
||||
},
|
||||
|
||||
load: function(path, callback) {
|
||||
load: function(path, opts, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.openFile(path, function(err, data, stat) {
|
||||
|
@ -37,7 +37,7 @@ var StorageDropbox = {
|
|||
}, _.noop);
|
||||
},
|
||||
|
||||
stat: function(path, callback) {
|
||||
stat: function(path, opts, callback) {
|
||||
logger.debug('Stat', path);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.stat(path, function(err, stat) {
|
||||
|
@ -51,7 +51,7 @@ var StorageDropbox = {
|
|||
}, _.noop);
|
||||
},
|
||||
|
||||
save: function(path, data, callback, rev) {
|
||||
save: function(path, opts, data, callback, rev) {
|
||||
logger.debug('Save', path, rev);
|
||||
var ts = logger.ts();
|
||||
DropboxLink.saveFile(path, data, rev, function(err, stat) {
|
||||
|
|
|
@ -33,7 +33,7 @@ var StorageFileCache = {
|
|||
}
|
||||
},
|
||||
|
||||
save: function(id, data, callback) {
|
||||
save: function(id, opts, data, callback) {
|
||||
logger.debug('Save', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
@ -51,7 +51,7 @@ var StorageFileCache = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
load: function(id, callback) {
|
||||
load: function(id, opts, callback) {
|
||||
logger.debug('Load', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
@ -69,7 +69,7 @@ var StorageFileCache = {
|
|||
}).bind(this));
|
||||
},
|
||||
|
||||
remove: function(id, callback) {
|
||||
remove: function(id, opts, callback) {
|
||||
logger.debug('Remove', id);
|
||||
this.init((function(err) {
|
||||
if (err) {
|
||||
|
|
|
@ -12,7 +12,7 @@ var StorageFile = {
|
|||
icon: 'hdd-o',
|
||||
enabled: !!Launcher,
|
||||
|
||||
load: function(path, callback) {
|
||||
load: function(path, opts, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
|
@ -26,7 +26,7 @@ var StorageFile = {
|
|||
}
|
||||
},
|
||||
|
||||
stat: function(path, callback) {
|
||||
stat: function(path, opts, callback) {
|
||||
logger.debug('Stat', path);
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
|
@ -42,7 +42,7 @@ var StorageFile = {
|
|||
}
|
||||
},
|
||||
|
||||
save: function(path, data, callback, rev) {
|
||||
save: function(path, opts, data, callback, rev) {
|
||||
logger.debug('Save', path, rev);
|
||||
var ts = logger.ts();
|
||||
try {
|
||||
|
|
|
@ -10,32 +10,132 @@ var StorageDropbox = {
|
|||
enabled: true,
|
||||
|
||||
openFields: [
|
||||
{ id: 'url', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
|
||||
{ id: 'user', title: 'openUser', placeholder: 'openUserPlaceholder', type: 'text' },
|
||||
{ id: 'password', title: 'openPass', placeholder: 'openPassPlaceholder', type: 'password' }
|
||||
{ id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
|
||||
{ id: 'user', title: 'openUser', desc: 'openUserDesc', placeholder: 'openUserPlaceholder', type: 'text' },
|
||||
{ id: 'password', title: 'openPass', desc: 'openPassDesc', placeholder: 'openPassPlaceholder', type: 'password' }
|
||||
],
|
||||
|
||||
load: function(path, callback) {
|
||||
load: function(path, opts, callback) {
|
||||
logger.debug('Load', path);
|
||||
var ts = logger.ts();
|
||||
var stat = {};
|
||||
logger.debug('Loaded', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
this._request({
|
||||
op: 'Load',
|
||||
method: 'GET',
|
||||
path: path,
|
||||
user: opts ? opts.user : null,
|
||||
password: opts ? opts.password : null
|
||||
}, callback ? function(err, xhr, stat) {
|
||||
callback(err, xhr.response, stat);
|
||||
} : null);
|
||||
},
|
||||
|
||||
stat: function(path, callback) {
|
||||
stat: function(path, opts, callback) {
|
||||
logger.debug('Stat', path);
|
||||
var ts = logger.ts();
|
||||
var stat = {};
|
||||
logger.debug('Stated', path, stat ? stat.versionTag : null, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
this._request({
|
||||
op: 'Stat',
|
||||
method: 'HEAD',
|
||||
path: path,
|
||||
user: opts ? opts.user : null,
|
||||
password: opts ? opts.password : null
|
||||
}, callback ? function(err, xhr, stat) {
|
||||
callback(err, stat);
|
||||
} : null);
|
||||
},
|
||||
|
||||
save: function(path, data, callback, rev) {
|
||||
save: function(path, opts, data, callback, rev) {
|
||||
logger.debug('Save', path, rev);
|
||||
var etag, lastModified;
|
||||
if (rev && rev.charAt(0) === 'E') {
|
||||
etag = rev.substr(1);
|
||||
} else if (rev && rev.charAt(0) === 'T') {
|
||||
lastModified = rev.substr(1);
|
||||
}
|
||||
var cb = callback ? function(err, xhr, stat) {
|
||||
callback(err, stat);
|
||||
} : null;
|
||||
var saveOpts = {
|
||||
op: 'Save',
|
||||
method: 'POST',
|
||||
path: path,
|
||||
user: opts ? opts.user : null,
|
||||
password: opts ? opts.password : null,
|
||||
data: data,
|
||||
etag: etag
|
||||
};
|
||||
if (lastModified) {
|
||||
logger.debug('Stat before save', path, rev);
|
||||
this.stat(path, opts, function(err, stat) {
|
||||
if (err) { return cb(err); }
|
||||
if (stat.rev !== rev) {
|
||||
logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
|
||||
return cb({ revConflict: true });
|
||||
}
|
||||
this._request(saveOpts, cb);
|
||||
});
|
||||
} else {
|
||||
this._request(saveOpts, cb);
|
||||
}
|
||||
},
|
||||
|
||||
_request: function(config, callback) {
|
||||
var ts = logger.ts();
|
||||
logger.debug('Saved', path, logger.ts(ts));
|
||||
callback('not implemented');
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status !== 200) {
|
||||
logger.debug(config.op + ' error', config.path, xhr.status, logger.ts(ts));
|
||||
var err;
|
||||
switch (xhr.status) {
|
||||
case 404:
|
||||
err = { notFound: true };
|
||||
break;
|
||||
case 412:
|
||||
err = { revConflict: true };
|
||||
break;
|
||||
default:
|
||||
err = 'HTTP status ' + xhr.status;
|
||||
break;
|
||||
}
|
||||
if (callback) { callback(err); callback = null; }
|
||||
return;
|
||||
}
|
||||
var rev = xhr.getResponseHeader('ETag');
|
||||
if (rev) {
|
||||
rev = 'E' + rev;
|
||||
} else {
|
||||
rev = xhr.getResponseHeader('Last-Modified');
|
||||
if (rev) {
|
||||
rev = 'T' + rev;
|
||||
}
|
||||
}
|
||||
if (!rev) {
|
||||
logger.debug(config.op + ' error', config.path, 'no headers', logger.ts(ts));
|
||||
if (callback) { callback('No header ETag or Last-Modified'); callback = null; }
|
||||
return;
|
||||
}
|
||||
logger.debug(config.op + 'ed', config.path, rev, logger.ts(ts));
|
||||
if (callback) { callback(null, xhr, rev ? { rev: rev } : null); callback = null; }
|
||||
});
|
||||
xhr.addEventListener('error', function() {
|
||||
logger.debug(config.op + ' error', config.path, logger.ts(ts));
|
||||
if (callback) { callback('network error'); callback = null; }
|
||||
});
|
||||
xhr.addEventListener('abort', function() {
|
||||
logger.debug(config.op + ' error', config.path, 'aborted', logger.ts(ts));
|
||||
if (callback) { callback('aborted'); callback = null; }
|
||||
});
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.open(config.method, config.path);
|
||||
if (config.user) {
|
||||
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(config.user + ':' + config.password));
|
||||
}
|
||||
if (config.etag) {
|
||||
xhr.setRequestHeader('If-Match', config.etag);
|
||||
}
|
||||
if (config.data) {
|
||||
var blob = new Blob([config.data], {type: 'application/octet-stream'});
|
||||
xhr.send(blob);
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -135,8 +135,10 @@ var Locale = {
|
|||
openUrl: 'URL',
|
||||
openUrlDesc: 'https://server/path/file.kdbx, or just file.kdbx',
|
||||
openUser: 'Username',
|
||||
openUserDesc: 'WebDAV server username (if required)',
|
||||
openUserPlaceholder: 'no username',
|
||||
openPass: 'Password',
|
||||
openPassDesc: 'WebDAV server password (this is not your file password)',
|
||||
openPassPlaceholder: 'no password',
|
||||
openConfigError: 'Error: {}',
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
var UrlUtil = {
|
||||
getDataFileName: function(url) {
|
||||
var ix = url.lastIndexOf('/');
|
||||
if (ix >= 0) {
|
||||
url = url.substr(ix + 1);
|
||||
}
|
||||
url = url.replace(/\?.*/, '').replace(/\.kdbx/i, '');
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = UrlUtil;
|
|
@ -9,6 +9,7 @@ var Backbone = require('backbone'),
|
|||
DropboxLink = require('../comp/dropbox-link'),
|
||||
Logger = require('../util/logger'),
|
||||
Locale = require('../util/locale'),
|
||||
UrlUtil = require('../util/url-util'),
|
||||
Storage = require('../storage');
|
||||
|
||||
var logger = new Logger('open-view');
|
||||
|
@ -398,7 +399,7 @@ var OpenView = Backbone.View.extend({
|
|||
var allDropboxFiles = {};
|
||||
filesStat.forEach(function(file) {
|
||||
if (!file.isFolder && !file.isRemoved) {
|
||||
var fileName = file.name.replace(/\.kdbx/i, '');
|
||||
var fileName = UrlUtil.getDataFileName(file.name);
|
||||
buttons.push({ result: file.path, title: fileName });
|
||||
allDropboxFiles[file.path] = file;
|
||||
}
|
||||
|
@ -438,7 +439,7 @@ var OpenView = Backbone.View.extend({
|
|||
this.params.id = null;
|
||||
this.params.storage = 'dropbox';
|
||||
this.params.path = fileStat.path;
|
||||
this.params.name = fileStat.name.replace(/\.kdbx/i, '');
|
||||
this.params.name = UrlUtil.getDataFileName(fileStat.name);
|
||||
this.params.rev = fileStat.versionTag;
|
||||
this.params.fileData = null;
|
||||
this.displayOpenFile();
|
||||
|
@ -577,18 +578,36 @@ var OpenView = Backbone.View.extend({
|
|||
this.views.openConfig.setDisabled(true);
|
||||
var storage = Storage[config.storage];
|
||||
this.storageWaitId = Math.random();
|
||||
storage.stat(config, this.storageStatComplete.bind(this, this.storageWaitId));
|
||||
var path = config.path;
|
||||
var opts = _.omit(config, 'path');
|
||||
var req = {
|
||||
waitId: this.storageWaitId,
|
||||
storage: config.storage,
|
||||
path: path,
|
||||
opts: opts
|
||||
};
|
||||
storage.stat(path, opts, this.storageStatComplete.bind(this, req));
|
||||
},
|
||||
|
||||
storageStatComplete: function(waitId, err) {
|
||||
if (this.storageWaitId !== waitId) {
|
||||
storageStatComplete: function(req, err, stat) {
|
||||
if (this.storageWaitId !== req.waitId) {
|
||||
return;
|
||||
}
|
||||
this.storageWaitId = null;
|
||||
this.busy = false;
|
||||
this.views.openConfig.setDisabled(false);
|
||||
if (err) {
|
||||
this.views.openConfig.setDisabled(false);
|
||||
this.views.openConfig.setError(err);
|
||||
} else {
|
||||
this.closeConfig();
|
||||
this.params.id = null;
|
||||
this.params.storage = req.storage;
|
||||
this.params.path = req.path;
|
||||
this.params.opts = req.opts;
|
||||
this.params.name = UrlUtil.getDataFileName(req.path);
|
||||
this.params.rev = stat.rev;
|
||||
this.params.fileData = null;
|
||||
this.displayOpenFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue