diff --git a/chrome/content/zotero/preferences/preferences.js b/chrome/content/zotero/preferences/preferences.js index 0c3cbbafc..6b193fb49 100644 --- a/chrome/content/zotero/preferences/preferences.js +++ b/chrome/content/zotero/preferences/preferences.js @@ -291,7 +291,7 @@ function updateStorageSettings(enabled, protocol, skipWarnings) { var sql = "INSERT OR IGNORE INTO settings VALUES (?,?,?)"; Zotero.DB.query(sql, ['storage', 'zfsPurge', 'user']); - Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs', function (success) { + Zotero.Sync.Storage.ZFS.purgeDeletedStorageFiles(function (success) { if (success) { ps.alert( null, @@ -363,7 +363,7 @@ function verifyStorageServer() { abortButton.hidden = false; progressMeter.hidden = false; - var requestHolder = Zotero.Sync.Storage.checkServer('WebDAV', function (uri, status, callback) { + var requestHolder = Zotero.Sync.Storage.WebDAV.checkServer(function (uri, status, callback) { verifyButton.hidden = false; abortButton.hidden = true; progressMeter.hidden = true; @@ -388,7 +388,7 @@ function verifyStorageServer() { break; } - callback(uri, status, window); + Zotero.Sync.Storage.WebDAV.checkServerCallback(uri, status, window); }); abortButton.onclick = function () { diff --git a/chrome/content/zotero/preferences/preferences.xul b/chrome/content/zotero/preferences/preferences.xul index 7cc3afd03..ac98e0c38 100644 --- a/chrome/content/zotero/preferences/preferences.xul +++ b/chrome/content/zotero/preferences/preferences.xul @@ -157,7 +157,7 @@ To add a new preference: <prefpane id="zotero-prefpane-sync" label="&zotero.preferences.prefpane.sync;" - onpaneload="document.getElementById('sync-password').value = Zotero.Sync.Server.password; document.getElementById('storage-password').value = Zotero.Sync.Storage.password;" + onpaneload="document.getElementById('sync-password').value = Zotero.Sync.Server.password; var pass = Zotero.Sync.Storage.WebDAV.password; if (pass) { document.getElementById('storage-password').value = pass; }" image="chrome://zotero/skin/prefs-sync.png" helpTopic="sync"> <preferences> @@ -283,7 +283,7 @@ To add a new preference: preference="pref-storage-username" onkeypress="if (Zotero.isMac && event.keyCode == 13) { this.blur(); setTimeout(verifyStorageServer, 1); }" onsynctopreference="unverifyStorageServer();" - onchange="var pass = document.getElementById('storage-password'); if (pass.value) { Zotero.Sync.Storage.Session.WebDAV.prototype.password = pass.value; }"/> + onchange="var pass = document.getElementById('storage-password'); if (pass.value) { Zotero.Sync.Storage.WebDAV.password = pass.value; }"/> </hbox> </row> <row> @@ -292,7 +292,7 @@ To add a new preference: <textbox id="storage-password" flex="0" type="password" onkeypress="if (Zotero.isMac && event.keyCode == 13) { this.blur(); setTimeout(verifyStorageServer, 1); }" oninput="unverifyStorageServer()" - onchange="Zotero.Sync.Storage.Session.WebDAV.prototype.password = this.value"/> + onchange="Zotero.Sync.Storage.WebDAV.password = this.value;"/> </hbox> </row> <row> diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js index 622640e8a..1076b4da6 100644 --- a/chrome/content/zotero/xpcom/data/relations.js +++ b/chrome/content/zotero/xpcom/data/relations.js @@ -252,9 +252,13 @@ Zotero.Relations = new function () { } relation.libraryID = parseInt(libraryID); } - relation.subject = _getFirstChildContent(relationNode, 'subject'); - relation.predicate = _getFirstChildContent(relationNode, 'predicate'); - relation.object = _getFirstChildContent(relationNode, 'object'); + + var elems = Zotero.Utilities.xpath(relationNode, 'subject'); + relation.subject = elems.length ? elems[0].textContent : ""; + var elems = Zotero.Utilities.xpath(relationNode, 'predicate'); + relation.predicate = elems.length ? elems[0].textContent : ""; + var elems = Zotero.Utilities.xpath(relationNode, 'object'); + relation.object = elems.length ? elems[0].textContent : ""; return relation; } diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js index cfa917787..2c928bfa8 100644 --- a/chrome/content/zotero/xpcom/storage.js +++ b/chrome/content/zotero/xpcom/storage.js @@ -87,41 +87,41 @@ Zotero.Sync.Storage = new function () { // // Public methods // - this.sync = function (moduleName, observer) { - var module = getModuleFromName(moduleName); + this.sync = function (modeName, observer) { + var mode = getModeFromName(modeName); if (!observer) { throw new Error("Observer not provided"); } - registerDefaultObserver(moduleName); - Zotero.Sync.Storage.EventManager.registerObserver(observer, true, moduleName); + registerDefaultObserver(modeName); + Zotero.Sync.Storage.EventManager.registerObserver(observer, true, modeName); - if (!module.active) { - if (!module.enabled) { - Zotero.debug(module.name + " file sync is not enabled"); + if (!mode.active) { + if (!mode.enabled) { + Zotero.debug(mode.name + " file sync is not enabled"); Zotero.Sync.Storage.EventManager.skip(); return; } - Zotero.debug(module.name + " file sync is not active"); + Zotero.debug(mode.name + " file sync is not active"); // Try to verify server now if it hasn't been - if (!module.verified) { - module.checkServer(function (uri, status) { + if (!mode.verified) { + mode.checkServer(function (uri, status) { var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var lastWin = wm.getMostRecentWindow("navigator:browser"); - var success = module.checkServerCallback(uri, status, lastWin, true); + var success = mode.checkServerCallback(uri, status, lastWin, true); if (success) { - Zotero.debug(module.name + " file sync is successfully set up"); - Zotero.Sync.Storage.sync(module.name); + Zotero.debug(mode.name + " file sync is successfully set up"); + Zotero.Sync.Storage.sync(mode.name); } else { - Zotero.debug(module.name + " verification failed"); + Zotero.debug(mode.name + " verification failed"); var e = new Zotero.Error( - Zotero.getString('sync.storage.error.verificationFailed', module.name), + Zotero.getString('sync.storage.error.verificationFailed', mode.name), 0, { dialogButtonText: Zotero.getString('sync.openSyncPreferences'), @@ -141,8 +141,8 @@ Zotero.Sync.Storage = new function () { return; } - if (!module.includeUserFiles && !module.includeGroupFiles) { - Zotero.debug("No libraries are enabled for " + module.name + " syncing"); + if (!mode.includeUserFiles && !mode.includeGroupFiles) { + Zotero.debug("No libraries are enabled for " + mode.name + " syncing"); Zotero.Sync.Storage.EventManager.skip(); return; } @@ -153,7 +153,7 @@ Zotero.Sync.Storage = new function () { ); } - Zotero.debug("Beginning " + module.name + " file sync"); + Zotero.debug("Beginning " + mode.name + " file sync"); _syncInProgress = true; _changesMade = false; @@ -161,8 +161,8 @@ Zotero.Sync.Storage = new function () { Zotero.Sync.Storage.checkForUpdatedFiles( null, null, - module.includeUserFiles && Zotero.Sync.Storage.downloadOnSync(), - module.includeGroupFiles && Zotero.Sync.Storage.downloadOnSync('groups') + mode.includeUserFiles && Zotero.Sync.Storage.downloadOnSync(), + mode.includeGroupFiles && Zotero.Sync.Storage.downloadOnSync('groups') ); } catch (e) { @@ -171,14 +171,14 @@ Zotero.Sync.Storage = new function () { var self = this; - module.getLastSyncTime(function (lastSyncTime) { + mode.getLastSyncTime(function (lastSyncTime) { // Register the observers again to make sure they're active when we // start the queues. (They'll only be registered once.) Observers are // cleared when all queues finish, so without this another sync // process (e.g., on-demand download) could finish and clear all // observers while getLastSyncTime() is running. - registerDefaultObserver(moduleName); - Zotero.Sync.Storage.EventManager.registerObserver(observer, true, moduleName); + registerDefaultObserver(modeName); + Zotero.Sync.Storage.EventManager.registerObserver(observer, true, modeName); var download = true; @@ -186,17 +186,17 @@ Zotero.Sync.Storage = new function () { var force = !!Zotero.DB.valueQuery(sql, Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD); if (!force && lastSyncTime) { - var sql = "SELECT version FROM version WHERE schema='storage_" + moduleName + "'"; + var sql = "SELECT version FROM version WHERE schema='storage_" + modeName + "'"; var version = Zotero.DB.valueQuery(sql); if (version == lastSyncTime) { - Zotero.debug("Last " + module.name + " sync time hasn't changed -- skipping file download step"); + Zotero.debug("Last " + mode.name + " sync time hasn't changed -- skipping file download step"); download = false; } } try { - var activeDown = download ? _downloadFiles(module) : false; - var activeUp = _uploadFiles(module); + var activeDown = download ? _downloadFiles(mode) : false; + var activeUp = _uploadFiles(mode); } catch (e) { Zotero.Sync.Storage.EventManager.error(e); @@ -620,9 +620,9 @@ Zotero.Sync.Storage = new function () { */ this.downloadFile = function (item, requestCallbacks) { var itemID = item.id; - var module = getModuleFromLibrary(item.libraryID); + var mode = getModeFromLibrary(item.libraryID); - if (!module || !module.active) { + if (!mode || !mode.active) { Zotero.debug("File syncing is not active for item's library -- skipping download"); return false; } @@ -681,7 +681,7 @@ Zotero.Sync.Storage = new function () { requestCallbacks = {}; } var onStart = function (request) { - module.downloadFile(request); + mode.downloadFile(request); }; requestCallbacks.onStart = requestCallbacks.onStart ? [onStart, requestCallbacks.onStart] @@ -699,7 +699,7 @@ Zotero.Sync.Storage = new function () { }; setup(); - module.cacheCredentials(function () { + mode.cacheCredentials(function () { run(); }); @@ -820,8 +820,7 @@ Zotero.Sync.Storage = new function () { } - this.checkServer = function (moduleName, callback) { - var module = getModuleFromName(moduleName); + this.checkServer = function (modeName, callback) { Zotero.Sync.Storage.EventManager.registerObserver({ onSuccess: function () {}, onError: function (e) { @@ -833,47 +832,16 @@ Zotero.Sync.Storage = new function () { return true; } }, false, "checkServer"); - return module.checkServer(function (uri, status) { + + var mode = getModeFromName(modeName); + return mode.checkServer(function (uri, status) { callback(uri, status, function () { - module.checkServerCallback(uri, status); + mode.checkServerCallback(uri, status); }); }); } - this.purgeDeletedStorageFiles = function (moduleName, callback) { - var module = getModuleFromName(moduleName); - if (!module.active) { - return; - } - Zotero.Sync.Storage.EventManager.registerObserver({ - onError: function (e) { - error(e); - } - }, false, "purgeDeletedStorageFiles"); - module.purgeDeletedStorageFiles(callback); - } - - - this.purgeOrphanedStorageFiles = function (moduleName, callback) { - var module = getModuleFromName(moduleName); - if (!module.active) { - return; - } - Zotero.Sync.Storage.EventManager.registerObserver({ - onError: function (e) { - error(e); - } - }, false, "purgeOrphanedStorageFiles"); - module.purgeOrphanedStorageFiles(callback); - } - - - this.isActive = function (moduleName) { - return getModuleFromName(moduleName).active; - } - - this.resetAllSyncStates = function (syncState, includeUserFiles, includeGroupFiles) { if (!includeUserFiles && !includeGroupFiles) { includeUserFiles = true; @@ -922,12 +890,12 @@ Zotero.Sync.Storage = new function () { // // Private methods // - function getModuleFromName(moduleName) { - return new Zotero.Sync.Storage.Module(moduleName); + function getModeFromName(modeName) { + return Zotero.Sync.Storage[modeName]; } - function getModuleFromLibrary(libraryID) { + function getModeFromLibrary(libraryID) { if (libraryID === undefined) { throw new Error("libraryID not provided"); } @@ -942,10 +910,10 @@ Zotero.Sync.Storage = new function () { var protocol = Zotero.Prefs.get('sync.storage.protocol'); switch (protocol) { case 'zotero': - return getModuleFromName('ZFS'); + return getModeFromName('ZFS'); case 'webdav': - return getModuleFromName('WebDAV'); + return getModeFromName('WebDAV'); default: throw new Error("Invalid storage protocol '" + protocol + "'"); @@ -958,7 +926,7 @@ Zotero.Sync.Storage = new function () { return false; } - return getModuleFromName('ZFS'); + return getModeFromName('ZFS'); } } @@ -968,13 +936,13 @@ Zotero.Sync.Storage = new function () { * * @return {Boolean} */ - function _downloadFiles(module) { + function _downloadFiles(mode) { if (!_syncInProgress) { _syncInProgress = true; } - var includeUserFiles = module.includeUserFiles && Zotero.Sync.Storage.downloadOnSync(); - var includeGroupFiles = module.includeGroupFiles && Zotero.Sync.Storage.downloadOnSync('groups'); + var includeUserFiles = mode.includeUserFiles && Zotero.Sync.Storage.downloadOnSync(); + var includeGroupFiles = mode.includeGroupFiles && Zotero.Sync.Storage.downloadOnSync('groups'); if (!includeUserFiles && !includeGroupFiles) { Zotero.debug("No libraries are enabled for on-sync downloading"); @@ -1005,7 +973,7 @@ Zotero.Sync.Storage = new function () { item.libraryID + '/' + item.key, { onStart: function (request) { - module.downloadFile(request); + mode.downloadFile(request); } } ); @@ -1021,12 +989,12 @@ Zotero.Sync.Storage = new function () { * * @return {Boolean} */ - function _uploadFiles(module) { + function _uploadFiles(mode) { if (!_syncInProgress) { _syncInProgress = true; } - var uploadFileIDs = _getFilesToUpload(module.includeUserFiles, module.includeGroupFiles); + var uploadFileIDs = _getFilesToUpload(mode.includeUserFiles, mode.includeGroupFiles); if (!uploadFileIDs) { Zotero.debug("No files to upload"); return false; @@ -1044,7 +1012,7 @@ Zotero.Sync.Storage = new function () { item.libraryID + '/' + item.key, { onStart: function (request) { - module.uploadFile(request); + mode.uploadFile(request); } } ); @@ -1688,7 +1656,7 @@ Zotero.Sync.Storage = new function () { } - function registerDefaultObserver(moduleName) { + function registerDefaultObserver(modeName) { var finish = function (cancelled, skipSuccessFile) { // Upload success file when done if (!_resyncOnFinish && !skipSuccessFile) { @@ -1698,20 +1666,20 @@ Zotero.Sync.Storage = new function () { var uploadQueue = Zotero.Sync.Storage.QueueManager.get('upload', true); var useLastSyncTime = !uploadQueue || (!cancelled && uploadQueue.lastTotalRequests == 0); - getModuleFromName(moduleName).setLastSyncTime(function () { + getModeFromName(modeName).setLastSyncTime(function () { finish(cancelled, true); }, useLastSyncTime); return false; } - Zotero.debug(moduleName + " sync is complete"); + Zotero.debug(modeName + " sync is complete"); _syncInProgress = false; if (_resyncOnFinish) { Zotero.debug("Force-resyncing items in conflict"); _resyncOnFinish = false; - Zotero.Sync.Storage.sync(moduleName); + Zotero.Sync.Storage.sync(modeName); return false; } diff --git a/chrome/content/zotero/xpcom/storage/eventManager.js b/chrome/content/zotero/xpcom/storage/eventManager.js index d0ac3f460..a632579c5 100644 --- a/chrome/content/zotero/xpcom/storage/eventManager.js +++ b/chrome/content/zotero/xpcom/storage/eventManager.js @@ -130,7 +130,7 @@ Zotero.Sync.Storage.EventManager = (function () { var queues = Zotero.Sync.Storage.QueueManager.getAll(); for each(var queue in queues) { if (queue.isRunning()) { - Zotero.debug(queue[0].toUpperCase() + queue.substr(1) + Zotero.debug(queue.name[0].toUpperCase() + queue.name.substr(1) + " queue not empty -- not clearing storage sync event observers"); return; } diff --git a/chrome/content/zotero/xpcom/storage/mode.js b/chrome/content/zotero/xpcom/storage/mode.js new file mode 100644 index 000000000..b289600b4 --- /dev/null +++ b/chrome/content/zotero/xpcom/storage/mode.js @@ -0,0 +1,184 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2009 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see <http://www.gnu.org/licenses/>. + + ***** END LICENSE BLOCK ***** +*/ + + +Zotero.Sync.Storage.Mode = function () {}; + +Zotero.Sync.Storage.Mode.prototype.__defineGetter__('enabled', function () { + try { + return this._enabled; + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.__defineGetter__('verified', function () { + try { + return this._verified; + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.__defineGetter__('active', function () { + try { + return this._enabled && this._verified && this._initFromPrefs(); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () { + try { + return this._username; + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () { + try { + return this._password; + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) { + try { + this._password = val; + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +}); + +Zotero.Sync.Storage.Mode.prototype.init = function () { + try { + return this._init(); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.initFromPrefs = function () { + try { + return this._initFromPrefs(); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.sync = function (observer) { + Zotero.Sync.Storage.sync(this.name, observer); +} + +Zotero.Sync.Storage.Mode.prototype.downloadFile = function (request) { + try { + this._downloadFile(request); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.uploadFile = function (request) { + try { + this._uploadFile(request); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.getLastSyncTime = function (callback) { + try { + this._getLastSyncTime(callback); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.setLastSyncTime = function (callback, useLastSyncTime) { + try { + this._setLastSyncTime(callback, useLastSyncTime); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.checkServer = function (callback) { + try { + return this._checkServer(callback); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) { + try { + return this._checkServerCallback(uri, status, window, skipSuccessMessage); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) { + try { + return this._cacheCredentials(callback); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) { + try { + this._purgeDeletedStorageFiles(callback); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} + +Zotero.Sync.Storage.Mode.prototype.purgeOrphanedStorageFiles = function (callback) { + try { + this._purgeOrphanedStorageFiles(callback); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } +} diff --git a/chrome/content/zotero/xpcom/storage/module.js b/chrome/content/zotero/xpcom/storage/module.js deleted file mode 100644 index fc32d83c8..000000000 --- a/chrome/content/zotero/xpcom/storage/module.js +++ /dev/null @@ -1,198 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see <http://www.gnu.org/licenses/>. - - ***** END LICENSE BLOCK ***** -*/ - - -Zotero.Sync.Storage.Module = function (moduleName) { - switch (moduleName) { - case 'ZFS': - this._module = Zotero.Sync.Storage.Module.ZFS; - break; - - case 'WebDAV': - this._module = Zotero.Sync.Storage.Module.WebDAV; - break; - - default: - throw ("Invalid storage session module '" + moduleName + "'"); - } -}; - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('name', function () this._module.name); -Zotero.Sync.Storage.Module.prototype.__defineGetter__('includeUserFiles', function () this._module.includeUserFiles); -Zotero.Sync.Storage.Module.prototype.__defineGetter__('includeGroupFiles', function () this._module.includeGroupFiles); - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('enabled', function () { - try { - return this._module.enabled; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('verified', function () { - try { - return this._module.verified; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('active', function () { - try { - return this._module.enabled && this._module.initFromPrefs() && this._module.verified; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('username', function () { - try { - return this._module.username; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - -Zotero.Sync.Storage.Module.prototype.__defineGetter__('password', function () { - try { - return this._module.password; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - -Zotero.Sync.Storage.Module.prototype.__defineSetter__('password', function (val) { - try { - this._module.password = val; - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -}); - - -Zotero.Sync.Storage.Module.prototype.init = function () { - try { - return this._module.init(); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.initFromPrefs = function () { - try { - return this._module.initFromPrefs(); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.downloadFile = function (request) { - try { - this._module.downloadFile(request); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.uploadFile = function (request) { - try { - this._module.uploadFile(request); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.getLastSyncTime = function (callback) { - try { - this._module.getLastSyncTime(callback); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.setLastSyncTime = function (callback, useLastSyncTime) { - try { - this._module.setLastSyncTime(callback, useLastSyncTime); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.checkServer = function (callback) { - try { - return this._module.checkServer(callback); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) { - try { - return this._module.checkServerCallback(uri, status, window, skipSuccessMessage); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.cacheCredentials = function (callback) { - try { - return this._module.cacheCredentials(callback); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.purgeDeletedStorageFiles = function (callback) { - try { - this._module.purgeDeletedStorageFiles(callback); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} - -Zotero.Sync.Storage.Module.prototype.purgeOrphanedStorageFiles = function (callback) { - try { - this._module.purgeOrphanedStorageFiles(callback); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } -} diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js index 5206c932b..507bbd435 100644 --- a/chrome/content/zotero/xpcom/storage/webdav.js +++ b/chrome/content/zotero/xpcom/storage/webdav.js @@ -24,7 +24,7 @@ */ -Zotero.Sync.Storage.Module.WebDAV = (function () { +Zotero.Sync.Storage.WebDAV = (function () { // TEMP // TODO: localize var _defaultError = "A WebDAV file sync error occurred. Please try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences."; @@ -387,7 +387,7 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { * Create a Zotero directory on the storage server */ function createServerDirectory(callback) { - var uri = Zotero.Sync.Storage.Module.WebDAV.rootURI; + var uri = Zotero.Sync.Storage.WebDAV.rootURI; Zotero.HTTP.WebDAV.doMkCol(uri, function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -429,7 +429,7 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { * @return {nsIURI} URI of file on storage server */ function getItemURI(item) { - var uri = Zotero.Sync.Storage.Module.WebDAV.rootURI; + var uri = Zotero.Sync.Storage.WebDAV.rootURI; uri.spec = uri.spec + item.key + '.zip'; return uri; } @@ -443,7 +443,7 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { * @return {nsIURI} URI of property file on storage server */ function getItemPropertyURI(item) { - var uri = Zotero.Sync.Storage.Module.WebDAV.rootURI; + var uri = Zotero.Sync.Storage.WebDAV.rootURI; uri.spec = uri.spec + item.key + '.prop'; return uri; } @@ -670,31 +670,37 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { } - return { - name: "WebDAV", - - get includeUserFiles() { + // + // Public methods (called via Zotero.Sync.Storage.WebDAV) + // + var obj = new Zotero.Sync.Storage.Mode; + obj.name = "WebDAV"; + + Object.defineProperty(obj, "includeUserFiles", { + get: function () { return Zotero.Prefs.get("sync.storage.enabled") && Zotero.Prefs.get("sync.storage.protocol") == 'webdav'; - }, - includeGroupItems: false, + } + }); + obj.includeGroupItems = false; - get enabled() { - return this.includeUserFiles; - }, - - get verified() { - return Zotero.Prefs.get("sync.storage.verified"); - }, - - get username() { - return Zotero.Prefs.get('sync.storage.username'); - }, - - get password() { - var username = this.username; + Object.defineProperty(obj, "_enabled", { + get: function () this.includeUserFiles + }); + + Object.defineProperty(obj, "_verified", { + get: function () Zotero.Prefs.get("sync.storage.verified") + }); + + Object.defineProperty(obj, "_username", { + get: function () Zotero.Prefs.get('sync.storage.username') + }); + + Object.defineProperty(obj, "_password", { + get: function () { + var username = this._username; if (!username) { - Zotero.debug('Username not set before getting Zotero.Sync.Storage.Module.WebDAV.password'); + Zotero.debug('Username not set before getting Zotero.Sync.Storage.WebDAV.password'); return ''; } @@ -713,10 +719,10 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { return ''; }, - set password(password) { - var username = this.username; + set: function (password) { + var username = this._username; if (!username) { - Zotero.debug('Username not set before setting Zotero.Sync.Server.Module.WebDAV.password'); + Zotero.debug('Username not set before setting Zotero.Sync.Server.Mode.WebDAV.password'); return; } @@ -740,419 +746,552 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { null, username, password, "", ""); loginManager.addLogin(loginInfo); } - }, - - get rootURI() { + } + }); + + Object.defineProperty(obj, "rootURI", { + get: function () { if (!_rootURI) { throw new Error("Root URI not initialized"); } return _rootURI.clone(); - }, - - get parentURI() { + } + }); + + Object.defineProperty(obj, "parentURI", { + get: function () { if (!_parentURI) { throw new Error("Parent URI not initialized"); } return _parentURI.clone(); - }, + } + }); + + obj._init = function (url, dir, username, password) { + if (!url) { + var msg = "WebDAV URL not provided"; + Zotero.debug(msg); + throw ({ + message: msg, + name: "Z_ERROR_NO_URL", + filename: "webdav.js", + toString: function () { return this.message; } + }); + } + if (username && !password) { + var msg = "WebDAV password not provided"; + Zotero.debug(msg); + throw ({ + message: msg, + name: "Z_ERROR_NO_PASSWORD", + filename: "webdav.js", + toString: function () { return this.message; } + }); + } - init: function (url, dir, username, password) { - if (!url) { - var msg = "WebDAV URL not provided"; - Zotero.debug(msg); - throw ({ - message: msg, - name: "Z_ERROR_NO_URL", - filename: "webdav.js", - toString: function () { return this.message; } - }); + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + try { + var uri = ios.newURI(url, null, null); + if (username) { + uri.username = username; + uri.password = password; + } + } + catch (e) { + Zotero.debug(e); + Components.utils.reportError(e); + return false; + } + if (!uri.spec.match(/\/$/)) { + uri.spec += "/"; + } + _parentURI = uri; + + var uri = uri.clone(); + uri.spec += "zotero/"; + _rootURI = uri; + return true; + }; + + + obj._initFromPrefs = function () { + var scheme = Zotero.Prefs.get('sync.storage.scheme'); + switch (scheme) { + case 'http': + case 'https': + break; + + default: + throw new Error("Invalid WebDAV scheme '" + scheme + "'"); + } + + var url = Zotero.Prefs.get('sync.storage.url'); + if (!url) { + return false; + } + + url = scheme + '://' + url; + var dir = "zotero"; + var username = this._username; + var password = this._password; + + return this._init(url, dir, username, password); + }; + + + /** + * Begin download process for individual file + * + * @param {Zotero.Sync.Storage.Request} [request] + */ + obj._downloadFile = function (request) { + var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); + if (!item) { + throw new Error("Item '" + request.name + "' not found"); + } + + // Retrieve modification time from server to store locally afterwards + getStorageModificationTime(item, function (item, mdate) { + if (!request.isRunning()) { + Zotero.debug("Download request '" + request.name + + "' is no longer running after getting mod time"); + return; } - if (username && !password) { - var msg = "WebDAV password not provided"; - Zotero.debug(msg); - throw ({ - message: msg, - name: "Z_ERROR_NO_PASSWORD", - filename: "webdav.js", - toString: function () { return this.message; } - }); + if (!mdate) { + Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item)); + request.finish(); + return; } - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); try { - var uri = ios.newURI(url, null, null); - if (username) { - uri.username = username; - uri.password = password; - } - } - catch (e) { - Zotero.debug(e); - Components.utils.reportError(e); - return false; - } - if (!uri.spec.match(/\/$/)) { - uri.spec += "/"; - } - _parentURI = uri; - - var uri = uri.clone(); - uri.spec += "zotero/"; - _rootURI = uri; - return true; - }, - - - initFromPrefs: function () { - var scheme = Zotero.Prefs.get('sync.storage.scheme'); - switch (scheme) { - case 'http': - case 'https': - break; + var syncModTime = mdate.getTime(); - default: - throw new Error("Invalid WebDAV scheme '" + scheme + "'"); - } - - var url = Zotero.Prefs.get('sync.storage.url'); - if (!url) { - return false; - } - - url = scheme + '://' + url; - var dir = "zotero"; - var username = this.username; - var password = this.password; - - return this.init(url, dir, username, password); - }, - - - /** - * Begin download process for individual file - * - * @param {Zotero.Sync.Storage.Request} [request] - */ - downloadFile: function (request) { - var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); - if (!item) { - throw new Error("Item '" + request.name + "' not found"); - } - - // Retrieve modification time from server to store locally afterwards - getStorageModificationTime(item, function (item, mdate) { - if (!request.isRunning()) { - Zotero.debug("Download request '" + request.name - + "' is no longer running after getting mod time"); - return; - } - - if (!mdate) { - Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item)); + // Skip download if local file exists and matches mod time + var file = item.getFile(); + if (file && file.exists() && syncModTime == file.lastModifiedTime) { + Zotero.debug("File mod time matches remote file -- skipping download"); + + Zotero.DB.beginTransaction(); + var syncState = Zotero.Sync.Storage.getSyncState(item.id); + var updateItem = syncState != 1; + Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); + Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); + Zotero.DB.commitTransaction(); + onChangesMade(); request.finish(); return; } - try { - var syncModTime = mdate.getTime(); - - // Skip download if local file exists and matches mod time - var file = item.getFile(); - if (file && file.exists() && syncModTime == file.lastModifiedTime) { - Zotero.debug("File mod time matches remote file -- skipping download"); - - Zotero.DB.beginTransaction(); - var syncState = Zotero.Sync.Storage.getSyncState(item.id); - var updateItem = syncState != 1; - Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); - Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); - Zotero.DB.commitTransaction(); - onChangesMade(); - request.finish(); - return; - } - - var uri = getItemURI(item); - var destFile = Zotero.getTempDirectory(); - destFile.append(item.key + '.zip.tmp'); - if (destFile.exists()) { - destFile.remove(false); - } - - var listener = new Zotero.Sync.Storage.StreamListener( - { - onStart: function (request, data) { - if (data.request.isFinished()) { - Zotero.debug("Download request " + data.request.name - + " stopped before download started -- closing channel"); - request.cancel(0x804b0002); // NS_BINDING_ABORTED - return; - } - }, - onProgress: function (a, b, c) { - request.onProgress(a, b, c) - }, - onStop: function (request, status, response, data) { - if (status == 404) { - var msg = "Remote ZIP file not found for item " + item.key; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - - // Delete the orphaned prop file - deleteStorageFiles([item.key + ".prop"]); - - data.request.finish(); - return; - } - else if (status != 200) { - var msg = "Unexpected status code " + status - + " for request " + data.request.name - + " in Zotero.Sync.Storage.Module.WebDAV.downloadFile()"; - Zotero.debug(msg, 1); - Components.utils.reportError(msg); - Zotero.Sync.Storage.EventManager.error(_defaultError); - } - - // Don't try to process if the request has been cancelled - if (data.request.isFinished()) { - Zotero.debug("Download request " + data.request.name - + " is no longer running after file download"); - return; - } - - Zotero.debug("Finished download of " + destFile.path); - - try { - Zotero.Sync.Storage.processDownload(data); - data.request.finish(); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } - }, - request: request, - item: item, - compressed: true, - syncModTime: syncModTime - } - ); - - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = '********'; - } - Zotero.debug('Saving ' + disp.spec + ' with saveURI()'); - const nsIWBP = Components.interfaces.nsIWebBrowserPersist; - var wbp = Components - .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(nsIWBP); - wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE; - wbp.progressListener = listener; - wbp.saveURI(uri, null, null, null, null, destFile); + var uri = getItemURI(item); + var destFile = Zotero.getTempDirectory(); + destFile.append(item.key + '.zip.tmp'); + if (destFile.exists()) { + destFile.remove(false); } - catch (e) { - request.error(e); - } - }); - }, - - - uploadFile: function (request) { - Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); }); - }, - - - getLastSyncTime: function (callback) { - // Cache the credentials at the root URI - var self = this; - this.cacheCredentials(function () { - try { - var uri = this.rootURI; - var successFileURI = uri.clone(); - successFileURI.spec += "lastsync"; - Zotero.HTTP.doGet(successFileURI, function (req) { - var ts = undefined; - try { - if (req.responseText) { - Zotero.debug(req.responseText); + + var listener = new Zotero.Sync.Storage.StreamListener( + { + onStart: function (request, data) { + if (data.request.isFinished()) { + Zotero.debug("Download request " + data.request.name + + " stopped before download started -- closing channel"); + request.cancel(0x804b0002); // NS_BINDING_ABORTED + return; } - Zotero.debug(req.status); - - if (req.status == 403) { - Zotero.debug("Clearing WebDAV authentication credentials", 2); - _cachedCredentials = false; + }, + onProgress: function (a, b, c) { + request.onProgress(a, b, c) + }, + onStop: function (request, status, response, data) { + if (status == 404) { + var msg = "Remote ZIP file not found for item " + item.key; + Zotero.debug(msg, 2); + Components.utils.reportError(msg); + + // Delete the orphaned prop file + deleteStorageFiles([item.key + ".prop"]); + + data.request.finish(); + return; } - - if (req.status != 200 && req.status != 404) { - var msg = "Unexpected status code " + req.status + " for HEAD request " - + "in Zotero.Sync.Storage.Module.WebDAV.getLastSyncTime()"; + else if (status != 200) { + var msg = "Unexpected status code " + status + + " for request " + data.request.name + + " in Zotero.Sync.Storage.WebDAV.downloadFile()"; Zotero.debug(msg, 1); Components.utils.reportError(msg); Zotero.Sync.Storage.EventManager.error(_defaultError); } - if (req.status == 200) { - var lastModified = req.getResponseHeader("Last-Modified"); - var date = new Date(lastModified); - Zotero.debug("Last successful storage sync was " + date); - ts = Zotero.Date.toUnixTimestamp(date); - } - else { - ts = null; + // Don't try to process if the request has been cancelled + if (data.request.isFinished()) { + Zotero.debug("Download request " + data.request.name + + " is no longer running after file download"); + return; } - callback(ts); - } - catch(e) { - Zotero.debug(e, 1); - Components.utils.reportError(e); - Zotero.Sync.Storage.EventManager.error(_defaultError); - } - }); - return; + Zotero.debug("Finished download of " + destFile.path); + + try { + Zotero.Sync.Storage.processDownload(data); + data.request.finish(); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } + }, + request: request, + item: item, + compressed: true, + syncModTime: syncModTime + } + ); + + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = '********'; } - catch (e) { - Zotero.debug(e); - Components.utils.reportError(e); - Zotero.Sync.Storage.EventManager.error(_defaultError); - } - }); - }, - - - setLastSyncTime: function (callback) { + Zotero.debug('Saving ' + disp.spec + ' with saveURI()'); + const nsIWBP = Components.interfaces.nsIWebBrowserPersist; + var wbp = Components + .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE; + wbp.progressListener = listener; + wbp.saveURI(uri, null, null, null, null, destFile); + } + catch (e) { + request.error(e); + } + }); + }; + + + obj._uploadFile = function (request) { + Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); }); + }; + + + obj._getLastSyncTime = function (callback) { + // Cache the credentials at the root URI + var self = this; + this._cacheCredentials(function () { try { var uri = this.rootURI; var successFileURI = uri.clone(); successFileURI.spec += "lastsync"; - - Zotero.HTTP.WebDAV.doPut(successFileURI, " ", function (req) { - Zotero.debug(req.responseText); - Zotero.debug(req.status); - - switch (req.status) { - case 200: - case 201: - case 204: - getLastSyncTime(function (ts) { - if (ts) { - var sql = "REPLACE INTO version VALUES ('storage_webdav', ?)"; - Zotero.DB.query(sql, { int: ts }); - } - if (callback) { - callback(); - } - }); - return; + Zotero.HTTP.doGet(successFileURI, function (req) { + var ts = undefined; + try { + if (req.responseText) { + Zotero.debug(req.responseText); + } + Zotero.debug(req.status); + + if (req.status == 403) { + Zotero.debug("Clearing WebDAV authentication credentials", 2); + _cachedCredentials = false; + } + + if (req.status != 200 && req.status != 404) { + var msg = "Unexpected status code " + req.status + " for HEAD request " + + "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()"; + Zotero.debug(msg, 1); + Components.utils.reportError(msg); + Zotero.Sync.Storage.EventManager.error(_defaultError); + } + + if (req.status == 200) { + var lastModified = req.getResponseHeader("Last-Modified"); + var date = new Date(lastModified); + Zotero.debug("Last successful storage sync was " + date); + ts = Zotero.Date.toUnixTimestamp(date); + } + else { + ts = null; + } + + callback(ts); } - - var msg = "Unexpected error code " + req.status + " uploading storage success file"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - if (callback) { - callback(); + catch(e) { + Zotero.debug(e, 1); + Components.utils.reportError(e); + Zotero.Sync.Storage.EventManager.error(_defaultError); } }); + return; } catch (e) { Zotero.debug(e); Components.utils.reportError(e); - if (callback) { - callback(); - } - return; - } - }, - - - cacheCredentials: function (callback) { - if (_cachedCredentials) { - Zotero.debug("Credentials are already cached"); - setTimeout(function () { - callback(); - }, 0); - return false; + Zotero.Sync.Storage.EventManager.error(_defaultError); } + }); + }; + + + obj._setLastSyncTime = function (callback) { + try { + var uri = this.rootURI; + var successFileURI = uri.clone(); + successFileURI.spec += "lastsync"; - Zotero.HTTP.doOptions(this.rootURI, function (req) { - checkResponse(req); - - if (req.status != 200) { - var msg = "Unexpected status code " + req.status + " for OPTIONS request " - + "in Zotero.Sync.Storage.Module.WebDAV.getLastSyncTime()"; - Zotero.debug(msg, 1); - Components.utils.reportError(msg); - Zotero.Sync.Storage.EventManager.error(_defaultErrorRestart); - } - Zotero.debug("Credentials are cached"); - _cachedCredentials = true; - callback(); - }); - return true; - }, - - - /** - * @param {Function} callback Function to pass URI and result value to - * @param {Object} errorCallbacks - */ - checkServer: function (callback) { - this.initFromPrefs(); - - try { - var parentURI = this.parentURI; - var uri = this.rootURI; - } - catch (e) { - switch (e.name) { - case 'Z_ERROR_NO_URL': - callback(null, Zotero.Sync.Storage.ERROR_NO_URL); - return; - - case 'Z_ERROR_NO_PASSWORD': - callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD); - return; - - default: - Zotero.debug(e); - Components.utils.reportError(e); - callback(null, Zotero.Sync.Storage.ERROR_UNKNOWN); - return; - } - } - - var requestHolder = { request: null }; - - var prolog = '<?xml version="1.0" encoding="utf-8" ?>\n'; - var D = new Namespace("D", "DAV:"); - var nsDeclarations = 'xmlns:' + D.prefix + '=' + '"' + D.uri + '"'; - - var requestXML = new XML('<D:propfind ' + nsDeclarations + '/>'); - requestXML.D::prop = ''; - // IIS 5.1 requires at least one property in PROPFIND - requestXML.D::prop.D::getcontentlength = ''; - - var xmlstr = prolog + requestXML.toXMLString(); - - // Test whether URL is WebDAV-enabled - var request = Zotero.HTTP.doOptions(uri, function (req) { - // Timeout - if (req.status == 0) { - checkResponse(req); - - callback(uri, Zotero.Sync.Storage.ERROR_UNREACHABLE); - return; - } - - Zotero.debug(req.getAllResponseHeaders()); + Zotero.HTTP.WebDAV.doPut(successFileURI, " ", function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); switch (req.status) { + case 200: + case 201: + case 204: + getLastSyncTime(function (ts) { + if (ts) { + var sql = "REPLACE INTO version VALUES ('storage_webdav', ?)"; + Zotero.DB.query(sql, { int: ts }); + } + if (callback) { + callback(); + } + }); + return; + } + + var msg = "Unexpected error code " + req.status + " uploading storage success file"; + Zotero.debug(msg, 2); + Components.utils.reportError(msg); + if (callback) { + callback(); + } + }); + } + catch (e) { + Zotero.debug(e); + Components.utils.reportError(e); + if (callback) { + callback(); + } + return; + } + }; + + + obj._cacheCredentials = function (callback) { + if (_cachedCredentials) { + Zotero.debug("Credentials are already cached"); + setTimeout(function () { + callback(); + }, 0); + return false; + } + + Zotero.HTTP.doOptions(this.rootURI, function (req) { + checkResponse(req); + + if (req.status != 200) { + var msg = "Unexpected status code " + req.status + " for OPTIONS request " + + "in Zotero.Sync.Storage.WebDAV.getLastSyncTime()"; + Zotero.debug(msg, 1); + Components.utils.reportError(msg); + Zotero.Sync.Storage.EventManager.error(_defaultErrorRestart); + } + Zotero.debug("Credentials are cached"); + _cachedCredentials = true; + callback(); + }); + return true; + }; + + + /** + * @param {Function} callback Function to pass URI and result value to + * @param {Object} errorCallbacks + */ + obj._checkServer = function (callback) { + this._initFromPrefs(); + + try { + var parentURI = this.parentURI; + var uri = this.rootURI; + } + catch (e) { + switch (e.name) { + case 'Z_ERROR_NO_URL': + callback(null, Zotero.Sync.Storage.ERROR_NO_URL); + return; + + case 'Z_ERROR_NO_PASSWORD': + callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD); + return; + + default: + Zotero.debug(e); + Components.utils.reportError(e); + callback(null, Zotero.Sync.Storage.ERROR_UNKNOWN); + return; + } + } + + var requestHolder = { request: null }; + + var prolog = '<?xml version="1.0" encoding="utf-8" ?>\n'; + var D = new Namespace("D", "DAV:"); + var nsDeclarations = 'xmlns:' + D.prefix + '=' + '"' + D.uri + '"'; + + var requestXML = new XML('<D:propfind ' + nsDeclarations + '/>'); + requestXML.D::prop = ''; + // IIS 5.1 requires at least one property in PROPFIND + requestXML.D::prop.D::getcontentlength = ''; + + var xmlstr = prolog + requestXML.toXMLString(); + + // Test whether URL is WebDAV-enabled + var request = Zotero.HTTP.doOptions(uri, function (req) { + // Timeout + if (req.status == 0) { + checkResponse(req); + + callback(uri, Zotero.Sync.Storage.ERROR_UNREACHABLE); + return; + } + + Zotero.debug(req.getAllResponseHeaders()); + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + switch (req.status) { + case 400: + callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST); + return; + + case 401: + callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); + return; + + case 403: + callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); + return; + + case 500: + callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); + return; + } + + var dav = req.getResponseHeader("DAV"); + if (dav == null) { + callback(uri, Zotero.Sync.Storage.ERROR_NOT_DAV); + return; + } + + // Get the Authorization header used in case we need to do a request + // on the parent below + var channelAuthorization = Zotero.HTTP.getChannelAuthorization(req.channel); + + var headers = { Depth: 0 }; + + // Test whether Zotero directory exists + Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + switch (req.status) { + case 207: + // Test if Zotero directory is writable + var testFileURI = uri.clone(); + testFileURI.spec += "zotero-test-file"; + Zotero.HTTP.WebDAV.doPut(testFileURI, " ", function (req) { + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + switch (req.status) { + case 200: + case 201: + case 204: + Zotero.HTTP.doGet( + testFileURI, + function (req) { + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + switch (req.status) { + case 200: + // Delete test file + Zotero.HTTP.WebDAV.doDelete( + testFileURI, + function (req) { + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + switch (req.status) { + case 200: // IIS 5.1 and Sakai return 200 + case 204: + callback(uri, Zotero.Sync.Storage.SUCCESS); + return; + + case 401: + callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); + return; + + case 403: + callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); + return; + + default: + callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); + return; + } + } + ); + return; + + case 401: + callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); + return; + + case 403: + callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); + return; + + // IIS 6+ configured not to serve extensionless files or .prop files + // http://support.microsoft.com/kb/326965 + case 404: + callback(uri, Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD); + return; + + case 500: + callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); + return; + + default: + callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); + return; + } + } + ); + return; + + case 401: + callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); + return; + + case 403: + callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); + return; + + case 500: + callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); + return; + + default: + callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); + return; + } + }); + return; + case 400: callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST); return; @@ -1165,530 +1304,399 @@ Zotero.Sync.Storage.Module.WebDAV = (function () { callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); return; - case 500: - callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); - return; - } - - var dav = req.getResponseHeader("DAV"); - if (dav == null) { - callback(uri, Zotero.Sync.Storage.ERROR_NOT_DAV); - return; - } - - // Get the Authorization header used in case we need to do a request - // on the parent below - var channelAuthorization = Zotero.HTTP.getChannelAuthorization(req.channel); - - var headers = { Depth: 0 }; - - // Test whether Zotero directory exists - Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { - Zotero.debug(req.responseText); - Zotero.debug(req.status); - - switch (req.status) { - case 207: - // Test if Zotero directory is writable - var testFileURI = uri.clone(); - testFileURI.spec += "zotero-test-file"; - Zotero.HTTP.WebDAV.doPut(testFileURI, " ", function (req) { + case 404: + // Include Authorization header from /zotero request, + // since Firefox probably won't apply it to the parent request + var newHeaders = {}; + for (var header in headers) { + newHeaders[header] = headers[header]; + } + newHeaders["Authorization"] = channelAuthorization; + + // Zotero directory wasn't found, so see if at least + // the parent directory exists + Zotero.HTTP.WebDAV.doProp("PROPFIND", parentURI, xmlstr, + function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); switch (req.status) { - case 200: - case 201: - case 204: - Zotero.HTTP.doGet( - testFileURI, - function (req) { - Zotero.debug(req.responseText); - Zotero.debug(req.status); - - switch (req.status) { - case 200: - // Delete test file - Zotero.HTTP.WebDAV.doDelete( - testFileURI, - function (req) { - Zotero.debug(req.responseText); - Zotero.debug(req.status); - - switch (req.status) { - case 200: // IIS 5.1 and Sakai return 200 - case 204: - callback( - uri, - Zotero.Sync.Storage.SUCCESS - ); - return; - - case 401: - callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); - return; - - case 403: - callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); - return; - - default: - callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); - return; - } - } - ); - return; - - case 401: - callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); - return; - - case 403: - callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); - return; - - // IIS 6+ configured not to serve extensionless files or .prop files - // http://support.microsoft.com/kb/326965 - case 404: - callback(uri, Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD); - return; - - case 500: - callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); - return; - - default: - callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); - return; - } - } - ); + // Parent directory existed + case 207: + callback(uri, Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND); + return; + + case 400: + callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST); return; case 401: callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); return; - case 403: - callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); - return; - - case 500: - callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); + // Parent directory wasn't found either + case 404: + callback(uri, Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND); return; default: callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); return; } - }); - return; - - case 400: - callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST); - return; - - case 401: - callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); - return; - - case 403: - callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN); - return; - - case 404: - // Include Authorization header from /zotero request, - // since Firefox probably won't apply it to the parent request - var newHeaders = {}; - for (var header in headers) { - newHeaders[header] = headers[header]; - } - newHeaders["Authorization"] = channelAuthorization; - - // Zotero directory wasn't found, so see if at least - // the parent directory exists - Zotero.HTTP.WebDAV.doProp("PROPFIND", this.parentURI, xmlstr, - function (req) { - Zotero.debug(req.responseText); - Zotero.debug(req.status); - - switch (req.status) { - // Parent directory existed - case 207: - callback(uri, Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND); - return; - - case 400: - callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST); - return; - - case 401: - callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED); - return; - - // Parent directory wasn't found either - case 404: - callback(uri, Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND); - return; - - default: - callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); - return; - } - }, newHeaders); - return; - - case 500: - callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); - return; - - default: - callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); - return; - } - }, headers); - }); - - if (!request) { - callback(uri, Zotero.Sync.Storage.ERROR_OFFLINE); - } - - requestHolder.request = request; - return requestHolder; - }, - - - checkServerCallback: function (uri, status, window, skipSuccessMessage) { - var promptService = - Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. - createInstance(Components.interfaces.nsIPromptService); - if (uri) { - var spec = uri.scheme + '://' + uri.hostPort + uri.path; - } - - switch (status) { - case Zotero.Sync.Storage.SUCCESS: - if (!skipSuccessMessage) { - promptService.alert( - window, - Zotero.getString('sync.storage.serverConfigurationVerified'), - Zotero.getString('sync.storage.fileSyncSetUp') - ); - } - Zotero.Prefs.set("sync.storage.verified", true); - return true; - - case Zotero.Sync.Storage.ERROR_NO_URL: - var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL'); - break; - - case Zotero.Sync.Storage.ERROR_NO_PASSWORD: - var errorMessage = Zotero.getString('sync.error.enterPassword'); - break; - - case Zotero.Sync.Storage.ERROR_UNREACHABLE: - var errorMessage = Zotero.getString('sync.storage.error.serverCouldNotBeReached', uri.host); - break; - - case Zotero.Sync.Storage.ERROR_NOT_DAV: - var errorMessage = Zotero.getString('sync.storage.error.webdav.invalidURL', spec); - break; - - case Zotero.Sync.Storage.ERROR_AUTH_FAILED: - var errorTitle = Zotero.getString('general.permissionDenied'); - var errorMessage = Zotero.localeJoin([ - Zotero.getString('sync.storage.error.webdav.invalidLogin'), - Zotero.getString('sync.storage.error.checkFileSyncSettings') - ]); - break; - - case Zotero.Sync.Storage.ERROR_FORBIDDEN: - var errorTitle = Zotero.getString('general.permissionDenied'); - var errorMessage = Zotero.localeJoin([ - Zotero.getString('sync.storage.error.webdav.permissionDenied', uri.path), - Zotero.getString('sync.storage.error.checkFileSyncSettings') - ]); - break; - - case Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND: - var errorTitle = Zotero.getString('sync.storage.error.directoryNotFound'); - var parentSpec = spec.replace(/\/zotero\/$/, ""); - var errorMessage = Zotero.getString('sync.storage.error.doesNotExist', parentSpec); - break; - - case Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND: - var create = promptService.confirmEx( - window, - Zotero.getString('sync.storage.error.directoryNotFound'), - Zotero.getString('sync.storage.error.doesNotExist', spec) + "\n\n" - + Zotero.getString('sync.storage.error.createNow'), - promptService.BUTTON_POS_0 - * promptService.BUTTON_TITLE_IS_STRING - + promptService.BUTTON_POS_1 - * promptService.BUTTON_TITLE_CANCEL, - Zotero.getString('general.create'), - null, null, null, {} - ); - - if (create != 0) { + }, newHeaders); return; - } - createServerDirectory(function (uri, status) { - switch (status) { - case Zotero.Sync.Storage.SUCCESS: - if (!skipSuccessMessage) { - promptService.alert( - window, - Zotero.getString('sync.storage.serverConfigurationVerified'), - Zotero.getString('sync.storage.fileSyncSetUp') - ); - } - Zotero.Prefs.set("sync.storage.verified", true); - return true; - - case Zotero.Sync.Storage.ERROR_FORBIDDEN: - var errorTitle = Zotero.getString('general.permissionDenied'); - var errorMessage = Zotero.getString('sync.storage.error.permissionDeniedAtAddress') + "\n\n" - + spec + "\n\n" - + Zotero.getString('sync.storage.error.checkFileSyncSettings'); - break; - } + case 500: + callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR); + return; - // TEMP - if (!errorMessage) { - var errorMessage = status; - } - promptService.alert(window, errorTitle, errorMessage); - }); - - return false; - - case Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD: - // TODO: localize - var errorTitle = "WebDAV Server Configuration Error"; - var errorMessage = "Your WebDAV server must be configured to serve files without extensions " - + "and files with .prop extensions in order to work with Zotero."; - break; - - case Zotero.Sync.Storage.ERROR_SERVER_ERROR: - // TODO: localize - var errorTitle = "WebDAV Server Configuration Error"; - var errorMessage = "Your WebDAV server returned an internal error." - + "\n\n" + Zotero.getString('sync.storage.error.checkFileSyncSettings'); - break; - - case Zotero.Sync.Storage.ERROR_UNKNOWN: - var errorMessage = Zotero.localeJoin([ - Zotero.getString('general.unknownErrorOccurred'), - Zotero.getString('sync.storage.error.checkFileSyncSettings') - ]); - break; - } - - if (!skipSuccessMessage) { - if (!errorTitle) { - var errorTitle = Zotero.getString("general.error"); + default: + callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN); + return; } - // TEMP - if (!errorMessage) { - var errorMessage = status; - } - promptService.alert(window, errorTitle, errorMessage); - } - return false; - }, + }, headers); + }); + if (!request) { + callback(uri, Zotero.Sync.Storage.ERROR_OFFLINE); + } - /** - * Remove files on storage server that were deleted locally more than - * sync.storage.deleteDelayDays days ago - * - * @param {Function} callback Passed number of files deleted - */ - purgeDeletedStorageFiles: function (callback) { - if (!this.active) { - return; - } - - Zotero.debug("Purging deleted storage files"); - var files = Zotero.Sync.Storage.getDeletedFiles(); - if (!files) { - Zotero.debug("No files to delete remotely"); - if (callback) { - callback(); + requestHolder.request = request; + return requestHolder; + }; + + + obj._checkServerCallback = function (uri, status, window, skipSuccessMessage) { + var promptService = + Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + createInstance(Components.interfaces.nsIPromptService); + if (uri) { + var spec = uri.scheme + '://' + uri.hostPort + uri.path; + } + + switch (status) { + case Zotero.Sync.Storage.SUCCESS: + if (!skipSuccessMessage) { + promptService.alert( + window, + Zotero.getString('sync.storage.serverConfigurationVerified'), + Zotero.getString('sync.storage.fileSyncSetUp') + ); } - Zotero.Sync.Storage.EventManager.skip(); - return; - } + Zotero.Prefs.set("sync.storage.verified", true); + return true; - // Add .zip extension - var files = files.map(function (file) file + ".zip"); + case Zotero.Sync.Storage.ERROR_NO_URL: + var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL'); + break; - deleteStorageFiles(files, function (results) { - // Remove deleted and nonexistent files from storage delete log - var toPurge = results.deleted.concat(results.missing); - if (toPurge.length > 0) { - var done = 0; - var maxFiles = 999; - var numFiles = toPurge.length; - - Zotero.DB.beginTransaction(); - - do { - var chunk = toPurge.splice(0, maxFiles); - var sql = "DELETE FROM storageDeleteLog WHERE key IN (" - + chunk.map(function () '?').join() + ")"; - Zotero.DB.query(sql, chunk); - done += chunk.length; + case Zotero.Sync.Storage.ERROR_NO_PASSWORD: + var errorMessage = Zotero.getString('sync.error.enterPassword'); + break; + + case Zotero.Sync.Storage.ERROR_UNREACHABLE: + var errorMessage = Zotero.getString('sync.storage.error.serverCouldNotBeReached', uri.host); + break; + + case Zotero.Sync.Storage.ERROR_NOT_DAV: + var errorMessage = Zotero.getString('sync.storage.error.webdav.invalidURL', spec); + break; + + case Zotero.Sync.Storage.ERROR_AUTH_FAILED: + var errorTitle = Zotero.getString('general.permissionDenied'); + var errorMessage = Zotero.localeJoin([ + Zotero.getString('sync.storage.error.webdav.invalidLogin'), + Zotero.getString('sync.storage.error.checkFileSyncSettings') + ]); + break; + + case Zotero.Sync.Storage.ERROR_FORBIDDEN: + var errorTitle = Zotero.getString('general.permissionDenied'); + var errorMessage = Zotero.localeJoin([ + Zotero.getString('sync.storage.error.webdav.permissionDenied', uri.path), + Zotero.getString('sync.storage.error.checkFileSyncSettings') + ]); + break; + + case Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND: + var errorTitle = Zotero.getString('sync.storage.error.directoryNotFound'); + var parentSpec = spec.replace(/\/zotero\/$/, ""); + var errorMessage = Zotero.getString('sync.storage.error.doesNotExist', parentSpec); + break; + + case Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND: + var create = promptService.confirmEx( + window, + Zotero.getString('sync.storage.error.directoryNotFound'), + Zotero.getString('sync.storage.error.doesNotExist', spec) + "\n\n" + + Zotero.getString('sync.storage.error.createNow'), + promptService.BUTTON_POS_0 + * promptService.BUTTON_TITLE_IS_STRING + + promptService.BUTTON_POS_1 + * promptService.BUTTON_TITLE_CANCEL, + Zotero.getString('general.create'), + null, null, null, {} + ); + + if (create != 0) { + return; + } + + createServerDirectory(function (uri, status) { + switch (status) { + case Zotero.Sync.Storage.SUCCESS: + if (!skipSuccessMessage) { + promptService.alert( + window, + Zotero.getString('sync.storage.serverConfigurationVerified'), + Zotero.getString('sync.storage.fileSyncSetUp') + ); + } + Zotero.Prefs.set("sync.storage.verified", true); + return true; + + case Zotero.Sync.Storage.ERROR_FORBIDDEN: + var errorTitle = Zotero.getString('general.permissionDenied'); + var errorMessage = Zotero.getString('sync.storage.error.permissionDeniedAtAddress') + "\n\n" + + spec + "\n\n" + + Zotero.getString('sync.storage.error.checkFileSyncSettings'); + break; } - while (done < numFiles); - Zotero.DB.commitTransaction(); + // TEMP + if (!errorMessage) { + var errorMessage = status; + } + promptService.alert(window, errorTitle, errorMessage); + }); + + return false; + + case Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD: + // TODO: localize + var errorTitle = "WebDAV Server Configuration Error"; + var errorMessage = "Your WebDAV server must be configured to serve files without extensions " + + "and files with .prop extensions in order to work with Zotero."; + break; + + case Zotero.Sync.Storage.ERROR_SERVER_ERROR: + // TODO: localize + var errorTitle = "WebDAV Server Configuration Error"; + var errorMessage = "Your WebDAV server returned an internal error." + + "\n\n" + Zotero.getString('sync.storage.error.checkFileSyncSettings'); + break; + + case Zotero.Sync.Storage.ERROR_UNKNOWN: + var errorMessage = Zotero.localeJoin([ + Zotero.getString('general.unknownErrorOccurred'), + Zotero.getString('sync.storage.error.checkFileSyncSettings') + ]); + break; + } + + if (!skipSuccessMessage) { + if (!errorTitle) { + var errorTitle = Zotero.getString("general.error"); + } + // TEMP + if (!errorMessage) { + var errorMessage = status; + } + promptService.alert(window, errorTitle, errorMessage); + } + return false; + }; + + + /** + * Remove files on storage server that were deleted locally more than + * sync.storage.deleteDelayDays days ago + * + * @param {Function} callback Passed number of files deleted + */ + obj._purgeDeletedStorageFiles = function (callback) { + if (!this._active) { + return; + } + + Zotero.debug("Purging deleted storage files"); + var files = Zotero.Sync.Storage.getDeletedFiles(); + if (!files) { + Zotero.debug("No files to delete remotely"); + if (callback) { + callback(); + } + Zotero.Sync.Storage.EventManager.skip(); + return; + } + + // Add .zip extension + var files = files.map(function (file) file + ".zip"); + + deleteStorageFiles(files, function (results) { + // Remove deleted and nonexistent files from storage delete log + var toPurge = results.deleted.concat(results.missing); + if (toPurge.length > 0) { + var done = 0; + var maxFiles = 999; + var numFiles = toPurge.length; + + Zotero.DB.beginTransaction(); + + do { + var chunk = toPurge.splice(0, maxFiles); + var sql = "DELETE FROM storageDeleteLog WHERE key IN (" + + chunk.map(function () '?').join() + ")"; + Zotero.DB.query(sql, chunk); + done += chunk.length; + } + while (done < numFiles); + + Zotero.DB.commitTransaction(); + } + + if (callback) { + callback(results.deleted.length); + } + + Zotero.Sync.Storage.EventManager.success(); + }); + }; + + + /** + * Delete orphaned storage files older than a day before last sync time + * + * @param {Function} callback + */ + obj._purgeOrphanedStorageFiles = function (callback) { + const daysBeforeSyncTime = 1; + + if (!this._active) { + Zotero.Sync.Storage.EventManager.skip(); + return; + } + + // If recently purged, skip + var lastpurge = Zotero.Prefs.get('lastWebDAVOrphanPurge'); + var days = 10; + if (lastpurge && new Date(lastpurge * 1000) > (new Date() - (1000 * 60 * 60 * 24 * days))) { + Zotero.Sync.Storage.EventManager.skip(); + return; + } + + Zotero.debug("Purging orphaned storage files"); + + var uri = this.rootURI; + var path = uri.path; + + var prolog = '<?xml version="1.0" encoding="utf-8" ?>\n'; + var D = new Namespace("D", "DAV:"); + var nsDeclarations = 'xmlns:' + D.prefix + '=' + '"' + D.uri + '"'; + + var requestXML = new XML('<D:propfind ' + nsDeclarations + '/>'); + requestXML.D::prop = ''; + requestXML.D::prop.D::getlastmodified = ''; + + var xmlstr = prolog + requestXML.toXMLString(); + + var lastSyncDate = new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000); + + Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { + Zotero.debug(req.responseText); + + var funcName = "Zotero.Sync.Storage.purgeOrphanedStorageFiles()"; + + // Strip XML declaration and convert to E4X + var xml = new XML(req.responseText.replace(/<\?xml.*\?>/, '')); + + var deleteFiles = []; + var trailingSlash = !!path.match(/\/$/); + for each(var response in xml.D::response) { + var href = response.D::href.toString(); + + // Strip trailing slash if there isn't one on the root path + if (!trailingSlash) { + href = href.replace(/\/$/, "") } + // Absolute + if (href.match(/^https?:\/\//)) { + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + var href = ios.newURI(href, null, null); + href = href.path; + } + + // Skip root URI + if (href == path + // Some Apache servers respond with a "/zotero" href + // even for a "/zotero/" request + || (trailingSlash && href + '/' == path) + // Try URL-encoded as well, as above + || decodeURIComponent(href) == path) { + continue; + } + + if (href.indexOf(path) == -1 + // Try URL-encoded as well, in case there's a '~' or similar + // character in the URL and the server (e.g., Sakai) is + // encoding the value + && decodeURIComponent(href).indexOf(path) == -1) { + Zotero.Sync.Storage.EventManager.error( + "DAV:href '" + href + "' does not begin with path '" + + path + "' in " + funcName + ); + } + + var matches = href.match(/[^\/]+$/); + if (!matches) { + Zotero.Sync.Storage.EventManager.error( + "Unexpected href '" + href + "' in " + funcName + ) + } + var file = matches[0]; + + if (file.indexOf('.') == 0) { + Zotero.debug("Skipping hidden file " + file); + continue; + } + if (!file.match(/\.zip$/) && !file.match(/\.prop$/)) { + Zotero.debug("Skipping file " + file); + continue; + } + + var key = file.replace(/\.(zip|prop)$/, ''); + var item = Zotero.Items.getByLibraryAndKey(null, key); + if (item) { + Zotero.debug("Skipping existing file " + file); + continue; + } + + Zotero.debug("Checking orphaned file " + file); + + // TODO: Parse HTTP date properly + var lastModified = response..*::getlastmodified.toString(); + lastModified = Zotero.Date.strToISO(lastModified); + lastModified = Zotero.Date.sqlToDate(lastModified); + + // Delete files older than a day before last sync time + var days = (lastSyncDate - lastModified) / 1000 / 60 / 60 / 24; + + if (days > daysBeforeSyncTime) { + deleteFiles.push(file); + } + } + + deleteStorageFiles(deleteFiles, function (results) { + Zotero.Prefs.set("lastWebDAVOrphanPurge", Math.round(new Date().getTime() / 1000)) if (callback) { - callback(results.deleted.length); + callback(results); } - Zotero.Sync.Storage.EventManager.success(); }); - }, - - - /** - * Delete orphaned storage files older than a day before last sync time - * - * @param {Function} callback - */ - purgeOrphanedStorageFiles: function (callback) { - const daysBeforeSyncTime = 1; - - if (!this.active) { - Zotero.Sync.Storage.EventManager.skip(); - return; - } - - // If recently purged, skip - var lastpurge = Zotero.Prefs.get('lastWebDAVOrphanPurge'); - var days = 10; - if (lastpurge && new Date(lastpurge * 1000) > (new Date() - (1000 * 60 * 60 * 24 * days))) { - Zotero.Sync.Storage.EventManager.skip(); - return; - } - - Zotero.debug("Purging orphaned storage files"); - - var uri = this.rootURI; - var path = uri.path; - - var prolog = '<?xml version="1.0" encoding="utf-8" ?>\n'; - var D = new Namespace("D", "DAV:"); - var nsDeclarations = 'xmlns:' + D.prefix + '=' + '"' + D.uri + '"'; - - var requestXML = new XML('<D:propfind ' + nsDeclarations + '/>'); - requestXML.D::prop = ''; - requestXML.D::prop.D::getlastmodified = ''; - - var xmlstr = prolog + requestXML.toXMLString(); - - var lastSyncDate = new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000); - - Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { - Zotero.debug(req.responseText); - - var funcName = "Zotero.Sync.Storage.purgeOrphanedStorageFiles()"; - - // Strip XML declaration and convert to E4X - var xml = new XML(req.responseText.replace(/<\?xml.*\?>/, '')); - - var deleteFiles = []; - var trailingSlash = !!path.match(/\/$/); - for each(var response in xml.D::response) { - var href = response.D::href.toString(); - - // Strip trailing slash if there isn't one on the root path - if (!trailingSlash) { - href = href.replace(/\/$/, "") - } - - // Absolute - if (href.match(/^https?:\/\//)) { - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - var href = ios.newURI(href, null, null); - href = href.path; - } - - // Skip root URI - if (href == path - // Some Apache servers respond with a "/zotero" href - // even for a "/zotero/" request - || (trailingSlash && href + '/' == path) - // Try URL-encoded as well, as above - || decodeURIComponent(href) == path) { - continue; - } - - if (href.indexOf(path) == -1 - // Try URL-encoded as well, in case there's a '~' or similar - // character in the URL and the server (e.g., Sakai) is - // encoding the value - && decodeURIComponent(href).indexOf(path) == -1) { - Zotero.Sync.Storage.EventManager.error( - "DAV:href '" + href + "' does not begin with path '" - + path + "' in " + funcName - ); - } - - var matches = href.match(/[^\/]+$/); - if (!matches) { - Zotero.Sync.Storage.EventManager.error( - "Unexpected href '" + href + "' in " + funcName - ) - } - var file = matches[0]; - - if (file.indexOf('.') == 0) { - Zotero.debug("Skipping hidden file " + file); - continue; - } - if (!file.match(/\.zip$/) && !file.match(/\.prop$/)) { - Zotero.debug("Skipping file " + file); - continue; - } - - var key = file.replace(/\.(zip|prop)$/, ''); - var item = Zotero.Items.getByLibraryAndKey(null, key); - if (item) { - Zotero.debug("Skipping existing file " + file); - continue; - } - - Zotero.debug("Checking orphaned file " + file); - - // TODO: Parse HTTP date properly - var lastModified = response..*::getlastmodified.toString(); - lastModified = Zotero.Date.strToISO(lastModified); - lastModified = Zotero.Date.sqlToDate(lastModified); - - // Delete files older than a day before last sync time - var days = (lastSyncDate - lastModified) / 1000 / 60 / 60 / 24; - - if (days > daysBeforeSyncTime) { - deleteFiles.push(file); - } - } - - deleteStorageFiles(deleteFiles, function (results) { - Zotero.Prefs.set("lastWebDAVOrphanPurge", Math.round(new Date().getTime() / 1000)) - if (callback) { - callback(results); - } - Zotero.Sync.Storage.EventManager.success(); - }); - }, { Depth: 1 }); - } + }, { Depth: 1 }); }; + + return obj; }()); diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js index e7c479535..64a3aeedf 100644 --- a/chrome/content/zotero/xpcom/storage/zfs.js +++ b/chrome/content/zotero/xpcom/storage/zfs.js @@ -24,7 +24,7 @@ */ -Zotero.Sync.Storage.Module.ZFS = (function () { +Zotero.Sync.Storage.ZFS = (function () { var _rootURI; var _userURI; var _cachedCredentials = false; @@ -40,7 +40,7 @@ Zotero.Sync.Storage.Module.ZFS = (function () { var uri = getItemInfoURI(item); Zotero.HTTP.doGet(uri, function (req) { - var funcName = "Zotero.Sync.Storage.Module.ZFS.getStorageFileInfo()"; + var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()"; if (req.status == 404) { callback(item, false); @@ -237,7 +237,7 @@ Zotero.Sync.Storage.Module.ZFS = (function () { } Zotero.HTTP.doPost(uri, body, function (req) { - var funcName = "Zotero.Sync.Storage.Module.ZFS.getFileUploadParameters()"; + var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()"; if (req.status == 413) { var retry = req.getResponseHeader('Retry-After'); @@ -597,7 +597,7 @@ Zotero.Sync.Storage.Module.ZFS = (function () { * @return {nsIURI} URI of file on storage server */ function getItemURI(item) { - var uri = Zotero.Sync.Storage.Module.ZFS.rootURI; + var uri = Zotero.Sync.Storage.ZFS.rootURI; // Be sure to mirror parameter changes to getItemInfoURI() below uri.spec += Zotero.URI.getItemPath(item) + '/file?auth=1&iskey=1&version=1'; return uri; @@ -612,7 +612,7 @@ Zotero.Sync.Storage.Module.ZFS = (function () { * @return {nsIURI} URI of file on storage server with info flag */ function getItemInfoURI(item) { - var uri = Zotero.Sync.Storage.Module.ZFS.rootURI; + var uri = Zotero.Sync.Storage.ZFS.rootURI; uri.spec += Zotero.URI.getItemPath(item) + '/file?auth=1&iskey=1&version=1&info=1'; return uri; } @@ -631,435 +631,445 @@ Zotero.Sync.Storage.Module.ZFS = (function () { } - return { - name: "ZFS", - - get includeUserFiles() { + // + // Public methods (called via Zotero.Sync.Storage.ZFS) + // + var obj = new Zotero.Sync.Storage.Mode; + obj.name = "ZFS"; + + Object.defineProperty(obj, "includeUserFiles", { + get: function () { return Zotero.Prefs.get("sync.storage.enabled") && Zotero.Prefs.get("sync.storage.protocol") == 'zotero'; - }, - - get includeGroupFiles() { + } + }); + + Object.defineProperty(obj, "includeGroupFiles", { + get: function () { return Zotero.Prefs.get("sync.storage.groups.enabled"); - }, - - get enabled() { - return this.includeUserFiles || this.includeGroupFiles; - }, - - get verified() { - return true; - }, - - get rootURI() { + } + }); + + Object.defineProperty(obj, "_enabled", { + get: function () this.includeUserFiles || this.includeGroupFiles + }); + + obj._verified = true; + + Object.defineProperty(obj, "rootURI", { + get: function () { if (!_rootURI) { throw ("Root URI not initialized in Zotero.Sync.Storage.ZFS.rootURI"); } return _rootURI.clone(); - }, - - get userURI() { + } + }); + + Object.defineProperty(obj, "userURI", { + get: function () { if (!_userURI) { throw ("User URI not initialized in Zotero.Sync.Storage.ZFS.userURI"); } return _userURI.clone(); - }, + } + }); + + + obj._init = function (url, username, password) { + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + try { + var uri = ios.newURI(url, null, null); + if (username) { + uri.username = username; + uri.password = password; + } + } + catch (e) { + Zotero.debug(e, 1); + Components.utils.reportError(e); + return false; + } + _rootURI = uri; + uri = uri.clone(); + uri.spec += 'users/' + Zotero.userID + '/'; + _userURI = uri; - init: function (url, username, password) { - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); + return true; + }; + + + obj._initFromPrefs = function () { + var url = ZOTERO_CONFIG.API_URL; + var username = Zotero.Sync.Server.username; + var password = Zotero.Sync.Server.password; + return this._init(url, username, password); + }; + + + /** + * Begin download process for individual file + * + * @param {Zotero.Sync.Storage.Request} [request] + */ + obj._downloadFile = function (request) { + var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); + if (!item) { + throw new Error("Item '" + request.name + "' not found"); + } + + // Retrieve file info from server to store locally afterwards + getStorageFileInfo(item, function (item, info) { + if (!request.isRunning()) { + Zotero.debug("Download request '" + request.name + + "' is no longer running after getting remote file info"); + return; + } + + if (!info) { + Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key); + request.finish(); + return; + } + try { - var uri = ios.newURI(url, null, null); - if (username) { - uri.username = username; - uri.password = password; - } - } - catch (e) { - Zotero.debug(e, 1); - Components.utils.reportError(e); - return false; - } - _rootURI = uri; - - uri = uri.clone(); - uri.spec += 'users/' + Zotero.userID + '/'; - _userURI = uri; - - return true; - }, - - - initFromPrefs: function () { - var url = ZOTERO_CONFIG.API_URL; - var username = Zotero.Sync.Server.username; - var password = Zotero.Sync.Server.password; - return this.init(url, username, password); - }, - - - /** - * Begin download process for individual file - * - * @param {Zotero.Sync.Storage.Request} [request] - */ - downloadFile: function (request) { - var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); - if (!item) { - throw new Error("Item '" + request.name + "' not found"); - } - - // Retrieve file info from server to store locally afterwards - getStorageFileInfo(item, function (item, info) { - if (!request.isRunning()) { - Zotero.debug("Download request '" + request.name - + "' is no longer running after getting remote file info"); - return; + var syncModTime = info.mtime; + var syncHash = info.hash; + + var file = item.getFile(); + // Skip download if local file exists and matches mod time + if (file && file.exists()) { + if (syncModTime == file.lastModifiedTime) { + Zotero.debug("File mod time matches remote file -- skipping download"); + + Zotero.DB.beginTransaction(); + var syncState = Zotero.Sync.Storage.getSyncState(item.id); + //var updateItem = syncState != 1; + var updateItem = false; + Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); + Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); + Zotero.DB.commitTransaction(); + Zotero.Sync.Storage.EventManager.changesMade(); + request.finish(); + return; + } + // If not compressed, check hash, in case only timestamp changed + else if (!info.compressed && item.attachmentHash == syncHash) { + Zotero.debug("File hash matches remote file -- skipping download"); + + Zotero.DB.beginTransaction(); + var syncState = Zotero.Sync.Storage.getSyncState(item.id); + //var updateItem = syncState != 1; + var updateItem = false; + if (!info.compressed) { + Zotero.Sync.Storage.setSyncedHash(item.id, syncHash, false); + } + Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); + Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); + Zotero.DB.commitTransaction(); + Zotero.Sync.Storage.EventManager.changesMade(); + request.finish(); + return; + } } - if (!info) { - Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key); - request.finish(); - return; + var destFile = Zotero.getTempDirectory(); + if (info.compressed) { + destFile.append(item.key + '.zip.tmp'); + } + else { + destFile.append(item.key + '.tmp'); } - try { - var syncModTime = info.mtime; - var syncHash = info.hash; - - var file = item.getFile(); - // Skip download if local file exists and matches mod time - if (file && file.exists()) { - if (syncModTime == file.lastModifiedTime) { - Zotero.debug("File mod time matches remote file -- skipping download"); - - Zotero.DB.beginTransaction(); - var syncState = Zotero.Sync.Storage.getSyncState(item.id); - //var updateItem = syncState != 1; - var updateItem = false; - Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); - Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); - Zotero.DB.commitTransaction(); - Zotero.Sync.Storage.EventManager.changesMade(); - request.finish(); - return; - } - // If not compressed, check hash, in case only timestamp changed - else if (!info.compressed && item.attachmentHash == syncHash) { - Zotero.debug("File hash matches remote file -- skipping download"); - - Zotero.DB.beginTransaction(); - var syncState = Zotero.Sync.Storage.getSyncState(item.id); - //var updateItem = syncState != 1; - var updateItem = false; - if (!info.compressed) { - Zotero.Sync.Storage.setSyncedHash(item.id, syncHash, false); - } - Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem); - Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); - Zotero.DB.commitTransaction(); - Zotero.Sync.Storage.EventManager.changesMade(); - request.finish(); - return; - } - } - - var destFile = Zotero.getTempDirectory(); - if (info.compressed) { - destFile.append(item.key + '.zip.tmp'); - } - else { - destFile.append(item.key + '.tmp'); - } - - if (destFile.exists()) { - try { - destFile.remove(false); - } - catch (e) { - Zotero.File.checkFileAccessError(e, destFile, 'delete'); - } - } - - // saveURI() below appears not to create empty files for Content-Length: 0, - // so we create one here just in case + if (destFile.exists()) { try { - destFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644); + destFile.remove(false); } catch (e) { - Zotero.File.checkFileAccessError(e, destFile, 'create'); + Zotero.File.checkFileAccessError(e, destFile, 'delete'); } - - var listener = new Zotero.Sync.Storage.StreamListener( - { - onStart: function (request, data) { - if (data.request.isFinished()) { - Zotero.debug("Download request " + data.request.name - + " stopped before download started -- closing channel"); - request.cancel(0x804b0002); // NS_BINDING_ABORTED - return; - } - }, - onProgress: function (a, b, c) { - request.onProgress(a, b, c) - }, - onStop: function (request, status, response, data) { - if (status != 200) { - var msg = "Unexpected status code " + status - + " for request " + data.request.name - + " in Zotero.Sync.Storage.Module.ZFS.downloadFile()"; - Zotero.debug(msg, 1); - Components.utils.reportError(msg); - Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError); - } - - // Don't try to process if the request has been cancelled - if (data.request.isFinished()) { - Zotero.debug("Download request " + data.request.name - + " is no longer running after file download", 2); - return; - } - - Zotero.debug("Finished download of " + destFile.path); - - try { - Zotero.Sync.Storage.processDownload(data); - data.request.finish(); - } - catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } - }, - request: request, - item: item, - compressed: info.compressed, - syncModTime: syncModTime, - syncHash: syncHash - } - ); - - var uri = getItemURI(item); - - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - Zotero.debug('Saving ' + disp.spec + ' with saveURI()'); - const nsIWBP = Components.interfaces.nsIWebBrowserPersist; - var wbp = Components - .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(nsIWBP); - wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE; - wbp.progressListener = listener; - wbp.saveURI(uri, null, null, null, null, destFile); + } + + // saveURI() below appears not to create empty files for Content-Length: 0, + // so we create one here just in case + try { + destFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644); } catch (e) { - Zotero.Sync.Storage.EventManager.error(e); - } - }); - }, - - - uploadFile: function (request) { - var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); - if (Zotero.Attachments.getNumFiles(item) > 1) { - Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); }); - } - else { - processUploadFile({ request: request }); - } - }, - - - getLastSyncTime: function (callback) { - var uri = this.userURI; - var successFileURI = uri.clone(); - successFileURI.spec += "laststoragesync?auth=1"; - - // Cache the credentials at the root - var self = this; - this.cacheCredentials(function () { - Zotero.HTTP.doGet(successFileURI, function (req) { - if (req.responseText) { - Zotero.debug(req.responseText); - } - Zotero.debug(req.status); - - if (req.status == 401 || req.status == 403) { - Zotero.debug("Clearing ZFS authentication credentials", 2); - _cachedCredentials = false; - } - - if (req.status != 200 && req.status != 404) { - Zotero.Sync.Storage.EventManager.error( - "Unexpected status code " + req.status + " getting " - + "last file sync time" - ); - } - - if (req.status == 200) { - var ts = req.responseText; - var date = new Date(ts * 1000); - Zotero.debug("Last successful storage sync was " + date); - _lastSyncTime = ts; - } - else { - var ts = null; - _lastSyncTime = null; - } - callback(ts); - }); - }); - }, - - - setLastSyncTime: function (callback, useLastSyncTime) { - if (useLastSyncTime) { - if (!_lastSyncTime) { - if (callback) { - callback(); - } - return; + Zotero.File.checkFileAccessError(e, destFile, 'create'); } - var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)"; - Zotero.DB.query(sql, { int: _lastSyncTime }); + var listener = new Zotero.Sync.Storage.StreamListener( + { + onStart: function (request, data) { + if (data.request.isFinished()) { + Zotero.debug("Download request " + data.request.name + + " stopped before download started -- closing channel"); + request.cancel(0x804b0002); // NS_BINDING_ABORTED + return; + } + }, + onProgress: function (a, b, c) { + request.onProgress(a, b, c) + }, + onStop: function (request, status, response, data) { + if (status != 200) { + var msg = "Unexpected status code " + status + + " for request " + data.request.name + + " in Zotero.Sync.Storage.ZFS.downloadFile()"; + Zotero.debug(msg, 1); + Components.utils.reportError(msg); + Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError); + } + + // Don't try to process if the request has been cancelled + if (data.request.isFinished()) { + Zotero.debug("Download request " + data.request.name + + " is no longer running after file download", 2); + return; + } + + Zotero.debug("Finished download of " + destFile.path); + + try { + Zotero.Sync.Storage.processDownload(data); + data.request.finish(); + } + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } + }, + request: request, + item: item, + compressed: info.compressed, + syncModTime: syncModTime, + syncHash: syncHash + } + ); - Zotero.debug("Clearing ZFS authentication credentials", 2); - _lastSyncTime = null; - _cachedCredentials = false; + var uri = getItemURI(item); - if (callback) { - callback(); + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; } - - return; + Zotero.debug('Saving ' + disp.spec + ' with saveURI()'); + const nsIWBP = Components.interfaces.nsIWebBrowserPersist; + var wbp = Components + .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE; + wbp.progressListener = listener; + wbp.saveURI(uri, null, null, null, null, destFile); } - _lastSyncTime = null; - - var uri = this.userURI; - var successFileURI = uri.clone(); - successFileURI.spec += "laststoragesync?auth=1"; - - Zotero.HTTP.doPost(successFileURI, "", function (req) { - Zotero.debug(req.responseText); + catch (e) { + Zotero.Sync.Storage.EventManager.error(e); + } + }); + }; + + + obj._uploadFile = function (request) { + var item = Zotero.Sync.Storage.getItemFromRequestName(request.name); + if (Zotero.Attachments.getNumFiles(item) > 1) { + Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); }); + } + else { + processUploadFile({ request: request }); + } + }; + + + obj._getLastSyncTime = function (callback) { + var uri = this.userURI; + var successFileURI = uri.clone(); + successFileURI.spec += "laststoragesync?auth=1"; + + // Cache the credentials at the root + var self = this; + this._cacheCredentials(function () { + Zotero.HTTP.doGet(successFileURI, function (req) { + if (req.responseText) { + Zotero.debug(req.responseText); + } Zotero.debug(req.status); - if (req.status != 200) { - var msg = "Unexpected status code " + req.status + " setting last file sync time"; - Zotero.debug(msg, 1); - Components.utils.reportError(msg); - Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError); + if (req.status == 401 || req.status == 403) { + Zotero.debug("Clearing ZFS authentication credentials", 2); + _cachedCredentials = false; } - var ts = req.responseText; - - var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)"; - Zotero.DB.query(sql, { int: ts }); - - Zotero.debug("Clearing ZFS authentication credentials", 2); - _cachedCredentials = false; - - if (callback) { - callback(); - } - }); - }, - - - cacheCredentials: function (callback) { - if (_cachedCredentials) { - Zotero.debug("Credentials are already cached"); - setTimeout(function () { - callback(); - }, 0); - return false; - } - - var uri = this.rootURI; - // TODO: move to root uri - uri.spec += "?auth=1"; - Zotero.HTTP.doGet(uri, function (req) { - if (req.status == 401) { - // TODO: localize - var msg = "File sync login failed\n\nCheck your username and password in the Sync pane of the Zotero preferences."; - Zotero.Sync.Storage.EventManager.error(msg); - } - else if (req.status != 200) { - var msg = "Unexpected status code " + req.status + " caching " - + "authentication credentials in Zotero.Sync.Storage.Module.ZFS.cacheCredentials()"; - Zotero.debug(msg, 1); - Components.utils.reportError(msg); - Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultErrorRestart); - } - Zotero.debug("Credentials are cached"); - _cachedCredentials = true; - callback(); - }); - return true; - }, - - - /** - * Remove all synced files from the server - */ - purgeDeletedStorageFiles: function (callback) { - // If we don't have a user id we've never synced and don't need to bother - if (!Zotero.userID) { - Zotero.Sync.Storage.EventManager.skip(); - return; - } - - var sql = "SELECT value FROM settings WHERE setting=? AND key=?"; - var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']); - if (!values) { - Zotero.Sync.Storage.EventManager.skip(); - return; - } - - Zotero.debug("Unlinking synced files on ZFS"); - - var uri = this.userURI; - uri.spec += "removestoragefiles?"; - // Unused - for each(var value in values) { - switch (value) { - case 'user': - uri.spec += "user=1&"; - break; - - case 'group': - uri.spec += "group=1&"; - break; - - default: - Zotero.Sync.Storage.EventManager.error( - "Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()" - ); - } - } - uri.spec = uri.spec.substr(0, uri.spec.length - 1); - - Zotero.HTTP.doPost(uri, "", function (xmlhttp) { - if (xmlhttp.status != 204) { - if (callback) { - callback(false); - } + if (req.status != 200 && req.status != 404) { Zotero.Sync.Storage.EventManager.error( - "Unexpected status code " + xmlhttp.status + " purging ZFS files" + "Unexpected status code " + req.status + " getting " + + "last file sync time" ); } - var sql = "DELETE FROM settings WHERE setting=? AND key=?"; - Zotero.DB.query(sql, ['storage', 'zfsPurge']); - - if (callback) { - callback(true); + if (req.status == 200) { + var ts = req.responseText; + var date = new Date(ts * 1000); + Zotero.debug("Last successful storage sync was " + date); + _lastSyncTime = ts; } - - Zotero.Sync.Storage.EventManager.success(); + else { + var ts = null; + _lastSyncTime = null; + } + callback(ts); }); + }); + }; + + + obj._setLastSyncTime = function (callback, useLastSyncTime) { + if (useLastSyncTime) { + if (!_lastSyncTime) { + if (callback) { + callback(); + } + return; + } + + var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)"; + Zotero.DB.query(sql, { int: _lastSyncTime }); + + Zotero.debug("Clearing ZFS authentication credentials", 2); + _lastSyncTime = null; + _cachedCredentials = false; + + if (callback) { + callback(); + } + + return; } - } + _lastSyncTime = null; + + var uri = this.userURI; + var successFileURI = uri.clone(); + successFileURI.spec += "laststoragesync?auth=1"; + + Zotero.HTTP.doPost(successFileURI, "", function (req) { + Zotero.debug(req.responseText); + Zotero.debug(req.status); + + if (req.status != 200) { + var msg = "Unexpected status code " + req.status + " setting last file sync time"; + Zotero.debug(msg, 1); + Components.utils.reportError(msg); + Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError); + } + + var ts = req.responseText; + + var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)"; + Zotero.DB.query(sql, { int: ts }); + + Zotero.debug("Clearing ZFS authentication credentials", 2); + _cachedCredentials = false; + + if (callback) { + callback(); + } + }); + }; + + + obj._cacheCredentials = function (callback) { + if (_cachedCredentials) { + Zotero.debug("Credentials are already cached"); + setTimeout(function () { + callback(); + }, 0); + return false; + } + + var uri = this.rootURI; + // TODO: move to root uri + uri.spec += "?auth=1"; + Zotero.HTTP.doGet(uri, function (req) { + if (req.status == 401) { + // TODO: localize + var msg = "File sync login failed\n\nCheck your username and password in the Sync pane of the Zotero preferences."; + Zotero.Sync.Storage.EventManager.error(msg); + } + else if (req.status != 200) { + var msg = "Unexpected status code " + req.status + " caching " + + "authentication credentials in Zotero.Sync.Storage.ZFS.cacheCredentials()"; + Zotero.debug(msg, 1); + Components.utils.reportError(msg); + Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultErrorRestart); + } + Zotero.debug("Credentials are cached"); + _cachedCredentials = true; + callback(); + }); + return true; + }; + + + /** + * Remove all synced files from the server + */ + obj._purgeDeletedStorageFiles = function (callback) { + // If we don't have a user id we've never synced and don't need to bother + if (!Zotero.userID) { + Zotero.Sync.Storage.EventManager.skip(); + return; + } + + var sql = "SELECT value FROM settings WHERE setting=? AND key=?"; + var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']); + if (!values) { + Zotero.Sync.Storage.EventManager.skip(); + return; + } + + Zotero.debug("Unlinking synced files on ZFS"); + + var uri = this.userURI; + uri.spec += "removestoragefiles?"; + // Unused + for each(var value in values) { + switch (value) { + case 'user': + uri.spec += "user=1&"; + break; + + case 'group': + uri.spec += "group=1&"; + break; + + default: + Zotero.Sync.Storage.EventManager.error( + "Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()" + ); + } + } + uri.spec = uri.spec.substr(0, uri.spec.length - 1); + + Zotero.HTTP.doPost(uri, "", function (xmlhttp) { + if (xmlhttp.status != 204) { + if (callback) { + callback(false); + } + Zotero.Sync.Storage.EventManager.error( + "Unexpected status code " + xmlhttp.status + " purging ZFS files" + ); + } + + var sql = "DELETE FROM settings WHERE setting=? AND key=?"; + Zotero.DB.query(sql, ['storage', 'zfsPurge']); + + if (callback) { + callback(true); + } + + Zotero.Sync.Storage.EventManager.success(); + }); + }; + + return obj; }()); diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js index a9a510af0..62babac68 100644 --- a/chrome/content/zotero/xpcom/sync.js +++ b/chrome/content/zotero/xpcom/sync.js @@ -421,7 +421,7 @@ Zotero.Sync.EventListener = new function () { var sql = "REPLACE INTO syncDeleteLog VALUES (?, ?, ?, ?)"; var syncStatement = Zotero.DB.getStatement(sql); - if (isItem && Zotero.Sync.Storage.isActive('WebDAV')) { + if (isItem && Zotero.Sync.Storage.WebDAV.active) { var storageEnabled = true; var sql = "INSERT INTO storageDeleteLog VALUES (?, ?, ?)"; var storageStatement = Zotero.DB.getStatement(sql); @@ -559,7 +559,7 @@ Zotero.Sync.Runner = new function () { Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles')); var zfsSync = function (skipSyncNeeded) { - Zotero.Sync.Storage.sync('ZFS', { + Zotero.Sync.Storage.ZFS.sync({ // ZFS success onSuccess: function () { setTimeout(function () { @@ -593,7 +593,7 @@ Zotero.Sync.Runner = new function () { }) }; - Zotero.Sync.Storage.sync('WebDAV', { + Zotero.Sync.Storage.WebDAV.sync({ // WebDAV success onSuccess: function () { zfsSync(true); diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 201dac23f..be8db2ec8 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1801,12 +1801,12 @@ const ZOTERO_CONFIG = { Zotero.Relations.purge(); if (!skipStoragePurge && Math.random() < 1/10) { - Zotero.Sync.Storage.purgeDeletedStorageFiles('ZFS'); - Zotero.Sync.Storage.purgeDeletedStorageFiles('WebDAV'); + Zotero.Sync.Storage.ZFS.purgeDeletedStorageFiles(); + Zotero.Sync.Storage.WebDAV.purgeDeletedStorageFiles(); } if (!skipStoragePurge) { - Zotero.Sync.Storage.purgeOrphanedStorageFiles('WebDAV'); + Zotero.Sync.Storage.WebDAV.purgeOrphanedStorageFiles(); } } diff --git a/components/zotero-service.js b/components/zotero-service.js index fa0dde08c..14cae9129 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -100,7 +100,7 @@ const xpcomFilesLocal = [ 'storage/queueManager', 'storage/queue', 'storage/request', - 'storage/module', + 'storage/mode', 'storage/zfs', 'storage/webdav', 'timeline',