diff --git a/chrome/content/zotero-platform/mac/overlay.css b/chrome/content/zotero-platform/mac/overlay.css
index e02cb1d70..495c12829 100644
--- a/chrome/content/zotero-platform/mac/overlay.css
+++ b/chrome/content/zotero-platform/mac/overlay.css
@@ -59,7 +59,7 @@
padding-top: 1px;
}
-#zotero-tb-sync-warning[error=true]
+#zotero-tb-sync-error[error=true]
{
margin-bottom: 2px;
}
diff --git a/chrome/content/zotero-platform/win/overlay.css b/chrome/content/zotero-platform/win/overlay.css
index 1313a941e..05dea2343 100644
--- a/chrome/content/zotero-platform/win/overlay.css
+++ b/chrome/content/zotero-platform/win/overlay.css
@@ -21,7 +21,7 @@
visibility: hidden;
}
-#zotero-tb-sync-warning {
+#zotero-tb-sync-error {
margin-right: 2px;
}
diff --git a/chrome/content/zotero/bindings/filesyncstatus.xml b/chrome/content/zotero/bindings/filesyncstatus.xml
new file mode 100644
index 000000000..1ff75a9b3
--- /dev/null
+++ b/chrome/content/zotero/bindings/filesyncstatus.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+ []
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
index 04cbb2022..59cb81ae7 100644
--- a/chrome/content/zotero/xpcom/collectionTreeView.js
+++ b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -240,7 +240,7 @@ Zotero.CollectionTreeView.prototype.reload = function()
*/
Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
{
- if ((!ids || ids.length == 0) && action != 'refresh') {
+ if ((!ids || ids.length == 0) && action != 'refresh' && action != 'redraw') {
return;
}
@@ -254,6 +254,11 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
return;
}
+ if (action == 'redraw') {
+ this._treebox.invalidate();
+ return;
+ }
+
this.selection.selectEventsSuppressed = true;
var savedSelection = this.saveSelection();
@@ -420,8 +425,12 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
{
var obj = this._getItemAtRow(row);
- if(column.id == "zotero-collections-name-column")
+ if (column.id == 'zotero-collections-name-column') {
return obj.getName();
+ }
+ else if (column.id == 'zotero-collections-sync-status-column') {
+ return "";
+ }
else
return "";
}
@@ -430,7 +439,41 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
var itemGroup = this._getItemAtRow(row);
var collectionType = itemGroup.type;
+
+ if (collectionType == 'group') {
+ collectionType = 'library';
+ }
+
+ // Show sync icons only in library rows
+ if (collectionType != 'library' && col.index != 0) {
+ return '';
+ }
+
switch (collectionType) {
+ case 'library':
+ if (col.id == 'zotero-collections-sync-status-column') {
+ if (itemGroup.isLibrary(true)) {
+ var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
+ var errors = Zotero.Sync.Runner.getErrors(libraryID);
+ if (errors) {
+ var e = Zotero.Sync.Runner.getPrimaryError(errors);
+ switch (e.status) {
+ case 'warning':
+ var image = 'error';
+ break;
+
+ default:
+ var image = 'exclamation';
+ break;
+ }
+
+ return 'chrome://zotero/skin/' + image + '.png';
+ }
+ }
+ return '';
+ }
+ break;
+
case 'trash':
if (this._trashNotEmpty[itemGroup.ref.libraryID ? itemGroup.ref.libraryID : 0]) {
collectionType += '-full';
@@ -446,7 +489,7 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
}
break;
- case 'group':
+
collectionType = 'library';
break;
diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js
index dbc71d302..72cd72cd2 100644
--- a/chrome/content/zotero/xpcom/data/dataObjects.js
+++ b/chrome/content/zotero/xpcom/data/dataObjects.js
@@ -112,7 +112,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
else {
sql += "libraryID";
- if (libraryID) {
+ if (libraryID && libraryID !== '0') {
sql += "=? ";
params.push(libraryID);
}
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
index adf9f27aa..8b868462e 100644
--- a/chrome/content/zotero/xpcom/data/item.js
+++ b/chrome/content/zotero/xpcom/data/item.js
@@ -674,7 +674,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
this._disabledCheck();
- //Zotero.debug("Setting field '" + field + "' to '" + value + "' (loadIn: " + (loadIn ? 'true' : 'false') + ")");
+ //Zotero.debug("Setting field '" + field + "' to '" + value + "' (loadIn: " + (loadIn ? 'true' : 'false') + ") for item " + this.id + " ");
if (!field) {
throw ("Field not specified in Item.setField()");
@@ -1609,6 +1609,7 @@ Zotero.Item.prototype.save = function() {
'libraryID',
'key'
];
+
for each(var field in updateFields) {
if (this._changedPrimaryData && this._changedPrimaryData[field]) {
sql += field + '=?, ';
@@ -3000,7 +3001,6 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
if (val === this.attachmentLinkMode) {
return;
}
-
if (!this._changedAttachmentData) {
this._changedAttachmentData = {};
}
diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js
index 81e13911d..e631f85bd 100644
--- a/chrome/content/zotero/xpcom/data/libraries.js
+++ b/chrome/content/zotero/xpcom/data/libraries.js
@@ -45,6 +45,10 @@ Zotero.Libraries = new function () {
this.getName = function (libraryID) {
+ if (!libraryID) {
+ return Zotero.getString('pane.collections.library');
+ }
+
var type = this.getType(libraryID);
switch (type) {
case 'group':
@@ -59,6 +63,9 @@ Zotero.Libraries = new function () {
this.getType = function (libraryID) {
+ if (libraryID === 0) {
+ return 'user';
+ }
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
if (!libraryType) {
diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js
index b70a16c29..4d13d7727 100644
--- a/chrome/content/zotero/xpcom/db.js
+++ b/chrome/content/zotero/xpcom/db.js
@@ -402,7 +402,8 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
}
else {
if (checkParams && numParams > 0) {
- throw ("No parameters provided for query containing placeholders");
+ throw ("No parameters provided for query containing placeholders "
+ + "[QUERY: " + sql + "]");
}
}
return statement;
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
index 1d257d806..99687ad83 100644
--- a/chrome/content/zotero/xpcom/itemTreeView.js
+++ b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -344,9 +344,29 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
var savedSelection = this.saveSelection();
var previousRow = false;
- // Redraw the tree (for tag color changes)
+ // Redraw the tree (for tag color and progress changes)
if (action == 'redraw') {
- this._treebox.invalidate();
+ // Redraw specific rows
+ if (type == 'item' && ids.length) {
+ // Redraw specific cells
+ if (extraData && extraData.column) {
+ var col = this._treebox.columns.getNamedColumn(
+ 'zotero-items-column-' + extraData.column
+ );
+ for each(var id in ids) {
+ this._treebox.invalidateCell(this._itemRowMap[id], col);
+ }
+ }
+ else {
+ for each(var id in ids) {
+ this._treebox.invalidateRow(this._itemRowMap[id]);
+ }
+ }
+ }
+ // Redraw the whole tree
+ else {
+ this._treebox.invalidate();
+ }
return;
}
@@ -849,6 +869,12 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
if (this._itemGroup.isTrash()) return false;
var treerow = this._getItemAtRow(row);
+
+ if ((!this.isContainer(row) || !this.isContainerOpen(row))
+ && Zotero.Sync.Storage.getItemDownloadImageNumber(treerow.ref)) {
+ return '';
+ }
+
if (treerow.level === 0) {
if (treerow.ref.isRegularItem()) {
switch (treerow.ref.getBestAttachmentState()) {
@@ -2746,7 +2772,8 @@ Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {
}
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
- var itemID = this._getItemAtRow(row).ref.id;
+ var treeRow = this._getItemAtRow(row);
+ var itemID = treeRow.ref.id;
// Set tag colors
//
@@ -2767,6 +2794,30 @@ Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("contextRow"));
}
+
+ // Mark hasAttachment column, which needs special image handling
+ if (col.id == 'zotero-items-column-hasAttachment') {
+ var aServ = Components.classes["@mozilla.org/atom-service;1"].
+ getService(Components.interfaces.nsIAtomService);
+ prop.AppendElement(aServ.getAtom("hasAttachment"));
+
+ // Don't show pie for open parent items, since we show it for the
+ // child item
+ if (this.isContainer(row) && this.isContainerOpen(row)) {
+ return;
+ }
+
+ var num = Zotero.Sync.Storage.getItemDownloadImageNumber(treeRow.ref);
+ //var num = Math.round(new Date().getTime() % 10000 / 10000 * 64);
+ if (num !== false) {
+ if (!aServ) {
+ var aServ = Components.classes["@mozilla.org/atom-service;1"].
+ getService(Components.interfaces.nsIAtomService);
+ }
+ prop.AppendElement(aServ.getAtom("pie"));
+ prop.AppendElement(aServ.getAtom("pie" + num));
+ }
+ }
}
Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js
index e57234e21..f4042aba2 100644
--- a/chrome/content/zotero/xpcom/storage.js
+++ b/chrome/content/zotero/xpcom/storage.js
@@ -36,6 +36,7 @@ Zotero.Sync.Storage = new function () {
this.SUCCESS = 1;
this.ERROR_NO_URL = -1;
+ this.ERROR_NO_USERNAME = -2;
this.ERROR_NO_PASSWORD = -3;
this.ERROR_OFFLINE = -4;
this.ERROR_UNREACHABLE = -5;
@@ -68,10 +69,9 @@ Zotero.Sync.Storage = new function () {
compressed: 0,
uncompressed: 0,
get ratio() {
- return Math.round(
- (Zotero.Sync.Storage.compressionTracker.uncompressed -
+ return (Zotero.Sync.Storage.compressionTracker.uncompressed -
Zotero.Sync.Storage.compressionTracker.compressed) /
- Zotero.Sync.Storage.compressionTracker.uncompressed * 100);
+ Zotero.Sync.Storage.compressionTracker.uncompressed;
}
}
@@ -80,134 +80,302 @@ Zotero.Sync.Storage = new function () {
//
var _syncInProgress;
var _updatesInProgress;
- var _changesMade;
- var _resyncOnFinish;
+ var _itemDownloadPercentages = {};
+
+
+ this.sync = function (libraries) {
+ if (libraries) {
+ Zotero.debug("Starting file sync for libraries " + libraries);
+ }
+ else {
+ Zotero.debug("Starting file sync");
+ }
+
+ var self = this;
+
+ var libraryModes = {};
+ var librarySyncTimes = {};
+
+ // Get personal library file sync mode
+ return Q.fcall(function () {
+ // TODO: Make sure modes are active
+
+ if (libraries && libraries.indexOf(0) == -1) {
+ return;
+ }
+
+ if (Zotero.Sync.Storage.ZFS.includeUserFiles) {
+ libraryModes[0] = Zotero.Sync.Storage.ZFS;
+ return;
+ }
+
+ if (Zotero.Sync.Storage.WebDAV.includeUserFiles) {
+ if (!Zotero.Sync.Storage.WebDAV.verified) {
+ Zotero.debug("WebDAV file sync is not active");
+
+ // Try to verify server now if it hasn't been
+ return mode.checkServerPromise()
+ .then(function () {
+ libraryModes[0] = Zotero.Sync.Storage.WebDAV;
+ });
+ }
+
+ libraryModes[0] = Zotero.Sync.Storage.WebDAV;
+ return;
+ }
+ })
+ .then(function () {
+ // Get group library file sync modes
+ if (Zotero.Sync.Storage.ZFS.includeGroupFiles) {
+ var groups = Zotero.Groups.getAll();
+ for each(var group in groups) {
+ if (libraries && libraries.indexOf(group.libraryID) == -1) {
+ continue;
+ }
+ // TODO: if library file syncing enabled
+ libraryModes[group.libraryID] = Zotero.Sync.Storage.ZFS;
+ }
+ }
+
+ // Cache auth credentials for each mode
+ var modes = [];
+ var promises = [];
+ for each(var mode in libraryModes) {
+ if (modes.indexOf(mode) == -1) {
+ modes.push(mode);
+ promises.push(mode.cacheCredentials());
+ }
+ }
+ return Q.allResolved(promises)
+ .then(function () {
+ var promises = [];
+ for (var libraryID in libraryModes) {
+ libraryID = parseInt(libraryID);
+ // Get the last sync time for each library
+ if (self.downloadOnSync(libraryID)) {
+ promises.push(Q.allResolved(
+ [libraryID, libraryModes[libraryID].getLastSyncTime(libraryID)]
+ ));
+ }
+ // If download-as-needed, we don't need the last sync time
+ else {
+ promises.push(Q.allResolved(
+ [libraryID, null]
+ ));
+ }
+ }
+ // 'promises' is an array of promises for arrays containing promises
+ // for a libraryID and the last sync time for that library
+ return Q.allResolved(promises);
+ });
+ })
+ .then(function (promises) {
+ if (!promises.length) {
+ Zotero.debug("No libraries are active for file sync");
+ return [];
+ }
+
+ promises.forEach(function (p) {
+ p = p.valueOf();
+ var libraryID = p[0].valueOf();
+ Zotero.debug(libraryID);
+ if (p[1].isFulfilled()) {
+ librarySyncTimes[libraryID] = p[1].valueOf();
+ }
+ else {
+ // TODO: error log of some sort
+ //librarySyncTimes[libraryID] = p[1].valueOf().exception;
+ Components.utils.reportError(p[1].valueOf().exception);
+ }
+ });
+
+ // Queue files to download and upload from each library
+ for (var libraryID in librarySyncTimes) {
+ var lastSyncTime = librarySyncTimes[libraryID];
+ libraryID = parseInt(libraryID);
+
+ self.checkForUpdatedFiles(null, libraryID);
+
+ var downloadAll = self.downloadOnSync(libraryID);
+
+ // Forced downloads happen even in on-demand mode
+ var sql = "SELECT COUNT(*) FROM items "
+ + "JOIN itemAttachments USING (itemID) "
+ + "WHERE libraryID=? AND syncState=?";
+ var downloadForced = !!Zotero.DB.valueQuery(
+ sql,
+ [
+ libraryID == 0 ? null : libraryID,
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
+ ]
+ );
+
+ // If we don't have any forced downloads, we can skip
+ // downloads if the last sync time hasn't changed
+ if (downloadAll && !downloadForced && lastSyncTime) {
+ var version = self.getStoredLastSyncTime(
+ libraryModes[libraryID], libraryID
+ );
+ if (version == lastSyncTime) {
+ Zotero.debug("Last " + libraryModes[libraryID].name
+ + " sync time hasn't changed for library "
+ + libraryID + " -- skipping file downloads");
+ downloadAll = false;
+ }
+ }
+
+ if (downloadAll || downloadForced) {
+ Zotero.debug(downloadAll);
+ for each(var itemID in _getFilesToDownload(libraryID, !downloadAll)) {
+ var item = Zotero.Items.get(itemID);
+ self.queueItem(item);
+ }
+ }
+
+ // Get files to upload
+ for each(var itemID in _getFilesToUpload(libraryID)) {
+ var item = Zotero.Items.get(itemID);
+ self.queueItem(item);
+ }
+ }
+
+ // TODO: change to start() for each library, with allResolved()
+ // for the whole set and all() for each library
+ return Zotero.Sync.Storage.QueueManager.start();
+ })
+ .then(function (promises) {
+ Zotero.debug('Queue manager is finished');
+
+ var changedLibraries = [];
+
+ var finalPromises = [];
+
+ promises.forEach(function (promise) {
+ var result = promise.valueOf();
+ if (promise.isFulfilled()) {
+ Zotero.debug("File " + result.type + " sync finished "
+ + "for library " + result.libraryID);
+ Zotero.debug(result);
+ if (result.localChanges) {
+ changedLibraries.push(result.libraryID);
+ }
+ finalPromises.push(Q.allResolved([
+ result.libraryID,
+ libraryModes[result.libraryID].setLastSyncTime(
+ result.libraryID,
+ result.remoteChanges
+ ? false : librarySyncTimes[result.libraryID]
+ )
+ ]));
+ }
+ else {
+ result = result.exception;
+ Zotero.debug("File " + result.type + " sync failed "
+ + "for library " + result.libraryID);
+
+ finalPromises.push([result.libraryID, promise]);
+ }
+ });
+
+ if (promises.length && !changedLibraries.length) {
+ Zotero.debug("No local changes made during file sync");
+ }
+
+ return Q.allResolved(finalPromises)
+ .then(function (promises) {
+ var results = {
+ changesMade: !!changedLibraries.length,
+ errors: []
+ };
+
+ promises.forEach(function (p) {
+ // If this is a promise, get an array
+ if (Q.isPromise(p)) {
+ p = p.valueOf();
+ }
+ var libraryID = p[0].valueOf();
+ if (p[1].isRejected()) {
+ var result = p[1].valueOf();
+ result = result.exception;
+ if (typeof result == 'string') {
+ result = new Error(result);
+ }
+ result.libraryID = libraryID;
+ results.errors.push(result);
+ }
+ });
+
+ return results;
+ });
+ });
+ }
//
// Public methods
//
- this.sync = function (modeName, observer) {
- var mode = getModeFromName(modeName);
-
- if (!observer) {
- throw new Error("Observer not provided");
+ this.queueItem = function (item, highPriority) {
+ if (item.libraryID) {
+ var library = item.libraryID;
+ var mode = Zotero.Sync.Storage.ZFS;
}
- registerDefaultObserver(modeName);
- Zotero.Sync.Storage.EventManager.registerObserver(observer, true, modeName);
-
- if (!mode.active) {
- if (!mode.enabled) {
- Zotero.debug(mode.name + " file sync is not enabled");
- Zotero.Sync.Storage.EventManager.skip();
- return;
- }
-
- Zotero.debug(mode.name + " file sync is not active");
-
- // Try to verify server now if it hasn't been
- 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 = mode.checkServerCallback(uri, status, lastWin, true);
- if (success) {
- Zotero.debug(mode.name + " file sync is successfully set up");
- Zotero.Sync.Storage.sync(mode.name);
+ else {
+ var library = 0;
+ var mode = Zotero.Sync.Storage.ZFS.includeUserFiles
+ ? Zotero.Sync.Storage.ZFS : Zotero.Sync.Storage.WebDAV;
+ }
+ switch (Zotero.Sync.Storage.getSyncState(item.id)) {
+ case this.SYNC_STATE_TO_DOWNLOAD:
+ case this.SYNC_STATE_FORCE_DOWNLOAD:
+ var queue = Zotero.Sync.Storage.QueueManager.get('download', library);
+ var callbacks = {
+ onStart: function (request) {
+ return mode.downloadFile(request);
}
- else {
- Zotero.debug(mode.name + " verification failed");
-
- var e = new Zotero.Error(
- Zotero.getString('sync.storage.error.verificationFailed', mode.name),
- 0,
- {
- dialogButtonText: Zotero.getString('sync.openSyncPreferences'),
- dialogButtonCallback: function () {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var lastWin = wm.getMostRecentWindow("navigator:browser");
- lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync');
- }
- }
- );
- Zotero.Sync.Storage.EventManager.error(e, true);
+ };
+ break;
+
+ case this.SYNC_STATE_TO_UPLOAD:
+ case this.SYNC_STATE_FORCE_UPLOAD:
+ var queue = Zotero.Sync.Storage.QueueManager.get('upload', library);
+ var callbacks = {
+ onStart: function (request) {
+ return mode.uploadFile(request);
}
- });
- }
+ };
+ break;
- return;
- }
-
- if (!mode.includeUserFiles && !mode.includeGroupFiles) {
- Zotero.debug("No libraries are enabled for " + mode.name + " syncing");
- Zotero.Sync.Storage.EventManager.skip();
- return;
- }
-
- if (_syncInProgress) {
- Zotero.Sync.Storage.EventManager.error(
- "File sync operation already in progress"
- );
- }
-
- Zotero.debug("Beginning " + mode.name + " file sync");
- _syncInProgress = true;
- _changesMade = false;
-
- try {
- Zotero.Sync.Storage.checkForUpdatedFiles(
- null,
- null,
- mode.includeUserFiles && Zotero.Sync.Storage.downloadOnSync(),
- mode.includeGroupFiles && Zotero.Sync.Storage.downloadOnSync('groups')
- );
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
-
- var self = this;
-
- 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(modeName);
- Zotero.Sync.Storage.EventManager.registerObserver(observer, true, modeName);
-
- var download = true;
-
- var sql = "SELECT COUNT(*) FROM itemAttachments WHERE syncState=?";
- 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_" + modeName + "'";
- var version = Zotero.DB.valueQuery(sql);
- if (version == lastSyncTime) {
- Zotero.debug("Last " + mode.name + " sync time hasn't changed -- skipping file download step");
- download = false;
- }
- }
-
- try {
- var activeDown = download ? _downloadFiles(mode) : false;
- var activeUp = _uploadFiles(mode);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
-
- if (!activeDown && !activeUp) {
- Zotero.Sync.Storage.EventManager.skip();
+ case false:
+ Zotero.debug("Sync state for item " + item.id + " not found", 2);
return;
- }
- });
- }
+ }
+
+ var request = new Zotero.Sync.Storage.Request(
+ (item.libraryID ? item.libraryID : 0) + '/' + item.key, callbacks
+ );
+ request.setMaxSize(Zotero.Attachments.getTotalFileSize(item));
+ queue.addRequest(request, highPriority);
+ };
+
+
+ this.getStoredLastSyncTime = function (mode, libraryID) {
+ var sql = "SELECT version FROM version WHERE schema=?";
+ return Zotero.DB.valueQuery(
+ sql, "storage_" + mode.name.toLowerCase() + "_" + libraryID
+ );
+ };
+
+
+ this.setStoredLastSyncTime = function (mode, libraryID, time) {
+ var sql = "REPLACE INTO version SET version=? WHERE schema=?";
+ Zotero.DB.query(
+ sql,
+ [
+ time,
+ "storage_" + mode.name.toLowerCase() + "_" + libraryID
+ ]
+ );
+ };
/**
@@ -233,10 +401,8 @@ Zotero.Sync.Storage = new function () {
break;
default:
- Zotero.Sync.Storage.EventManager.error(
- "Invalid sync state '" + syncState
- + "' in Zotero.Sync.Storage.setSyncState()"
- );
+ throw "Invalid sync state '" + syncState
+ + "' in Zotero.Sync.Storage.setSyncState()"
}
var sql = "UPDATE itemAttachments SET syncState=? WHERE itemID=?";
@@ -253,9 +419,8 @@ Zotero.Sync.Storage = new function () {
var sql = "SELECT storageModTime FROM itemAttachments WHERE itemID=?";
var mtime = Zotero.DB.valueQuery(sql, itemID);
if (mtime === false) {
- Zotero.Sync.Storage.EventManager.error(
- "Item " + itemID + " not found in Zotero.Sync.Storage.getSyncedModificationTime()"
- );
+ throw "Item " + itemID + " not found in "
+ + "Zotero.Sync.Storage.getSyncedModificationTime()";
}
return mtime;
}
@@ -298,9 +463,8 @@ Zotero.Sync.Storage = new function () {
var sql = "SELECT storageHash FROM itemAttachments WHERE itemID=?";
var hash = Zotero.DB.valueQuery(sql, itemID);
if (hash === false) {
- Zotero.Sync.Storage.EventManager.error(
- "Item " + itemID + " not found in Zotero.Sync.Storage.getSyncedHash()"
- );
+ throw "Item " + itemID + " not found in "
+ + "Zotero.Sync.Storage.getSyncedHash()";
}
return hash;
}
@@ -374,7 +538,7 @@ Zotero.Sync.Storage = new function () {
*/
this.downloadAsNeeded = function (libraryID) {
// Personal library
- if (libraryID == null) {
+ if (!libraryID) {
return Zotero.Prefs.get('sync.storage.downloadMode.personal') == 'on-demand';
}
// Group library (groupID or 'groups')
@@ -389,7 +553,7 @@ Zotero.Sync.Storage = new function () {
*/
this.downloadOnSync = function (libraryID) {
// Personal library
- if (libraryID == null) {
+ if (!libraryID) {
return Zotero.Prefs.get('sync.storage.downloadMode.personal') == 'on-sync';
}
// Group library (groupID or 'groups')
@@ -401,14 +565,10 @@ Zotero.Sync.Storage = new function () {
/**
- * Scans local files and marks any that have changed as 0 for uploading
- * and any that are missing as 1 for downloading
+ * Scans local files and marks any that have changed for uploading
+ * and any that are missing for downloading
*
- * Also marks missing files for downloading
- *
- * @param {Integer[]} [itemIDs] An optional set of item ids to check
- * @param {Object} [itemModTimes] Item mod times indexed by item ids
- * appearing in itemIDs; if set,
+ * @param {Object} [itemModTimes] Item mod times indexed by item ids;
* items with stored mod times
* that differ from the provided
* time but file mod times
@@ -419,30 +579,25 @@ Zotero.Sync.Storage = new function () {
* @return {Boolean} TRUE if any items changed state,
* FALSE otherwise
*/
- this.checkForUpdatedFiles = function (itemIDs, itemModTimes, includeUserFiles, includeGroupFiles) {
- Zotero.debug("Checking for locally changed attachment files");
- // check for current ops?
+ this.checkForUpdatedFiles = function (itemModTimes, libraryID) {
+ var msg = "Checking for locally changed attachment files";
- if (itemIDs) {
- if (includeUserFiles || includeGroupFiles) {
- throw new Error("includeUserFiles and includeGroupFiles are not allowed when itemIDs");
+ if (typeof libraryID != 'undefined') {
+ msg += " in library " + libraryID;
+ if (itemModTimes) {
+ throw new Error("libraryID is not allowed when itemIDs is set");
}
}
else {
- if (!includeUserFiles && !includeGroupFiles) {
+ if (!itemModTimes) {
return false;
}
}
-
- if (itemModTimes && !itemIDs) {
- throw new Error("itemModTimes can only be set if itemIDs is an array");
- }
+ Zotero.debug(msg);
var changed = false;
- if (!itemIDs) {
- itemIDs = [];
- }
+ var itemIDs = Object.keys(itemModTimes ? itemModTimes : {});
// Can only handle 999 bound parameters at a time
var numIDs = itemIDs.length;
@@ -457,18 +612,17 @@ Zotero.Sync.Storage = new function () {
var sql = "SELECT itemID, linkMode, path, storageModTime, storageHash, syncState "
+ "FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE linkMode IN (?,?) AND syncState IN (?,?)";
- if (includeUserFiles && !includeGroupFiles) {
- sql += " AND libraryID IS NULL";
- }
- else if (!includeUserFiles && includeGroupFiles) {
- sql += " AND libraryID IS NOT NULL";
- }
- var params = [
+ var params = [];
+ params.push(
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD,
Zotero.Sync.Storage.SYNC_STATE_IN_SYNC
- ];
+ );
+ if (typeof libraryID != 'undefined') {
+ sql += " AND libraryID=?";
+ params.push(libraryID == 0 ? null : libraryID);
+ }
if (chunk.length) {
sql += " AND itemID IN (" + chunk.map(function () '?').join() + ")";
params = params.concat(chunk);
@@ -481,13 +635,19 @@ Zotero.Sync.Storage = new function () {
}
while (done < numIDs);
- if (!rows) {
- Zotero.debug("No to-upload or in-sync files found");
+ // If no files, or everything is already marked for download,
+ // we don't need to do anything
+ if (!rows.length) {
+ var msg = "No in-sync or to-upload files found";
+ if (typeof libraryID != 'undefined') {
+ msg += " in library " + libraryID;
+ }
+ Zotero.debug(msg);
Zotero.DB.commitTransaction();
return changed;
}
- // Index data by item id
+ // Index attachment data by item id
var itemIDs = [];
var attachmentData = {};
for each(var row in rows) {
@@ -501,48 +661,51 @@ Zotero.Sync.Storage = new function () {
state: row.syncState
};
}
- if (itemIDs.length == 0) {
- Zotero.DB.commitTransaction();
- return changed;
- }
-
- rows = undefined;
+ rows = null;
var updatedStates = {};
var items = Zotero.Items.get(itemIDs);
for each(var item in items) {
+ var lk = libraryID + "/" + item.key;
+ Zotero.debug("Checking attachment file for item " + lk);
var file = item.getFile(attachmentData[item.id]);
if (!file) {
- Zotero.debug("Marking attachment " + item.id + " as missing");
+ Zotero.debug("Marking attachment " + lk + " as missing");
updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
continue;
}
+ // If file is already marked for upload, skip check. Even if this
+ // is download-marking mode (itemModTimes) and the file was
+ // changed remotely, conflicts are checked at upload time, so we
+ // don't need to worry about it here.
+ if (attachmentData[item.id].state == Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD) {
+ continue;
+ }
+
var fmtime = item.attachmentModificationTime;
- //Zotero.debug("Stored mtime is " + attachmentData[item.id].mtime);
- //Zotero.debug("File mtime is " + fmtime);
+ Zotero.debug("Stored mtime is " + attachmentData[item.id].mtime);
+ Zotero.debug("File mtime is " + fmtime);
// Download-marking mode
if (itemModTimes) {
- Zotero.debug("Item mod time is " + itemModTimes[item.id]);
+ Zotero.debug("Remote mod time for item " + lk + " is " + itemModTimes[item.id]);
- // Ignore attachments whose storage mod times haven't changed
+ // Ignore attachments whose stored mod times haven't changed
if (row.storageModTime == itemModTimes[id]) {
Zotero.debug("Storage mod time (" + row.storageModTime + ") "
- + "hasn't changed for attachment " + id);
+ + "hasn't changed for item " + lk);
continue;
}
- Zotero.debug("Marking attachment " + item.id + " for download");
+ Zotero.debug("Marking attachment " + lk + " for download");
updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
-
- continue;
}
var mtime = attachmentData[item.id].mtime;
- // If stored time matches file, it hasn't changed
+ // If stored time matches file, it hasn't changed locally
if (mtime == fmtime) {
continue;
}
@@ -550,8 +713,9 @@ Zotero.Sync.Storage = new function () {
// Allow floored timestamps for filesystems that don't support
// millisecond precision (e.g., HFS+)
if (Math.floor(mtime / 1000) * 1000 == fmtime || Math.floor(fmtime / 1000) * 1000 == mtime) {
- Zotero.debug("File mod times are within one-second precision (" + fmtime + " ≅ " + mtime + ") "
- + "for " + file.leafName + " -- ignoring");
+ Zotero.debug("File mod times are within one-second precision "
+ + "(" + fmtime + " ≅ " + mtime + ") for " + file.leafName
+ + " for item " + lk + " -- ignoring");
continue;
}
@@ -561,28 +725,25 @@ Zotero.Sync.Storage = new function () {
// And check with one-second precision as well
|| Math.abs(fmtime - Math.floor(mtime / 1000) * 1000) == 3600000
|| Math.abs(Math.floor(fmtime / 1000) * 1000 - mtime) == 3600000) {
- Zotero.debug("File mod time (" + fmtime + ") is exactly one hour off remote file (" + mtime + ") "
+ Zotero.debug("File mod time (" + fmtime + ") is exactly one "
+ + "hour off remote file (" + mtime + ") for item " + lk
+ "-- assuming time zone issue and skipping upload");
continue;
}
- // If file is already marked for upload, skip
- if (attachmentData[item.id].state == Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD) {
- continue;
- }
-
// If file hash matches stored hash, only the mod time changed, so skip
var f = item.getFile();
if (f) {
Zotero.debug(f.path);
}
else {
- Zotero.debug("File missing before getting hash");
+ Zotero.debug("File for item " + lk + " missing before getting hash");
}
var fileHash = item.attachmentHash;
if (attachmentData[item.id].hash && attachmentData[item.id].hash == fileHash) {
Zotero.debug("Mod time didn't match (" + fmtime + "!=" + mtime + ") "
- + "but hash did for " + file.leafName + " -- updating file mod time");
+ + "but hash did for " + file.leafName + " for item " + lk
+ + " -- updating file mod time");
try {
file.lastModifiedTime = attachmentData[item.id].mtime;
}
@@ -592,8 +753,9 @@ Zotero.Sync.Storage = new function () {
continue;
}
- Zotero.debug("Marking attachment " + item.id + " as changed ("
- + mtime + " != " + fmtime + ")");
+ // Mark file for upload
+ Zotero.debug("Marking attachment " + lk + " as changed "
+ + "(" + mtime + " != " + fmtime + ")");
updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD;
}
@@ -606,8 +768,6 @@ Zotero.Sync.Storage = new function () {
Zotero.debug("No synced files have changed locally");
}
- //throw ('foo');
-
Zotero.DB.commitTransaction();
return changed;
}
@@ -622,7 +782,8 @@ Zotero.Sync.Storage = new function () {
var itemID = item.id;
var mode = getModeFromLibrary(item.libraryID);
- if (!mode || !mode.active) {
+ // TODO: verify WebDAV on-demand?
+ if (!mode || !mode.verified) {
Zotero.debug("File syncing is not active for item's library -- skipping download");
return false;
}
@@ -635,75 +796,40 @@ Zotero.Sync.Storage = new function () {
Zotero.debug("File already exists -- replacing");
}
- var setup = function () {
- Zotero.Sync.Storage.EventManager.registerObserver({
- onSuccess: function () _syncInProgress = false,
-
- onSkip: function () _syncInProgress = false,
-
- onStop: function () _syncInProgress = false,
-
- onError: function (e) {
- Zotero.Sync.Runner.setSyncIcon('error', e);
- error(e);
- requestCallbacks.onStop();
- }
- }, false, "downloadFile");
-
- try {
- var queue = Zotero.Sync.Storage.QueueManager.get('download');
-
- var isRunning = queue.isRunning();
- if (!isRunning) {
- _syncInProgress = true;
-
- // Reset the sync icon
- Zotero.Sync.Runner.setSyncIcon();
- }
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
+ // TODO: start sync icon in cacheCredentials
+ return Q.fcall(function () {
+ return mode.cacheCredentials();
+ })
+ .then(function () {
+ // TODO: start sync icon
+ var library = item.libraryID;
+ if (!library) {
+ library = 0;
}
- return isRunning;
- };
-
- var run = function () {
- // We have to perform setup again at the same time that we add the
- // request, because otherwise a sync process could complete while
- // cacheCredentials() is running and clear the event handlers.
- var isRunning = setup();
+ var queue = Zotero.Sync.Storage.QueueManager.get(
+ 'download', library
+ );
- try {
- var queue = Zotero.Sync.Storage.QueueManager.get('download');
-
- if (!requestCallbacks) {
- requestCallbacks = {};
- }
- var onStart = function (request) {
- mode.downloadFile(request);
- };
- requestCallbacks.onStart = requestCallbacks.onStart
- ? [onStart, requestCallbacks.onStart]
- : onStart;
-
- var request = new Zotero.Sync.Storage.Request(
- item.libraryID + '/' + item.key, requestCallbacks
- );
-
- queue.addRequest(request, isRunning);
+ if (!requestCallbacks) {
+ requestCallbacks = {};
}
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
- };
-
- setup();
- mode.cacheCredentials(function () {
- run();
+ var onStart = function (request) {
+ return mode.downloadFile(request);
+ };
+ requestCallbacks.onStart = requestCallbacks.onStart
+ ? [onStart, requestCallbacks.onStart]
+ : onStart;
+
+ var request = new Zotero.Sync.Storage.Request(
+ library + '/' + item.key, requestCallbacks
+ );
+
+ queue.addRequest(request, true);
+ queue.start();
+
+ return request.promise;
});
-
- return true;
}
@@ -718,19 +844,19 @@ Zotero.Sync.Storage = new function () {
var funcName = "Zotero.Sync.Storage.processDownload()";
if (!data) {
- Zotero.Sync.Storage.EventManager.error("|data| not set in " + funcName);
+ throw "'data' not set in " + funcName;
}
if (!data.item) {
- Zotero.Sync.Storage.EventManager.error("|data.item| not set in " + funcName);
+ throw "'data.item' not set in " + funcName;
}
if (!data.syncModTime) {
- Zotero.Sync.Storage.EventManager.error("|data.syncModTime| not set in " + funcName);
+ throw "'data.syncModTime' not set in " + funcName;
}
if (!data.compressed && !data.syncHash) {
- Zotero.Sync.Storage.EventManager.error("|data.syncHash| is required if |data.compressed| is false in " + funcName);
+ throw "'data.syncHash' is required if 'data.compressed' is false in " + funcName;
}
var item = data.item;
@@ -750,13 +876,15 @@ Zotero.Sync.Storage = new function () {
// and mark for updated
var file = item.getFile();
if (newFile && file.leafName != newFile.leafName) {
+ // Bypass library access check
_updatesInProgress = true;
// If library isn't editable but filename was changed, update
// database without updating the item's mod time, which would result
// in a library access error
if (!Zotero.Items.editCheck(item)) {
- Zotero.debug("File renamed without library access -- updating itemAttachments path", 3);
+ Zotero.debug("File renamed without library access -- "
+ + "updating itemAttachments path", 3);
item.relinkAttachmentFile(newFile, true);
var useCurrentModTime = false;
}
@@ -779,16 +907,16 @@ Zotero.Sync.Storage = new function () {
// elsewhere but the renamed file wasn't synced, so the ZIP doesn't
// contain a file with the known name
var missingFile = item.getFile(null, true);
- Components.utils.reportError("File '" + missingFile.leafName + "' not found after processing download "
+ Components.utils.reportError("File '" + missingFile.leafName
+ + "' not found after processing download "
+ item.libraryID + "/" + item.key + " in " + funcName);
- return;
+ return false;
}
Zotero.DB.beginTransaction();
- var syncState = Zotero.Sync.Storage.getSyncState(item.id);
-
- var updateItem = syncState != 1;
+ //var syncState = Zotero.Sync.Storage.getSyncState(item.id);
+ //var updateItem = syncState != this.SYNC_STATE_TO_DOWNLOAD;
var updateItem = false;
try {
@@ -798,7 +926,7 @@ Zotero.Sync.Storage = new function () {
// Reset hash and sync state
Zotero.Sync.Storage.setSyncedHash(item.id, null);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD);
- _resyncOnFinish = true;
+ this.queueItem(item);
}
else {
file.lastModifiedTime = syncModTime;
@@ -816,29 +944,89 @@ Zotero.Sync.Storage = new function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
Zotero.DB.commitTransaction();
- _changesMade = true;
+
+ return true;
}
- this.checkServer = function (modeName, callback) {
- Zotero.Sync.Storage.EventManager.registerObserver({
- onSuccess: function () {},
- onError: function (e) {
- Zotero.debug(e, 1);
- callback(null, null, function () {
- // If there's an error, just display that
- Zotero.Utilities.Internal.errorPrompt(Zotero.getString('general.error'), e);
- });
- return true;
+ this.checkServerPromise = function (mode) {
+ var deferred = Q.defer();
+ 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 = mode.checkServerCallback(uri, status, lastWin, true);
+ if (success) {
+ Zotero.debug(mode.name + " file sync is successfully set up");
+ Q.resolve();
+ }
+ else {
+ Zotero.debug(mode.name + " verification failed");
+
+ var e = new Zotero.Error(
+ Zotero.getString('sync.storage.error.verificationFailed', mode.name),
+ 0,
+ {
+ dialogButtonText: Zotero.getString('sync.openSyncPreferences'),
+ dialogButtonCallback: function () {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var lastWin = wm.getMostRecentWindow("navigator:browser");
+ lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync');
+ }
+ }
+ );
+
+ Q.reject(e);
}
- }, false, "checkServer");
-
- var mode = getModeFromName(modeName);
- return mode.checkServer(function (uri, status) {
- callback(uri, status, function () {
- mode.checkServerCallback(uri, status);
- });
});
+ return deferred.promise;
+ }
+
+
+ this.getItemDownloadImageNumber = function (item) {
+ var numImages = 64;
+
+ var lk = (item.libraryID ? item.libraryID : 0) + "/" + item.key;
+
+ if (typeof _itemDownloadPercentages[lk] == 'undefined') {
+ return false;
+ }
+
+ var percentage = _itemDownloadPercentages[lk];
+ return Math.round(percentage / 100 * (numImages - 1)) + 1;
+ }
+
+
+ this.setItemDownloadPercentage = function (libraryKey, percentage) {
+ Zotero.debug("Setting image download percentage to " + percentage
+ + " for item " + libraryKey);
+
+ if (percentage !== false) {
+ _itemDownloadPercentages[libraryKey] = percentage;
+ }
+ else {
+ delete _itemDownloadPercentages[libraryKey];
+ }
+
+ var libraryID, key;
+ [libraryID, key] = libraryKey.split("/");
+ var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
+ Zotero.Notifier.trigger('redraw', 'item', item.id, { column: "hasAttachment" });
+
+ var parent = item.getSource();
+ if (parent) {
+ var parentItem = Zotero.Items.get(parent);
+ var parentLibraryKey = libraryID + "/" + parentItem.key;
+ if (percentage !== false) {
+ _itemDownloadPercentages[parentLibraryKey] = percentage;
+ }
+ else {
+ delete _itemDownloadPercentages[parentLibraryKey];
+ }
+ Zotero.Notifier.trigger('redraw', 'item', parentItem.id, { column: "hasAttachment" });
+ }
}
@@ -880,9 +1068,6 @@ Zotero.Sync.Storage = new function () {
this.getItemFromRequestName = function (name) {
var [libraryID, key] = name.split('/');
- if (libraryID == "null") {
- libraryID = null;
- }
return Zotero.Items.getByLibraryAndKey(libraryID, key);
}
@@ -890,137 +1075,29 @@ Zotero.Sync.Storage = new function () {
//
// Private methods
//
- function getModeFromName(modeName) {
- return Zotero.Sync.Storage[modeName];
- }
-
-
function getModeFromLibrary(libraryID) {
if (libraryID === undefined) {
throw new Error("libraryID not provided");
}
// Personal library
- if (libraryID === null) {
- if (!Zotero.Prefs.get('sync.storage.enabled')) {
- Zotero.debug('disabled');
- return false;
+ if (!libraryID) {
+ if (Zotero.Sync.Storage.ZFS.includeUserFiles) {
+ return Zotero.Sync.Storage.ZFS;
}
-
- var protocol = Zotero.Prefs.get('sync.storage.protocol');
- switch (protocol) {
- case 'zotero':
- return getModeFromName('ZFS');
-
- case 'webdav':
- return getModeFromName('WebDAV');
-
- default:
- throw new Error("Invalid storage protocol '" + protocol + "'");
+ if (Zotero.Sync.Storage.WebDAV.includeUserFiles) {
+ return Zotero.Sync.Storage.WebDAV;
}
+ return false;
}
// Group library
else {
- if (!Zotero.Prefs.get('sync.storage.groups.enabled')) {
- return false;
+ if (Zotero.Sync.Storage.ZFS.includeGroupFiles) {
+ return Zotero.Sync.Storage.ZFS;
}
-
- return getModeFromName('ZFS');
- }
- }
-
-
- /**
- * Starts download of all attachments marked for download
- *
- * @return {Boolean}
- */
- function _downloadFiles(mode) {
- if (!_syncInProgress) {
- _syncInProgress = true;
- }
-
- 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");
return false;
}
-
- var downloadFileIDs = _getFilesToDownload(includeUserFiles, includeGroupFiles);
- if (!downloadFileIDs) {
- Zotero.debug("No files to download");
- return false;
- }
-
- // Check for active operations?
-
- var queue = Zotero.Sync.Storage.QueueManager.get('download');
-
- for each(var itemID in downloadFileIDs) {
- var item = Zotero.Items.get(itemID);
- if (Zotero.Sync.Storage.getSyncState(itemID) !=
- Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
- && Zotero.Sync.Storage.isFileModified(itemID)) {
- Zotero.debug("File for attachment " + itemID + " has been modified");
- Zotero.Sync.Storage.setSyncState(itemID, this.SYNC_STATE_TO_UPLOAD);
- continue;
- }
-
- var request = new Zotero.Sync.Storage.Request(
- item.libraryID + '/' + item.key,
- {
- onStart: function (request) {
- mode.downloadFile(request);
- }
- }
- );
- queue.addRequest(request);
- }
-
- return true;
- }
-
-
- /**
- * Start upload of all attachments marked for upload
- *
- * @return {Boolean}
- */
- function _uploadFiles(mode) {
- if (!_syncInProgress) {
- _syncInProgress = true;
- }
-
- var uploadFileIDs = _getFilesToUpload(mode.includeUserFiles, mode.includeGroupFiles);
- if (!uploadFileIDs) {
- Zotero.debug("No files to upload");
- return false;
- }
-
- // Check for active operations?
- var queue = Zotero.Sync.Storage.QueueManager.get('upload');
-
- Zotero.debug(uploadFileIDs.length + " file(s) to upload");
-
- for each(var itemID in uploadFileIDs) {
- var item = Zotero.Items.get(itemID);
-
- var request = new Zotero.Sync.Storage.Request(
- item.libraryID + '/' + item.key,
- {
- onStart: function (request) {
- mode.uploadFile(request);
- }
- }
- );
- request.progressMax = Zotero.Attachments.getTotalFileSize(item, true);
- queue.addRequest(request);
- }
-
- return true;
}
@@ -1520,7 +1597,7 @@ Zotero.Sync.Storage = new function () {
if (fileList.length == 0) {
Zotero.debug('No files to add -- removing zip file');
tmpFile.remove(null);
- request.finish();
+ request.stop();
return false;
}
@@ -1533,6 +1610,7 @@ Zotero.Sync.Storage = new function () {
return true;
}
catch (e) {
+ Zotero.debug(e, 1);
request.error(e);
return false;
}
@@ -1572,35 +1650,30 @@ Zotero.Sync.Storage = new function () {
/**
- * Get files marked as ready to upload
+ * Get files marked as ready to download
*
* @inner
* @return {Number[]} Array of attachment itemIDs
*/
- function _getFilesToDownload(includeUserFiles, includeGroupFiles) {
- if (!includeUserFiles && !includeGroupFiles) {
- Zotero.Sync.Storage.EventManager.error(
- "At least one of includeUserFiles or includeGroupFiles must be set "
- + "in Zotero.Sync.Storage._getFilesToDownload()"
- );
- }
-
+ function _getFilesToDownload(libraryID, forcedOnly) {
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
- + "WHERE syncState IN (?,?) "
- // Skip attachments with empty path, which can't be saved
- + "AND path!=''";
- if (includeUserFiles && !includeGroupFiles) {
- sql += " AND libraryID IS NULL";
+ + "WHERE libraryID=? AND syncState IN (?";
+ var params = [
+ libraryID == 0 ? null : libraryID,
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
+ ];
+ if (!forcedOnly) {
+ sql += ",?";
+ params.push(Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD);
}
- else if (!includeUserFiles && includeGroupFiles) {
- sql += " AND libraryID IS NOT NULL";
+ sql += ") "
+ // Skip attachments with empty path, which can't be saved
+ + "AND path!=''";
+ var itemIDs = Zotero.DB.columnQuery(sql, params);
+ if (!itemIDs) {
+ return [];
}
- return Zotero.DB.columnQuery(sql,
- [
- Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD,
- Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
- ]
- );
+ return itemIDs;
}
@@ -1610,30 +1683,27 @@ Zotero.Sync.Storage = new function () {
* @inner
* @return {Number[]} Array of attachment itemIDs
*/
- function _getFilesToUpload(includeUserFiles, includeGroupFiles) {
- if (!includeUserFiles && !includeGroupFiles) {
- Zotero.Sync.Storage.EventManager.error(
- "At least one of includeUserFiles or includeGroupFiles must be set "
- + "in Zotero.Sync.Storage._getFilesToUpload()"
- );
- }
-
+ function _getFilesToUpload(libraryID) {
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE syncState IN (?,?) AND linkMode IN (?,?)";
- if (includeUserFiles && !includeGroupFiles) {
- sql += " AND libraryID IS NULL";
+ var params = [
+ Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD,
+ Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD,
+ Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
+ Zotero.Attachments.LINK_MODE_IMPORTED_URL
+ ];
+ if (typeof libraryID != 'undefined') {
+ sql += " AND libraryID=?";
+ params.push(libraryID == 0 ? null : libraryID);
}
- else if (!includeUserFiles && includeGroupFiles) {
- sql += " AND libraryID IS NOT NULL";
+ else {
+ throw new Error("libraryID not specified");
}
- return Zotero.DB.columnQuery(sql,
- [
- Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD,
- Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD,
- Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
- Zotero.Attachments.LINK_MODE_IMPORTED_URL
- ]
- );
+ var itemIDs = Zotero.DB.columnQuery(sql, params);
+ if (!itemIDs) {
+ return [];
+ }
+ return itemIDs;
}
@@ -1656,63 +1726,6 @@ Zotero.Sync.Storage = new function () {
}
- function registerDefaultObserver(modeName) {
- var finish = function (cancelled, skipSuccessFile) {
- // Upload success file when done
- if (!_resyncOnFinish && !skipSuccessFile) {
- // If we finished successfully and didn't upload any files, save the
- // last sync time locally rather than setting a new one on the server,
- // since we don't want other clients to check for new files
- var uploadQueue = Zotero.Sync.Storage.QueueManager.get('upload', true);
- var useLastSyncTime = !uploadQueue || (!cancelled && uploadQueue.lastTotalRequests == 0);
-
- getModeFromName(modeName).setLastSyncTime(function () {
- finish(cancelled, true);
- }, useLastSyncTime);
- return false;
- }
-
- Zotero.debug(modeName + " sync is complete");
-
- _syncInProgress = false;
-
- if (_resyncOnFinish) {
- Zotero.debug("Force-resyncing items in conflict");
- _resyncOnFinish = false;
- Zotero.Sync.Storage.sync(modeName);
- return false;
- }
-
- if (cancelled) {
- Zotero.Sync.Storage.EventManager.stop();
- }
- else if (!_changesMade) {
- Zotero.debug("No changes made during storage sync");
- Zotero.Sync.Storage.EventManager.skip();
- }
- else {
- Zotero.Sync.Storage.EventManager.success();
- }
-
- return true;
- };
-
- Zotero.Sync.Storage.EventManager.registerObserver({
- onSuccess: function () finish(),
-
- onSkip: function () {
- _syncInProgress = false
- },
-
- onStop: function () finish(true),
-
- onError: function (e) error(e),
-
- onChangesMade: function () _changesMade = true
- }, false, "default");
- }
-
-
function error(e) {
if (_syncInProgress) {
Zotero.Sync.Storage.QueueManager.cancel(true);
@@ -1808,7 +1821,7 @@ Zotero.Sync.Storage.ZipWriterObserver.prototype = {
Zotero.Sync.Storage.compressionTracker.compressed += this._zipWriter.file.fileSize;
Zotero.Sync.Storage.compressionTracker.uncompressed += originalSize;
Zotero.debug("Average compression so far: "
- + Zotero.Sync.Storage.compressionTracker.ratio + "%");
+ + Math.round(Zotero.Sync.Storage.compressionTracker.ratio * 100) + "%");
this._callback(this._data);
}
diff --git a/chrome/content/zotero/xpcom/storage/eventLog.js b/chrome/content/zotero/xpcom/storage/eventLog.js
new file mode 100644
index 000000000..05be22b7c
--- /dev/null
+++ b/chrome/content/zotero/xpcom/storage/eventLog.js
@@ -0,0 +1,94 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2012 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 .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Sync.Storage.EventLog = (function () {
+ // Non-library-specific
+ var _general = { warnings: [], errors: [] };
+ // Library-specific
+ var _warnings = {};
+ var _errors = {};
+
+ function call(type, data, libraryID) {
+ if (libraryID) {
+ switch (type) {
+ case 'warning':
+ var target = _general.warnings;
+ break;
+
+ case 'error':
+ var target = _general.errors;
+ break;
+ }
+ }
+ else {
+ switch (type) {
+ case 'warning':
+ var target = _warnings;
+ break;
+
+ case 'error':
+ var target = _errors;
+ break;
+ }
+ }
+
+ if (!target[libraryID]) {
+ target[libraryID] = [];
+ }
+
+ target[libraryID].push(data);
+
+ Zotero.debug(data, type == 'error' ? 1 : 2);
+ Components.utils.reportError(new Error(data));
+ }
+
+ return {
+ error: function (e, libraryID) call('error', e, libraryID),
+ warning: function (e, libraryID) call('warning', e, libraryID),
+
+ clear: function (libraryID) {
+ var queues = Zotero.Sync.Storage.QueueManager.getAll();
+ for each(var queue in queues) {
+ if (queue.isRunning()) {
+ Zotero.debug(queue.name[0].toUpperCase() + queue.name.substr(1)
+ + " queue not empty -- not clearing storage sync event observers");
+ return;
+ }
+ }
+
+ if (typeof libraryID == 'undefined') {
+ Zotero.debug("Clearing file sync event log");
+ _general = { warnings: [], errors: [] };
+ _warnings = {};
+ _errors = {};
+ }
+ else {
+ Zotero.debug("Clearing file sync event log for library " + libraryID);
+ _warnings[libraryID] = [];
+ _errors[libraryID] = [];
+ }
+ }
+ };
+}());
diff --git a/chrome/content/zotero/xpcom/storage/eventManager.js b/chrome/content/zotero/xpcom/storage/eventManager.js
deleted file mode 100644
index a632579c5..000000000
--- a/chrome/content/zotero/xpcom/storage/eventManager.js
+++ /dev/null
@@ -1,143 +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 .
-
- ***** END LICENSE BLOCK *****
-*/
-
-Zotero.Sync.Storage.EventManager = (function () {
- var _observers = [];
-
- function call(handler, data, clear) {
- Zotero.debug("Calling storage sync " + handler + " handlers");
-
- var observers = _observers;
- var cont = true;
- var handled = false;
-
- if (clear) {
- Zotero.Sync.Storage.EventManager.clear();
- }
-
- // Process most recently assigned observers first
- for (var i = observers.length - 1; i >= 0; i--) {
- let observer = observers[i].observer;
- let j = i;
- if (observer[handler]) {
- handled = true;
- if (observers[i].async) {
- setTimeout(function () {
- Zotero.debug("Calling " + handler + " handler " + j);
- var cont = observer[handler](data);
- if (cont === false) {
- throw new Error("Cannot cancel events from async observer");
- }
- }, 0);
- }
- else {
- Zotero.debug("Calling " + handler + " handler " + j);
- var cont = observer[handler](data);
- // If handler returns explicit false, cancel further events
- if (cont === false) {
- break;
- }
- }
- }
- }
-
- if (!handled && data) {
- var msg = "Unhandled storage sync event: " + data;
- Zotero.debug(msg, 1);
- if (handler == 'onError') {
- throw new Error(msg);
- }
- else {
- Components.utils.reportError(msg);
- }
- }
-
- // Throw errors to stop execution
- if (handler == 'onError') {
- if (!data) {
- throw new Error("Data not provided for error");
- }
-
- if (cont !== false) {
- throw (data);
- }
- }
- }
-
- return {
- registerObserver: function (observer, async, id) {
- var pos = -1;
-
- if (id) {
- for (var i = 0, len = _observers.length; i < len; i++) {
- var o = _observers[i];
- if (o.id === id && o.async == async) {
- pos = o;
- break;
- }
- }
- }
-
- if (pos == -1) {
- Zotero.debug("Registering storage sync event observer '" + id + "'");
- _observers.push({
- observer: observer,
- async: !!async,
- id: id
- });
- }
- else {
- Zotero.debug("Replacing storage sync event observer '" + id + "'");
- _observers[pos] = {
- observer: observer,
- async: !!async,
- id: id
- };
- }
- },
-
- success: function () call('onSuccess', false, true),
- skip: function (clear) call('onSkip', false, true),
- stop: function () call('onStop', false, true),
- error: function (e) call('onError', e, true),
-
- warning: function (e) call('onWarning', e),
- changesMade: function () call('onChangesMade'),
-
- clear: function () {
- var queues = Zotero.Sync.Storage.QueueManager.getAll();
- for each(var queue in queues) {
- if (queue.isRunning()) {
- Zotero.debug(queue.name[0].toUpperCase() + queue.name.substr(1)
- + " queue not empty -- not clearing storage sync event observers");
- return;
- }
- }
-
- Zotero.debug("Clearing storage sync event observers");
- _observers = [];
- }
- };
-}());
diff --git a/chrome/content/zotero/xpcom/storage/mode.js b/chrome/content/zotero/xpcom/storage/mode.js
index b289600b4..303c517e7 100644
--- a/chrome/content/zotero/xpcom/storage/mode.js
+++ b/chrome/content/zotero/xpcom/storage/mode.js
@@ -26,159 +26,62 @@
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);
- }
+ return this._verified;
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('username', function () {
- try {
- return this._username;
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._username;
});
Zotero.Sync.Storage.Mode.prototype.__defineGetter__('password', function () {
- try {
- return this._password;
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._password;
});
Zotero.Sync.Storage.Mode.prototype.__defineSetter__('password', function (val) {
- try {
- this._password = val;
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ this._password = val;
});
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);
- }
+ return this._init();
}
Zotero.Sync.Storage.Mode.prototype.sync = function (observer) {
- Zotero.Sync.Storage.sync(this.name, observer);
+ return 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);
- }
+ return this._downloadFile(request);
}
Zotero.Sync.Storage.Mode.prototype.uploadFile = function (request) {
- try {
- this._uploadFile(request);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._uploadFile(request);
}
-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.getLastSyncTime = function (libraryID) {
+ return this._getLastSyncTime(libraryID);
}
Zotero.Sync.Storage.Mode.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
- try {
- this._setLastSyncTime(callback, useLastSyncTime);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._setLastSyncTime(callback, useLastSyncTime);
}
Zotero.Sync.Storage.Mode.prototype.checkServer = function (callback) {
- try {
- return this._checkServer(callback);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._checkServer(callback);
}
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);
- }
+ return this._checkServerCallback(uri, status, window, skipSuccessMessage);
}
Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
- try {
- return this._cacheCredentials(callback);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._cacheCredentials(callback);
}
Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) {
- try {
- this._purgeDeletedStorageFiles(callback);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._purgeDeletedStorageFiles(callback);
}
Zotero.Sync.Storage.Mode.prototype.purgeOrphanedStorageFiles = function (callback) {
- try {
- this._purgeOrphanedStorageFiles(callback);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return this._purgeOrphanedStorageFiles(callback);
}
diff --git a/chrome/content/zotero/xpcom/storage/queue.js b/chrome/content/zotero/xpcom/storage/queue.js
index df5ff5a4b..21956a009 100644
--- a/chrome/content/zotero/xpcom/storage/queue.js
+++ b/chrome/content/zotero/xpcom/storage/queue.js
@@ -26,13 +26,14 @@
/**
* Queue for storage sync transfer requests
*
- * @param {String} name Queue name (e.g., 'download' or 'upload')
+ * @param {String} type Queue type (e.g., 'download' or 'upload')
*/
-Zotero.Sync.Storage.Queue = function (name) {
- Zotero.debug("Initializing " + name + " queue");
+Zotero.Sync.Storage.Queue = function (type, libraryID) {
+ Zotero.debug("Initializing " + type + " queue for library " + libraryID);
// Public properties
- this.name = name;
+ this.type = type;
+ this.libraryID = libraryID;
this.maxConcurrentRequests = 1;
this.activeRequests = 0;
this.totalRequests = 0;
@@ -42,16 +43,25 @@ Zotero.Sync.Storage.Queue = function (name) {
this._highPriority = [];
this._running = false;
this._stopping = false;
+ this._finished = false;
+ this._error = false;
this._finishedReqs = 0;
- this._lastTotalRequests = 0;
+ this._localChanges = false;
+ this._remoteChanges = false;
+ this._conflicts = [];
}
-Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Name', function () {
- return this.name[0].toUpperCase() + this.name.substr(1);
+Zotero.Sync.Storage.Queue.prototype.__defineGetter__('name', function () {
+ return this.type + "/" + this.libraryID;
+});
+
+Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Type', function () {
+ return this.type[0].toUpperCase() + this.type.substr(1);
});
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('running', function () this._running);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('stopping', function () this._stopping);
+Zotero.Sync.Storage.Queue.prototype.__defineGetter__('finished', function () this._finished);
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('unfinishedRequests', function () {
return this.totalRequests - this.finishedRequests;
@@ -73,22 +83,51 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
// Last request
if (val == this.totalRequests) {
- Zotero.debug(this.Name + " queue is done");
+ Zotero.debug(this.Type + " queue is done for library " + this.libraryID);
// DEBUG info
Zotero.debug("Active requests: " + this.activeRequests);
if (this.activeRequests) {
- throw new Error(this.Name + " queue can't be done if there are active requests");
+ throw new Error(this.Type + " queue for library " + this.libraryID
+ + " can't be done if there are active requests");
}
this._running = false;
this._stopping = false;
+ this._finished = true;
this._requests = {};
this._highPriority = [];
- this._finishedReqs = 0;
- this._lastTotalRequests = this.totalRequests;
- this.totalRequests = 0;
+
+ var localChanges = this._localChanges;
+ var remoteChanges = this._remoteChanges;
+ var conflicts = this._conflicts.concat();
+ this._localChanges = false;
+ this._remoteChanges = false;
+ this._conflicts = [];
+
+ if (!this._error) {
+ Zotero.debug("Resolving promise for queue " + this.name);
+ Zotero.debug(this._localChanges);
+ Zotero.debug(this._remoteChanges);
+ Zotero.debug(this._conflicts);
+
+ this._deferred.resolve({
+ libraryID: this.libraryID,
+ type: this.type,
+ localChanges: localChanges,
+ remoteChanges: remoteChanges,
+ conflicts: conflicts
+ });
+ }
+ else {
+ Zotero.debug("Rejecting promise for queue " + this.name);
+ var e = this._error;
+ this._error = false;
+ e.libraryID = this.libraryID;
+ e.type = this.type;
+ this._deferred.reject(e);
+ }
return;
}
@@ -99,10 +138,6 @@ Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', functio
this.advance();
});
-Zotero.Sync.Storage.Queue.prototype.__defineGetter__('lastTotalRequests', function () {
- return this._lastTotalRequests;
-});
-
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('queuedRequests', function () {
return this.unfinishedRequests - this.activeRequests;
});
@@ -119,6 +154,9 @@ Zotero.Sync.Storage.Queue.prototype.__defineGetter__('percentage', function () {
if (this.totalRequests == 0) {
return 0;
}
+ if (this._finished) {
+ return 100;
+ }
var completedRequests = 0;
for each(var request in this._requests) {
@@ -144,9 +182,13 @@ Zotero.Sync.Storage.Queue.prototype.isStopping = function () {
* @param {Boolean} highPriority Add or move request to high priority queue
*/
Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority) {
+ if (this._finished) {
+ this.reset();
+ }
+
request.queue = this;
var name = request.name;
- Zotero.debug("Queuing " + this.name + " request '" + name + "'");
+ Zotero.debug("Queuing " + this.type + " request '" + name + "' for library " + this.libraryID);
if (this._requests[name]) {
if (highPriority) {
@@ -166,56 +208,133 @@ Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority
if (highPriority) {
this._highPriority.push(name);
}
-
- this.advance();
}
+Zotero.Sync.Storage.Queue.prototype.start = function () {
+ if (!this._deferred || this._deferred.promise.isResolved()) {
+ Zotero.debug("Creating deferred for queue " + this.name);
+ this._deferred = Q.defer();
+ }
+ // The queue manager needs to know what queues were running in the
+ // current session
+ Zotero.Sync.Storage.QueueManager.addCurrentQueue(this);
+ this.advance();
+ return this._deferred.promise;
+}
+
+
+
/**
* Start another request in this queue if there's an available slot
*/
Zotero.Sync.Storage.Queue.prototype.advance = function () {
this._running = true;
+ this._finished = false;
if (this._stopping) {
- Zotero.debug(this.Name + " queue is being stopped in "
- + "Zotero.Sync.Storage.Queue.advance()", 2);
+ Zotero.debug(this.Type + " queue for library " + this.libraryID
+ + "is being stopped in Zotero.Sync.Storage.Queue.advance()", 2);
return;
}
if (!this.queuedRequests) {
- Zotero.debug("No remaining requests in " + this.name + " queue ("
+ Zotero.debug("No remaining requests in " + this.type
+ + " queue for library " + this.libraryID + " ("
+ this.activeRequests + " active, "
+ this.finishedRequests + " finished)");
return;
}
if (this.activeRequests >= this.maxConcurrentRequests) {
- Zotero.debug(this.Name + " queue is busy ("
- + this.activeRequests + "/" + this.maxConcurrentRequests + ")");
+ Zotero.debug(this.Type + " queue for library " + this.libraryID
+ + " is busy (" + this.activeRequests + "/"
+ + this.maxConcurrentRequests + ")");
return;
}
+
+
// Start the first unprocessed request
// Try the high-priority queue first
- var name, request;
+ var self = this;
+ var request, name;
while (name = this._highPriority.shift()) {
request = this._requests[name];
- if (!request.isRunning() && !request.isFinished()) {
- request.start();
- this.advance();
- return;
+ if (request.isRunning() || request.isFinished()) {
+ continue;
}
+
+ let requestName = name;
+
+ Q.fcall(function () {
+ var promise = request.start();
+ self.advance();
+ return promise;
+ })
+ .then(function (result) {
+ if (result.localChanges) {
+ self._localChanges = true;
+ }
+ if (result.remoteChanges) {
+ self._remoteChanges = true;
+ }
+ if (result.conflict) {
+ self.addConflict(
+ requestName,
+ result.conflict.local,
+ result.conflict.remote
+ );
+ }
+ })
+ .fail(function (e) {
+ self.error(e);
+ });
+
+ return;
}
// And then others
- for each(request in this._requests) {
- if (!request.isRunning() && !request.isFinished()) {
- request.start();
- this.advance();
- return;
+ for each(var request in this._requests) {
+ if (request.isRunning() || request.isFinished()) {
+ continue;
}
+
+ let requestName = request.name;
+
+ // This isn't in an fcall() because the request needs to get marked
+ // as running immediately so that it doesn't get run again by a
+ // subsequent advance() call.
+ try {
+ var promise = request.start();
+ self.advance();
+ }
+ catch (e) {
+ self.error(e);
+ }
+
+ Q.when(promise)
+ .then(function (result) {
+ if (result.localChanges) {
+ self._localChanges = true;
+ }
+ if (result.remoteChanges) {
+ self._remoteChanges = true;
+ }
+ if (result.conflict) {
+ self.addConflict(
+ requestName,
+ result.conflict.local,
+ result.conflict.remote
+ );
+ }
+ })
+ .fail(function (e) {
+ self.error(e);
+ });
+
+ return;
}
}
@@ -225,8 +344,26 @@ Zotero.Sync.Storage.Queue.prototype.updateProgress = function () {
}
+Zotero.Sync.Storage.Queue.prototype.addConflict = function (requestName, localData, remoteData) {
+ Zotero.debug('===========');
+ Zotero.debug(localData);
+ Zotero.debug(remoteData);
+
+ this._conflicts.push({
+ name: requestName,
+ localData: localData,
+ remoteData: remoteData
+ });
+}
+
+
Zotero.Sync.Storage.Queue.prototype.error = function (e) {
- Zotero.Sync.Storage.EventManager.error(e);
+ if (!this._error) {
+ this._error = e;
+ }
+ Zotero.debug(e, 1);
+ Components.utils.reportError(e.message ? e.message : e);
+ this.stop();
}
@@ -235,14 +372,18 @@ Zotero.Sync.Storage.Queue.prototype.error = function (e) {
*/
Zotero.Sync.Storage.Queue.prototype.stop = function () {
if (!this._running) {
- Zotero.debug(this.Name + " queue is not running");
+ Zotero.debug(this.Type + " queue for library " + this.libraryID
+ + " is not running");
return;
}
if (this._stopping) {
- Zotero.debug("Already stopping " + this.name + " queue");
+ Zotero.debug("Already stopping " + this.type + " queue for library "
+ + this.libraryID);
return;
}
+ Zotero.debug("Stopping " + this.type + " queue for library " + this.libraryID);
+
// If no requests, finish manually
/*if (this.activeRequests == 0) {
this._finishedRequests = this._finishedRequests;
@@ -255,4 +396,13 @@ Zotero.Sync.Storage.Queue.prototype.stop = function () {
request.stop();
}
}
+
+ Zotero.debug("Queue is stopped");
+}
+
+
+Zotero.Sync.Storage.Queue.prototype.reset = function () {
+ this._finished = false;
+ this._finishedReqs = 0;
+ this.totalRequests = 0;
}
diff --git a/chrome/content/zotero/xpcom/storage/queueManager.js b/chrome/content/zotero/xpcom/storage/queueManager.js
index a2732a8bb..ba8e7c437 100644
--- a/chrome/content/zotero/xpcom/storage/queueManager.js
+++ b/chrome/content/zotero/xpcom/storage/queueManager.js
@@ -26,8 +26,63 @@
Zotero.Sync.Storage.QueueManager = new function () {
var _queues = {};
- var _conflicts = [];
- var _cancelled = false;
+ var _currentQueues = [];
+
+ this.start = function (libraryID) {
+ if (libraryID === 0 || libraryID) {
+ var queues = this.getAll(libraryID);
+ }
+ else {
+ var queues = this.getAll();
+ }
+
+ Zotero.debug("Starting file sync queues");
+
+ var promises = [];
+ for each(var queue in queues) {
+ if (!queue.unfinishedRequests) {
+ continue;
+ }
+ Zotero.debug("Starting queue " + queue.name);
+ promises.push(queue.start());
+ }
+
+ if (!promises.length) {
+ Zotero.debug("No files to sync");
+ }
+
+ return Q.allResolved(promises)
+ .then(function (promises) {
+ Zotero.debug("All storage queues are finished");
+ promises.forEach(function (promise) {
+ if (promise.isFulfilled()) {
+ var result = promise.valueOf();
+ if (result.conflicts.length) {
+ Zotero.debug("Reconciling conflicts for library " + result.libraryID);
+ Zotero.debug(result.conflicts);
+ var data = _reconcileConflicts(result.conflicts);
+ if (data) {
+ _processMergeData(data);
+ }
+ }
+ }
+ });
+
+ return promises;
+ });
+ };
+
+ this.stop = function (libraryID) {
+ if (libraryID === 0 || libraryID) {
+ var queues = this.getAll(libraryID);
+ }
+ else {
+ var queues = this.getAll();
+ }
+ for (var queue in queues) {
+ queue.stop();
+ }
+ };
/**
@@ -35,13 +90,19 @@ Zotero.Sync.Storage.QueueManager = new function () {
*
* @param {String} queueName
*/
- this.get = function (queueName, noInit) {
+ this.get = function (queueName, libraryID, noInit) {
+ if (typeof libraryID == 'undefined') {
+ throw new Error("libraryID not specified");
+ }
+
+ var hash = queueName + "/" + libraryID;
+
// Initialize the queue if it doesn't exist yet
- if (!_queues[queueName]) {
+ if (!_queues[hash]) {
if (noInit) {
return false;
}
- var queue = new Zotero.Sync.Storage.Queue(queueName);
+ var queue = new Zotero.Sync.Storage.Queue(queueName, libraryID);
switch (queueName) {
case 'download':
queue.maxConcurrentRequests =
@@ -56,22 +117,36 @@ Zotero.Sync.Storage.QueueManager = new function () {
default:
throw ("Invalid queue '" + queueName + "' in Zotero.Sync.Storage.QueueManager.get()");
}
- _queues[queueName] = queue;
+ _queues[hash] = queue;
}
- return _queues[queueName];
- }
+ return _queues[hash];
+ };
- this.getAll = function () {
+ this.getAll = function (libraryID) {
var queues = [];
for each(var queue in _queues) {
- queues.push(queue);
+ if (typeof libraryID == 'undefined' || queue.libraryID === libraryID) {
+ queues.push(queue);
+ }
}
return queues;
};
+ this.addCurrentQueue = function (queue) {
+ if (!this.hasCurrentQueue(queue)) {
+ _currentQueues.push(queue.name);
+ }
+ }
+
+
+ this.hasCurrentQueue = function (queue) {
+ return _currentQueues.indexOf(queue.name) != -1;
+ }
+
+
/**
* Stop all queues
*
@@ -81,7 +156,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
*/
this.cancel = function (skipStorageFinish) {
Zotero.debug("Stopping all storage queues");
- _cancelled = true;
for each(var queue in _queues) {
if (queue.isRunning() && !queue.isStopping()) {
queue.stop();
@@ -92,26 +166,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
this.finish = function () {
Zotero.debug("All storage queues are finished");
-
- if (!_cancelled && _conflicts.length) {
- var data = _reconcileConflicts();
- if (data) {
- _processMergeData(data);
- }
- }
-
- try {
- if (_cancelled) {
- Zotero.Sync.Storage.EventManager.stop();
- }
- else {
- Zotero.Sync.Storage.EventManager.success();
- }
- }
- finally {
- _cancelled = false;
- _conflicts = [];
- }
+ _currentQueues = [];
}
@@ -132,86 +187,32 @@ Zotero.Sync.Storage.QueueManager = new function () {
activeRequests += queue.activeRequests;
}
if (activeRequests == 0) {
- this.updateProgressMeters(0);
+ _updateProgressMeters(0);
if (allFinished) {
this.finish();
}
return;
}
- // Percentage
- var percentageSum = 0;
- var numQueues = 0;
+ var status = {};
for each(var queue in _queues) {
- percentageSum += queue.percentage;
- numQueues++;
- }
- var percentage = Math.round(percentageSum / numQueues);
- //Zotero.debug("Total percentage is " + percentage);
-
- // Remaining KB
- var downloadStatus = _queues.download ?
- _getQueueStatus(_queues.download) : 0;
- var uploadStatus = _queues.upload ?
- _getQueueStatus(_queues.upload) : 0;
-
- this.updateProgressMeters(
- activeRequests, percentage, downloadStatus, uploadStatus
- );
- }
-
-
- /**
- * Cycle through windows, updating progress meters with new values
- */
- this.updateProgressMeters = function (activeRequests, percentage, downloadStatus, uploadStatus) {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var enumerator = wm.getEnumerator("navigator:browser");
- while (enumerator.hasMoreElements()) {
- var win = enumerator.getNext();
- if (!win.ZoteroPane) continue;
- var doc = win.ZoteroPane.document;
-
- //
- // TODO: Move to overlay.js?
- //
- var box = doc.getElementById("zotero-tb-sync-progress-box");
- var meter = doc.getElementById("zotero-tb-sync-progress");
-
- if (activeRequests == 0) {
- box.hidden = true;
+ if (!this.hasCurrentQueue(queue)) {
continue;
}
- meter.setAttribute("value", percentage);
- box.hidden = false;
-
- var tooltip = doc.
- getElementById("zotero-tb-sync-progress-tooltip-progress");
- tooltip.setAttribute("value", percentage + "%");
-
- var tooltip = doc.
- getElementById("zotero-tb-sync-progress-tooltip-downloads");
- tooltip.setAttribute("value", downloadStatus);
-
- var tooltip = doc.
- getElementById("zotero-tb-sync-progress-tooltip-uploads");
- tooltip.setAttribute("value", uploadStatus);
+ if (!status[queue.libraryID]) {
+ status[queue.libraryID] = {};
+ }
+ if (!status[queue.libraryID][queue.type]) {
+ status[queue.libraryID][queue.type] = {};
+ }
+ status[queue.libraryID][queue.type].statusString = _getQueueStatus(queue);
+ status[queue.libraryID][queue.type].percentage = queue.percentage;
+ status[queue.libraryID][queue.type].totalRequests = queue.totalRequests;
+ status[queue.libraryID][queue.type].finished = queue.finished;
}
- }
-
-
- this.addConflict = function (requestName, localData, remoteData) {
- Zotero.debug('===========');
- Zotero.debug(localData);
- Zotero.debug(remoteData);
- _conflicts.push({
- name: requestName,
- localData: localData,
- remoteData: remoteData
- });
+ _updateProgressMeters(activeRequests, status);
}
@@ -226,26 +227,76 @@ Zotero.Sync.Storage.QueueManager = new function () {
var unfinishedRequests = queue.unfinishedRequests;
if (!unfinishedRequests) {
- return Zotero.getString('sync.storage.none')
+ return Zotero.getString('sync.storage.none');
}
- var kbRemaining = Zotero.getString(
- 'sync.storage.kbRemaining',
- Zotero.Utilities.numberFormat(remaining / 1024, 0)
- );
+ if (remaining > 1000) {
+ var bytesRemaining = Zotero.getString(
+ 'sync.storage.mbRemaining',
+ Zotero.Utilities.numberFormat(remaining / 1000 / 1000, 1)
+ );
+ }
+ else {
+ var bytesRemaining = Zotero.getString(
+ 'sync.storage.kbRemaining',
+ Zotero.Utilities.numberFormat(remaining / 1000, 0)
+ );
+ }
var totalRequests = queue.totalRequests;
var filesRemaining = Zotero.getString(
'sync.storage.filesRemaining',
[totalRequests - unfinishedRequests, totalRequests]
);
- var status = Zotero.localeJoin([kbRemaining, '(' + filesRemaining + ')']);
- return status;
+ return bytesRemaining + ' (' + filesRemaining + ')';
+ }
+
+ /**
+ * Cycle through windows, updating progress meters with new values
+ */
+ function _updateProgressMeters(activeRequests, status) {
+ // Get overall percentage across queues
+ var sum = 0, num = 0, percentage, total;
+ for each(var libraryStatus in status) {
+ for each(var queueStatus in libraryStatus) {
+ percentage = queueStatus.percentage;
+ total = queueStatus.totalRequests;
+ sum += total * percentage;
+ num += total;
+ }
+ }
+ var percentage = Math.round(sum / num);
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var enumerator = wm.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ var win = enumerator.getNext();
+ if (!win.ZoteroPane) continue;
+ var doc = win.ZoteroPane.document;
+
+ var box = doc.getElementById("zotero-tb-sync-progress-box");
+ var meter = doc.getElementById("zotero-tb-sync-progress");
+
+ if (activeRequests == 0) {
+ box.hidden = true;
+ continue;
+ }
+
+ meter.setAttribute("value", percentage);
+ box.hidden = false;
+
+ var percentageLabel = doc.getElementById('zotero-tb-sync-progress-tooltip-progress');
+ percentageLabel.lastChild.setAttribute('value', percentage + "%");
+
+ var statusBox = doc.getElementById('zotero-tb-sync-progress-status');
+ statusBox.data = status;
+ }
}
- function _reconcileConflicts() {
+ function _reconcileConflicts(conflicts) {
var objectPairs = [];
- for each(var conflict in _conflicts) {
+ for each(var conflict in conflicts) {
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
var item1 = item.clone(false, false, true);
item1.setField('dateModified',
@@ -279,8 +330,8 @@ Zotero.Sync.Storage.QueueManager = new function () {
// Since we're only putting cloned items into the merge window,
// we have to manually set the ids
- for (var i=0; i<_conflicts.length; i++) {
- io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(_conflicts[i].name).id;
+ for (var i=0; i 100) {
Zotero.debug(percentage + " is greater than 100 for "
- + this.name + " request", 2);
+ + "request " + this.name, 2);
Zotero.debug(this.progress);
Zotero.debug(this.progressMax);
percentage = 100;
@@ -119,7 +135,15 @@ Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function ()
Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
+ if (this._finished) {
+ return 0;
+ }
+
if (!this.progressMax) {
+ if (this.queue.type == 'upload' && this._maxSize) {
+ return Math.round(Zotero.Sync.Storage.compressionTracker.ratio * this._maxSize);
+ }
+
//Zotero.debug("Remaining not yet available for request '" + this.name + "'");
return 0;
}
@@ -151,22 +175,70 @@ Zotero.Sync.Storage.Request.prototype.setChannel = function (channel) {
Zotero.Sync.Storage.Request.prototype.start = function () {
if (!this.queue) {
- throw ("Request '" + this.name + "' must be added to a queue before starting");
+ throw ("Request " + this.name + " must be added to a queue before starting");
}
+ Zotero.debug("Starting " + this.queue.name + " request " + this.name);
+
if (this._running) {
- throw ("Request '" + this.name + "' already running in "
- + "Zotero.Sync.Storage.Request.start()");
+ throw new Error("Request " + this.name + " already running");
}
- Zotero.debug("Starting " + this.queue.name + " request '" + this.name + "'");
this._running = true;
this.queue.activeRequests++;
- if (this._onStart) {
- for each(var f in this._onStart) {
- f(this);
- }
+
+ if (this.queue.type == 'download') {
+ Zotero.Sync.Storage.setItemDownloadPercentage(this.name, 0);
}
+
+ var self = this;
+
+ // this._onStart is an array of promises returning changesMade.
+ //
+ // The main sync logic is triggered here.
+
+ Q.all([f(this) for each(f in this._onStart)])
+ .then(function (results) {
+ return {
+ localChanges: results.some(function (val) val && val.localChanges == true),
+ remoteChanges: results.some(function (val) val && val.remoteChanges == true),
+ conflict: results.reduce(function (prev, cur) {
+ return prev.conflict ? prev : cur;
+ }).conflict
+ };
+ })
+ .then(function (results) {
+ Zotero.debug('!!!!');
+ Zotero.debug(results);
+
+ if (results.localChanges) {
+ Zotero.debug("Changes were made by " + self.queue.name
+ + " request " + self.name);
+ }
+ else {
+ Zotero.debug("No changes were made by " + self.queue.name
+ + " request " + self.name);
+ }
+
+ // This promise updates localChanges/remoteChanges on the queue
+ self._deferred.resolve(results);
+ })
+ .fail(function (e) {
+ Zotero.debug(self.queue.Type + " request " + self.name + " failed");
+ Zotero.debug(self._deferred);
+ Zotero.debug(self._deferred.promise.isFulfilled());
+ self._deferred.reject(e);
+ Zotero.debug(self._deferred.promise.isFulfilled());
+ Zotero.debug(self._deferred.promise.isRejected());
+ })
+ // Finish the request (and in turn the queue, if this is the last request)
+ .fin(function () {
+ if (!self._finished) {
+ self._finish();
+ }
+ });
+
+ return this._deferred.promise;
}
@@ -191,6 +263,8 @@ Zotero.Sync.Storage.Request.prototype.isFinished = function () {
* (usually total bytes)
*/
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
+ Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
+
if (!this._running) {
Zotero.debug("Trying to update finished request " + this.name + " in "
+ "Zotero.Sync.Storage.Request.onProgress() "
@@ -219,6 +293,10 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
this.progressMax = progressMax;
this.queue.updateProgress();
+ if (this.queue.type == 'download') {
+ Zotero.Sync.Storage.setItemDownloadPercentage(this.name, this.percentage);
+ }
+
if (this.onProgress) {
for each(var f in this._onProgress) {
f(progress, progressMax);
@@ -227,62 +305,48 @@ Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress,
}
-Zotero.Sync.Storage.Request.prototype.error = function (e) {
- this.queue.error(e);
-}
-
-
/**
* Stop the request's underlying network request, if there is one
*/
Zotero.Sync.Storage.Request.prototype.stop = function () {
- var finishNow = false;
- try {
- // If upload already finished, finish() will never be called otherwise
- if (this.channel) {
- this.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
- // Throws error if request not finished
- this.channel.requestSucceeded;
- Zotero.debug("Channel is no longer running for request " + this.name);
- Zotero.debug(this.channel.requestSucceeded);
- finishNow = true;
+ if (this.channel) {
+ try {
+ Zotero.debug("Stopping request '" + this.name + "'");
+ this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
+ }
+ catch (e) {
+ Zotero.debug(e);
}
}
- catch (e) {}
-
- if (!this._running || !this.channel || finishNow) {
- this.finish();
- return;
+ else {
+ this._finish();
}
-
- Zotero.debug("Stopping request '" + this.name + "'");
- this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
}
/**
* Mark request as finished and notify queue that it's done
*/
-Zotero.Sync.Storage.Request.prototype.finish = function () {
- if (this._finished) {
- throw ("Request '" + this.name + "' is already finished");
- }
-
+Zotero.Sync.Storage.Request.prototype._finish = function () {
Zotero.debug("Finishing " + this.queue.name + " request '" + this.name + "'");
this._finished = true;
var active = this._running;
this._running = false;
+ Zotero.Sync.Storage.setItemDownloadPercentage(this.name, false);
+
if (active) {
this.queue.activeRequests--;
}
- // mechanism for failures?
- this.queue.finishedRequests++;
- this.queue.updateProgress();
-
- if (this._onStop) {
- for each(var f in this._onStop) {
- f();
- }
+ // TEMP: mechanism for failures?
+ try {
+ this.queue.finishedRequests++;
+ this.queue.updateProgress();
+ }
+ catch (e) {
+ Zotero.debug(e);
+ Components.utils.reportError(e);
+ this._deferred.reject(e);
+ throw e;
}
}
diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js
index 20b07eed9..ca6c29979 100644
--- a/chrome/content/zotero/xpcom/storage/webdav.js
+++ b/chrome/content/zotero/xpcom/storage/webdav.js
@@ -30,6 +30,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
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.";
var _defaultErrorRestart = "A WebDAV file sync error occurred. Please restart " + Zotero.appName + " and try syncing again.\n\nIf you receive this message repeatedly, check your WebDAV server settings in the Sync pane of the Zotero preferences.";
+ var _initialized = false;
var _parentURI;
var _rootURI;
var _cachedCredentials = false;
@@ -46,85 +47,84 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Zotero.Item} item
* @param {Function} callback Callback f(item, mdate)
*/
- function getStorageModificationTime(item, callback) {
+ function getStorageModificationTime(item) {
var uri = getItemPropertyURI(item);
- Zotero.HTTP.doGet(uri, function (req) {
- checkResponse(req);
-
- var funcName = "Zotero.Sync.Storage.WebDAV.getStorageModificationTime()";
-
- // mod_speling can return 300s for 404s with base name matches
- if (req.status == 404 || req.status == 300) {
- callback(item, false);
- return;
- }
- else if (req.status != 200) {
- Zotero.debug(req.responseText);
- Zotero.Sync.Storage.EventManager.error(
- "Unexpected status code " + req.status + " in " + funcName
- );
- }
-
- Zotero.debug(req.responseText);
-
- // No modification time set
- if (!req.responseText) {
- callback(item, false);
- return;
- }
-
- try {
- var xml = new XML(req.responseText);
- }
- catch (e) {
- Zotero.debug(e);
- var xml = null;
- }
-
- if (xml) {
- Zotero.debug(xml.children().length());
- }
-
- if (xml && xml.children().length()) {
- // TODO: other stuff, but this makes us forward-compatible
- mtime = xml.mtime.toString();
- var seconds = false;
- }
- else {
- mtime = req.responseText;
- var seconds = true;
- }
-
- var invalid = false;
-
- // Unix timestamps need to be converted to ms-based timestamps
- if (seconds) {
- if (mtime.match(/^[0-9]{1,10}$/)) {
- Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
- mtime = mtime * 1000;
+ return Zotero.HTTP.promise(
+ "GET", uri, { debug: true, successCodes: [200, 300, 404] }
+ )
+ .then(function (req) {
+ checkResponse(req);
+
+ var funcName = "Zotero.Sync.Storage.WebDAV.getStorageModificationTime()";
+
+ // mod_speling can return 300s for 404s with base name matches
+ if (req.status == 404 || req.status == 300) {
+ return false;
+ }
+
+ // No modification time set
+ if (!req.responseText) {
+ return false;
+ }
+
+ try {
+ var xml = new XML(req.responseText);
+ }
+ catch (e) {
+ Zotero.debug(e);
+ var xml = null;
+ }
+
+ if (xml) {
+ Zotero.debug(xml.children().length());
+ }
+
+ if (xml && xml.children().length()) {
+ // TODO: other stuff, but this makes us forward-compatible
+ mtime = xml.mtime.toString();
+ var seconds = false;
}
else {
+ mtime = req.responseText;
+ var seconds = true;
+ }
+
+ var invalid = false;
+
+ // Unix timestamps need to be converted to ms-based timestamps
+ if (seconds) {
+ if (mtime.match(/^[0-9]{1,10}$/)) {
+ Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
+ mtime = mtime * 1000;
+ }
+ else {
+ invalid = true;
+ }
+ }
+ else if (!mtime.match(/^[0-9]{1,13}$/)) {
invalid = true;
}
- }
- else if (!mtime.match(/^[0-9]{1,13}$/)) {
- invalid = true;
- }
-
- // Delete invalid .prop files
- if (invalid) {
- var msg = "Invalid mod date '" + Zotero.Utilities.ellipsize(mtime, 20)
- + "' for item " + Zotero.Items.getLibraryKeyHash(item);
- Zotero.debug(msg, 1);
- Components.utils.reportError(msg);
- deleteStorageFiles([item.key + ".prop"]);
- Zotero.Sync.Storage.EventManager.error(_defaultError);
- }
-
- var mdate = new Date(parseInt(mtime));
- callback(item, mdate);
- });
+
+ // Delete invalid .prop files
+ if (invalid) {
+ var msg = "Invalid mod date '" + Zotero.Utilities.ellipsize(mtime, 20)
+ + "' for item " + Zotero.Items.getLibraryKeyHash(item);
+ Zotero.debug(msg, 1);
+ Components.utils.reportError(msg);
+ deleteStorageFiles([item.key + ".prop"]);
+ throw _defaultError;
+ }
+
+ return new Date(parseInt(mtime));
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ Zotero.debug(req.responseText);
+ throw new Error("Unexpected status code " + e.status + " in " + funcName);
+ }
+ throw e;
+ });
}
@@ -132,9 +132,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
* Set mod time of file on storage server
*
* @param {Zotero.Item} item
- * @param {Function} callback Callback f(item, props)
*/
- function setStorageModificationTime(item, callback) {
+ function setStorageModificationTime(item) {
var uri = getItemPropertyURI(item);
var mtime = item.attachmentModificationTime;
@@ -145,202 +144,198 @@ Zotero.Sync.Storage.WebDAV = (function () {
{hash}
;
- Zotero.HTTP.WebDAV.doPut(uri, prop.toXMLString(), function (req) {
- switch (req.status) {
- case 200:
- case 201:
- case 204:
- break;
-
- default:
- Zotero.debug(req.responseText);
- throw new Error("Unexpected status code " + req.status);
- }
- callback(item, { mtime: mtime, hash: hash });
- });
-
-
- /**
- * Upload the generated ZIP file to the server
- *
- * @param {Object} Object with 'request' property
- * @return {void}
- */
- function processUploadFile(data) {
- /*
- updateSizeMultiplier(
- (100 - Zotero.Sync.Storage.compressionTracker.ratio) / 100
- );
- */
- var request = data.request;
- var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
-
- getStorageModificationTime(item, function (item, mdate) {
- try {
- if (!request.isRunning()) {
- Zotero.debug("Upload request '" + request.name
- + "' is no longer running after getting mod time");
- return;
- }
-
- // Check for conflict
- if (Zotero.Sync.Storage.getSyncState(item.id)
- != Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
- if (mdate) {
- // Remote prop time
- var mtime = mdate.getTime();
-
- // Local file time
- var fmtime = item.attachmentModificationTime;
-
- var same = false;
- if (fmtime == mtime) {
- same = true;
- Zotero.debug("File mod time matches remote file -- skipping upload");
- }
- // Allow floored timestamps for filesystems that don't support
- // millisecond precision (e.g., HFS+)
- else if (Math.floor(mtime / 1000) * 1000 == fmtime || Math.floor(fmtime / 1000) * 1000 == mtime) {
- same = true;
- Zotero.debug("File mod times are within one-second precision (" + fmtime + " ≅ " + mtime + ") "
- + "-- skipping upload");
- }
- // Allow timestamp to be exactly one hour off to get around
- // time zone issues -- there may be a proper way to fix this
- else if (Math.abs(fmtime - mtime) == 3600000
- // And check with one-second precision as well
- || Math.abs(fmtime - Math.floor(mtime / 1000) * 1000) == 3600000
- || Math.abs(Math.floor(fmtime / 1000) * 1000 - mtime) == 3600000) {
- same = true;
- Zotero.debug("File mod time (" + fmtime + ") is exactly one hour off remote file (" + mtime + ") "
- + "-- assuming time zone issue and skipping upload");
- }
-
- if (same) {
- Zotero.DB.beginTransaction();
- var syncState = Zotero.Sync.Storage.getSyncState(item.id);
- Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
- Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
- Zotero.DB.commitTransaction();
- onChangesMade();
- request.finish();
- return;
- }
-
- var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
- if (smtime != mtime) {
- var localData = { modTime: fmtime };
- var remoteData = { modTime: mtime };
- Zotero.Sync.Storage.QueueManager.addConflict(
- request.name, localData, remoteData
- );
- Zotero.debug("Conflict -- last synced file mod time "
- + "does not match time on storage server"
- + " (" + smtime + " != " + mtime + ")");
- request.finish();
- return;
- }
- }
- else {
- Zotero.debug("Remote file not found for item " + item.id);
- }
- }
-
- var file = Zotero.getTempDirectory();
- file.append(item.key + '.zip');
-
- var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
- .createInstance(Components.interfaces.nsIFileInputStream);
- fis.init(file, 0x01, 0, 0);
-
- var bis = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
- .createInstance(Components.interfaces.nsIBufferedInputStream)
- bis.init(fis, 64 * 1024);
-
- var uri = getItemURI(item);
-
- var ios = Components.classes["@mozilla.org/network/io-service;1"].
- getService(Components.interfaces.nsIIOService);
- var channel = ios.newChannelFromURI(uri);
- channel.QueryInterface(Components.interfaces.nsIUploadChannel);
- channel.setUploadStream(bis, 'application/octet-stream', -1);
- channel.QueryInterface(Components.interfaces.nsIHttpChannel);
- channel.requestMethod = 'PUT';
- channel.allowPipelining = false;
-
- channel.setRequestHeader('Keep-Alive', '', false);
- channel.setRequestHeader('Connection', '', false);
-
- var listener = new Zotero.Sync.Storage.StreamListener(
- {
- onProgress: function (a, b, c) {
- request.onProgress(a, b, c);
- },
- onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response,data); },
- onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
- request: request,
- item: item,
- streams: [fis, bis]
- }
- );
- channel.notificationCallbacks = listener;
-
- var dispURI = uri.clone();
- if (dispURI.password) {
- dispURI.password = '********';
- }
- Zotero.debug("HTTP PUT of " + file.leafName + " to " + dispURI.spec);
-
- channel.asyncOpen(listener, null);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return Zotero.HTTP.promise("PUT", uri, prop.toXMLString(),
+ { debug: true, successCodes: [200, 201, 204] })
+ .then(function (req) {
+ return { mtime: mtime, hash: hash };
+ })
+ .fail(function (e) {
+ throw new Error("Unexpected status code " + e.xmlhttp.status);
});
- }
+ };
+
+
+
+ /**
+ * Upload the generated ZIP file to the server
+ *
+ * @param {Object} Object with 'request' property
+ * @return {void}
+ */
+ function processUploadFile(data) {
+ /*
+ updateSizeMultiplier(
+ (100 - Zotero.Sync.Storage.compressionTracker.ratio) / 100
+ );
+ */
+ var request = data.request;
+ var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
-
- function onUploadComplete(httpRequest, status, response, data) {
- var request = data.request;
- var item = data.item;
- var url = httpRequest.name;
-
- Zotero.debug("Upload of attachment " + item.key
- + " finished with status code " + status);
-
- switch (status) {
- case 200:
- case 201:
- case 204:
- break;
-
- case 403:
- case 500:
- Zotero.debug(response);
- Zotero.Sync.Storage.EventManager.error(
- Zotero.getString('sync.storage.error.fileUploadFailed')
- + " " + Zotero.getString('sync.storage.error.checkFileSyncSettings')
- );
-
- case 507:
- Zotero.debug(response);
- Zotero.Sync.Storage.EventManager.error(
- Zotero.getString('sync.storage.error.webdav.insufficientSpace')
- );
-
- default:
- Zotero.debug(response);
- Zotero.Sync.Storage.EventManager.error(
- "Unexpected file upload status " + status
- + " in Zotero.Sync.Storage.WebDAV.onUploadComplete()"
- );
- }
-
- setStorageModificationTime(item, function (item, props) {
+ return getStorageModificationTime(item)
+ .then(function (mdate) {
if (!request.isRunning()) {
Zotero.debug("Upload request '" + request.name
+ "' is no longer running after getting mod time");
- return;
+ return false;
+ }
+
+ // Check for conflict
+ if (Zotero.Sync.Storage.getSyncState(item.id)
+ != Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
+ if (mdate) {
+ // Remote prop time
+ var mtime = mdate.getTime();
+
+ // Local file time
+ var fmtime = item.attachmentModificationTime;
+
+ var same = false;
+ if (fmtime == mtime) {
+ same = true;
+ Zotero.debug("File mod time matches remote file -- skipping upload");
+ }
+ // Allow floored timestamps for filesystems that don't support
+ // millisecond precision (e.g., HFS+)
+ else if (Math.floor(mtime / 1000) * 1000 == fmtime || Math.floor(fmtime / 1000) * 1000 == mtime) {
+ same = true;
+ Zotero.debug("File mod times are within one-second precision (" + fmtime + " ≅ " + mtime + ") "
+ + "-- skipping upload");
+ }
+ // Allow timestamp to be exactly one hour off to get around
+ // time zone issues -- there may be a proper way to fix this
+ else if (Math.abs(fmtime - mtime) == 3600000
+ // And check with one-second precision as well
+ || Math.abs(fmtime - Math.floor(mtime / 1000) * 1000) == 3600000
+ || Math.abs(Math.floor(fmtime / 1000) * 1000 - mtime) == 3600000) {
+ same = true;
+ Zotero.debug("File mod time (" + fmtime + ") is exactly one hour off remote file (" + mtime + ") "
+ + "-- assuming time zone issue and skipping upload");
+ }
+
+ if (same) {
+ Zotero.DB.beginTransaction();
+ var syncState = Zotero.Sync.Storage.getSyncState(item.id);
+ Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
+ Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
+ Zotero.DB.commitTransaction();
+ return true;
+ }
+
+ var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
+ if (smtime != mtime) {
+ var localData = { modTime: fmtime };
+ var remoteData = { modTime: mtime };
+ Zotero.Sync.Storage.QueueManager.addConflict(
+ request.name, localData, remoteData
+ );
+ Zotero.debug("Conflict -- last synced file mod time "
+ + "does not match time on storage server"
+ + " (" + smtime + " != " + mtime + ")");
+ return false;
+ }
+ }
+ else {
+ Zotero.debug("Remote file not found for item " + item.id);
+ }
+ }
+
+ var file = Zotero.getTempDirectory();
+ file.append(item.key + '.zip');
+
+ var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ fis.init(file, 0x01, 0, 0);
+
+ var bis = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
+ .createInstance(Components.interfaces.nsIBufferedInputStream)
+ bis.init(fis, 64 * 1024);
+
+ var uri = getItemURI(item);
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+ var channel = ios.newChannelFromURI(uri);
+ channel.QueryInterface(Components.interfaces.nsIUploadChannel);
+ channel.setUploadStream(bis, 'application/octet-stream', -1);
+ channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ channel.requestMethod = 'PUT';
+ channel.allowPipelining = false;
+
+ channel.setRequestHeader('Keep-Alive', '', false);
+ channel.setRequestHeader('Connection', '', false);
+
+ var deferred = Q.defer();
+
+ var listener = new Zotero.Sync.Storage.StreamListener(
+ {
+ onProgress: function (a, b, c) {
+ request.onProgress(a, b, c);
+ },
+ onStop: function (httpRequest, status, response, data) {
+ deferred.resolve(
+ onUploadComplete(httpRequest, status, response, data)
+ );
+ },
+ onCancel: function (httpRequest, status, data) {
+ onUploadCancel(httpRequest, status, data);
+ deferred.resolve(false);
+ },
+ request: request,
+ item: item,
+ streams: [fis, bis]
+ }
+ );
+ channel.notificationCallbacks = listener;
+
+ var dispURI = uri.clone();
+ if (dispURI.password) {
+ dispURI.password = '********';
+ }
+ Zotero.debug("HTTP PUT of " + file.leafName + " to " + dispURI.spec);
+
+ channel.asyncOpen(listener, null);
+
+ return deferred.promise;
+ });
+ }
+
+
+ function onUploadComplete(httpRequest, status, response, data) {
+ var request = data.request;
+ var item = data.item;
+ var url = httpRequest.name;
+
+ Zotero.debug("Upload of attachment " + item.key
+ + " finished with status code " + status);
+
+ switch (status) {
+ case 200:
+ case 201:
+ case 204:
+ break;
+
+ case 403:
+ case 500:
+ Zotero.debug(response);
+ throw (Zotero.getString('sync.storage.error.fileUploadFailed') +
+ " " + Zotero.getString('sync.storage.error.checkFileSyncSettings'));
+
+ case 507:
+ Zotero.debug(response);
+ throw Zotero.getString('sync.storage.error.webdav.insufficientSpace');
+
+ default:
+ Zotero.debug(response);
+ throw ("Unexpected file upload status " + status +
+ " in Zotero.Sync.Storage.WebDAV.onUploadComplete()");
+ }
+
+ return setStorageModificationTime(item)
+ .then(function (props) {
+ if (!request.isRunning()) {
+ Zotero.debug("Upload request '" + request.name
+ + "' is no longer running after getting mod time");
+ return false;
}
Zotero.DB.beginTransaction();
@@ -360,28 +355,27 @@ Zotero.Sync.Storage.WebDAV = (function () {
Components.utils.reportError(e);
}
- onChangesMade();
- request.finish();
+ return {
+ localChanges: true,
+ remoteChanges: true
+ };
});
+ }
+
+
+ function onUploadCancel(httpRequest, status, data) {
+ var request = data.request;
+ var item = data.item;
+
+ Zotero.debug("Upload of attachment " + item.key + " cancelled with status code " + status);
+
+ try {
+ var file = Zotero.getTempDirectory();
+ file.append(item.key + '.zip');
+ file.remove(false);
}
-
-
- function onUploadCancel(httpRequest, status, data) {
- var request = data.request;
- var item = data.item;
-
- Zotero.debug("Upload of attachment " + item.key + " cancelled with status code " + status);
-
- try {
- var file = Zotero.getTempDirectory();
- file.append(item.key + '.zip');
- file.remove(false);
- }
- catch (e) {
- Components.utils.reportError(e);
- }
-
- request.finish();
+ catch (e) {
+ Components.utils.reportError(e);
}
}
@@ -649,8 +643,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
}
);
-
- Zotero.Sync.Storage.EventManager.error(e);
+ throw e;
}
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
var msg = Zotero.getString('sync.storage.error.webdav.sslConnectionError', host) +
@@ -667,7 +660,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
}
);
- Zotero.Sync.Storage.EventManager.error(e);
+ throw e;
}
}
}
@@ -686,10 +679,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
});
obj.includeGroupItems = false;
- Object.defineProperty(obj, "_enabled", {
- get: function () this.includeUserFiles
- });
-
Object.defineProperty(obj, "_verified", {
get: function () Zotero.Prefs.get("sync.storage.verified")
});
@@ -755,7 +744,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Object.defineProperty(obj, "rootURI", {
get: function () {
if (!_rootURI) {
- throw new Error("Root URI not initialized");
+ this._init();
}
return _rootURI.clone();
}
@@ -764,62 +753,16 @@ Zotero.Sync.Storage.WebDAV = (function () {
Object.defineProperty(obj, "parentURI", {
get: function () {
if (!_parentURI) {
- throw new Error("Parent URI not initialized");
+ this._init();
}
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; }
- });
- }
+ obj._init = function () {
+ _rootURI = false;
+ _parentURI = false;
- 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; }
- });
- }
-
- 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':
@@ -832,7 +775,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
var url = Zotero.Prefs.get('sync.storage.url');
if (!url) {
- return false;
+ 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; }
+ });
}
url = scheme + '://' + url;
@@ -840,7 +790,41 @@ Zotero.Sync.Storage.WebDAV = (function () {
var username = this._username;
var password = this._password;
- return this._init(url, dir, username, password);
+ if (!username) {
+ var msg = "WebDAV username not provided";
+ Zotero.debug(msg);
+ throw ({
+ message: msg,
+ name: "Z_ERROR_NO_USERNAME",
+ filename: "webdav.js",
+ toString: function () { return this.message; }
+ });
+ }
+
+ if (!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; }
+ });
+ }
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+ var uri = ios.newURI(url, null, null);
+ uri.username = username;
+ uri.password = password;
+ if (!uri.spec.match(/\/$/)) {
+ uri.spec += "/";
+ }
+ _parentURI = uri;
+
+ var uri = uri.clone();
+ uri.spec += "zotero/";
+ _rootURI = uri;
};
@@ -856,20 +840,20 @@ Zotero.Sync.Storage.WebDAV = (function () {
}
// 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));
- request.finish();
- return;
- }
-
- try {
+ return getStorageModificationTime(item)
+ .then(function (mdate) {
+ if (!request.isRunning()) {
+ Zotero.debug("Download request '" + request.name
+ + "' is no longer running after getting mod time");
+ return false;
+ }
+
+ if (!mdate) {
+ Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item));
+ request.finish();
+ return false;
+ }
+
var syncModTime = mdate.getTime();
// Skip download if local file exists and matches mod time
@@ -883,9 +867,9 @@ Zotero.Sync.Storage.WebDAV = (function () {
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;
+ return {
+ localChanges: true
+ };
}
var uri = getItemURI(item);
@@ -895,6 +879,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
destFile.remove(false);
}
+ var deferred = Q.defer();
+
var listener = new Zotero.Sync.Storage.StreamListener(
{
onStart: function (request, data) {
@@ -902,7 +888,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("Download request " + data.request.name
+ " stopped before download started -- closing channel");
request.cancel(0x804b0002); // NS_BINDING_ABORTED
- return;
+ deferred.resolve(false);
}
},
onProgress: function (a, b, c) {
@@ -917,7 +903,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
// Delete the orphaned prop file
deleteStorageFiles([item.key + ".prop"]);
- data.request.finish();
+ deferred.resolve(false);
return;
}
else if (status != 200) {
@@ -926,26 +912,31 @@ Zotero.Sync.Storage.WebDAV = (function () {
+ " in Zotero.Sync.Storage.WebDAV.downloadFile()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
- Zotero.Sync.Storage.EventManager.error(_defaultError);
+ deferred.reject(_defaultError);
+ return;
}
// 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");
+ deferred.resolve(false);
return;
}
Zotero.debug("Finished download of " + destFile.path);
try {
- Zotero.Sync.Storage.processDownload(data);
- data.request.finish();
+ deferred.resolve(Zotero.Sync.Storage.processDownload(data));
}
catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
+ deferred.reject(e);
}
},
+ onCancel: function (request, status, data) {
+ Zotero.debug("Request cancelled");
+ deferred.resolve(false);
+ },
request: request,
item: item,
compressed: true,
@@ -972,146 +963,134 @@ Zotero.Sync.Storage.WebDAV = (function () {
// XXX Always use when we no longer support Firefox < 18
wbp.saveURI(uri, null, null, null, null, destFile, null);
}
- }
- catch (e) {
- request.error(e);
- }
- });
+
+ return deferred.promise;
+ });
};
obj._uploadFile = function (request) {
- Zotero.Sync.Storage.createUploadFile(request, function (data) { processUploadFile(data); });
+ var deferred = Q.defer();
+ Zotero.Sync.Storage.createUploadFile(
+ request,
+ function (data) {
+ deferred.resolve(processUploadFile(data));
+ }
+ );
+ return deferred.promise;
};
- obj._getLastSyncTime = function (callback) {
+ obj._getLastSyncTime = function () {
// 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);
- }
- 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);
- }
- catch(e) {
- Zotero.debug(e, 1);
- Components.utils.reportError(e);
- Zotero.Sync.Storage.EventManager.error(_defaultError);
- }
- });
- return;
+ return Q.fcall(function () {
+ return self._cacheCredentials();
+ })
+ .then(function () {
+ var lastSyncURI = this.rootURI;
+ lastSyncURI.spec += "lastsync";
+ return Zotero.HTTP.promise("GET", lastSyncURI,
+ { debug: true, successCodes: [200, 404] });
+ })
+ .then(function (req) {
+ if (req.status == 404) {
+ Zotero.debug("No last WebDAV sync time");
+ return null;
}
- catch (e) {
- Zotero.debug(e);
- Components.utils.reportError(e);
- Zotero.Sync.Storage.EventManager.error(_defaultError);
+
+ var lastModified = req.getResponseHeader("Last-Modified");
+ var date = new Date(lastModified);
+ Zotero.debug("Last successful WebDAV sync was " + date);
+ return Zotero.Date.toUnixTimestamp(date);
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ if (e.status == 403) {
+ Zotero.debug("Clearing WebDAV authentication credentials", 2);
+ _cachedCredentials = false;
+ }
+ else {
+ throw("Unexpected status code " + e.status + " getting "
+ + "WebDAV last sync time");
+ }
+
+ return Q.reject(e);
+ }
+ // TODO: handle browser offline exception
+ else {
+ throw (e);
}
});
};
- obj._setLastSyncTime = function (callback) {
- 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;
- }
-
+ obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
+ if (libraryID) {
+ throw new Error("libraryID must be 0");
+ }
+
+ // DEBUG: is this necessary for WebDAV?
+ if (localLastSyncTime) {
+ var sql = "REPLACE INTO version VALUES (?, ?)";
+ Zotero.DB.query(
+ sql, ['storage_webdav_' + libraryID, { int: localLastSyncTime }]
+ );
+ return;
+ }
+
+ var uri = this.rootURI;
+ var successFileURI = uri.clone();
+ successFileURI.spec += "lastsync";
+
+ var self = this;
+
+ return Zotero.HTTP.promise("PUT", successFileURI, " ",
+ { debug: true, successCodes: [200, 201, 204] })
+ .then(function () {
+ return self._getLastSyncTime()
+ .then(function (ts) {
+ if (ts) {
+ var sql = "REPLACE INTO version VALUES (?, ?)";
+ Zotero.DB.query(
+ sql, ['storage_webdav_' + libraryID, { int: ts }]
+ );
+ }
+ });
+ })
+ .fail(function (e) {
var msg = "Unexpected error code " + req.status + " uploading storage success file";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
- if (callback) {
- callback();
- }
+ throw _defaultError;
});
- }
- catch (e) {
- Zotero.debug(e);
- Components.utils.reportError(e);
- if (callback) {
- callback();
- }
- return;
- }
};
- obj._cacheCredentials = function (callback) {
+ obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("Credentials are already cached");
- setTimeout(function () {
- callback();
- }, 0);
- return false;
+ return;
}
- 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;
+ return Zotero.HTTP.promise("OPTIONS", this.rootURI)
+ .then(function (req) {
+ // TODO: promisify
+ checkResponse(req);
+
+ Zotero.debug("Credentials are cached");
+ _cachedCredentials = true;
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ var msg = "Unexpected status code " + e.status + " "
+ + "for OPTIONS request caching WebDAV credentials";
+ Zotero.debug(msg, 1);
+ Components.utils.reportError(msg);
+ throw new Error(_defaultErrorRestart);
+ }
+ throw e;
+ });
};
@@ -1120,9 +1099,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Object} errorCallbacks
*/
obj._checkServer = function (callback) {
- this._initFromPrefs();
-
try {
+ // Clear URIs
+ this.init();
+
var parentURI = this.parentURI;
var uri = this.rootURI;
}
@@ -1132,6 +1112,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
callback(null, Zotero.Sync.Storage.ERROR_NO_URL);
return;
+ case 'Z_ERROR_NO_USERNAME':
+ callback(null, Zotero.Sync.Storage.ERROR_NO_USERNAME);
+ return;
+
case 'Z_ERROR_NO_PASSWORD':
callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD);
return;
@@ -1399,6 +1383,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
var errorMessage = Zotero.getString('sync.storage.error.webdav.enterURL');
break;
+ case Zotero.Sync.Storage.ERROR_NO_USERNAME:
+ var errorMessage = Zotero.getString('sync.error.usernameNotSet');
+ break;
+
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
var errorMessage = Zotero.getString('sync.error.enterPassword');
break;
@@ -1525,7 +1513,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
*/
obj._purgeDeletedStorageFiles = function (callback) {
if (!this._active) {
- return;
+ return false;
}
Zotero.debug("Purging deleted storage files");
@@ -1535,10 +1523,11 @@ Zotero.Sync.Storage.WebDAV = (function () {
if (callback) {
callback();
}
- Zotero.Sync.Storage.EventManager.skip();
- return;
+ return false;
}
+ // TODO: promisify
+
// Add .zip extension
var files = files.map(function (file) file + ".zip");
@@ -1582,16 +1571,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
const daysBeforeSyncTime = 1;
if (!this._active) {
- Zotero.Sync.Storage.EventManager.skip();
- return;
+ return false;
}
// 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;
+ return false;
}
Zotero.debug("Purging orphaned storage files");
diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js
index 7f07cb96f..bbf76d1d5 100644
--- a/chrome/content/zotero/xpcom/storage/zfs.js
+++ b/chrome/content/zotero/xpcom/storage/zfs.js
@@ -28,7 +28,6 @@ Zotero.Sync.Storage.ZFS = (function () {
var _rootURI;
var _userURI;
var _cachedCredentials = false;
- var _lastSyncTime = null;
/**
* Get file metadata on storage server
@@ -36,53 +35,55 @@ Zotero.Sync.Storage.ZFS = (function () {
* @param {Zotero.Item} item
* @param {Function} callback Callback f(item, etag)
*/
- function getStorageFileInfo(item, callback) {
- var uri = getItemInfoURI(item);
+ function getStorageFileInfo(item) {
+ var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()";
- Zotero.HTTP.doGet(uri, function (req) {
- var funcName = "Zotero.Sync.Storage.ZFS.getStorageFileInfo()";
-
- if (req.status == 404) {
- callback(item, false);
- return;
- }
- else if (req.status != 200) {
- var msg = "Unexpected status code " + req.status + " in " + funcName
- + " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
- Zotero.debug(msg, 1);
- Zotero.debug(req.responseText);
- Components.utils.reportError(msg);
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
- }
-
- var info = {};
- info.hash = req.getResponseHeader('ETag');
- if (!info.hash) {
- var msg = "Hash not found in info response in " + funcName
- + " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
- Zotero.debug(msg, 1);
- Zotero.debug(req.responseText);
- Components.utils.reportError(msg);
- try {
- Zotero.debug(req.getAllResponseHeaders());
+ return Zotero.HTTP.promise("GET", getItemInfoURI(item), { successCodes: [200, 404] })
+ .then(function (req) {
+ if (req.status == 404) {
+ return false;
}
- catch (e) {
- Zotero.debug("Response headers unavailable");
+
+ var info = {};
+ info.hash = req.getResponseHeader('ETag');
+ if (!info.hash) {
+ var msg = "Hash not found in info response in " + funcName
+ + " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
+ Zotero.debug(msg, 1);
+ Zotero.debug(req.responseText);
+ Components.utils.reportError(msg);
+ try {
+ Zotero.debug(req.getAllResponseHeaders());
+ }
+ catch (e) {
+ Zotero.debug("Response headers unavailable");
+ }
+ // TODO: localize?
+ var msg = "A file sync error occurred. Please restart " + Zotero.appName + " and/or your computer and try syncing again.\n\n"
+ + "If the error persists, there may be a problem with either your computer or your network: security software, proxy server, VPN, etc. "
+ + "Try disabling any security/firewall software you're using or, if this is a laptop, try from a different network.";
+ throw msg;
}
- // TODO: localize?
- var msg = "A file sync error occurred. Please restart " + Zotero.appName + " and/or your computer and try syncing again.\n\n"
- + "If the error persists, there may be a problem with either your computer or your network: security software, proxy server, VPN, etc. "
- + "Try disabling any security/firewall software you're using or, if this is a laptop, try from a different network.";
- Zotero.Sync.Storage.EventManager.error(msg);
- }
- info.filename = req.getResponseHeader('X-Zotero-Filename');
- var mtime = req.getResponseHeader('X-Zotero-Modification-Time');
- info.mtime = parseInt(mtime);
- info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
- Zotero.debug(info);
-
- callback(item, info);
- });
+ info.filename = req.getResponseHeader('X-Zotero-Filename');
+ var mtime = req.getResponseHeader('X-Zotero-Modification-Time');
+ info.mtime = parseInt(mtime);
+ info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
+ Zotero.debug(info);
+
+ return info;
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ var msg = "Unexpected status code " + e.xmlhttp.status
+ + " getting storage file info";
+ Zotero.debug(msg, 1);
+ Zotero.debug(e.xmlhttp.responseText);
+ Components.utils.reportError(msg);
+ throw new Error(Zotero.Sync.Storage.defaultError);
+ }
+
+ throw e;
+ });
}
@@ -101,14 +102,14 @@ Zotero.Sync.Storage.ZFS = (function () {
var request = data.request;
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
- getStorageFileInfo(item, function (item, info) {
- if (request.isFinished()) {
- Zotero.debug("Upload request '" + request.name
- + "' is no longer running after getting file info");
- return;
- }
-
- try {
+ return getStorageFileInfo(item)
+ .then(function (info) {
+ if (request.isFinished()) {
+ Zotero.debug("Upload request '" + request.name
+ + "' is no longer running after getting file info");
+ return false;
+ }
+
// Check for conflict
if (Zotero.Sync.Storage.getSyncState(item.id)
!= Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
@@ -156,23 +157,25 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime);
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
Zotero.DB.commitTransaction();
- Zotero.Sync.Storage.EventManager.changesMade();
- request.finish();
- return;
+ return {
+ localChanges: true,
+ remoteChanges: false
+ };
}
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
if (!useLocal && smtime != mtime) {
- var localData = { modTime: fmtime };
- var remoteData = { modTime: mtime };
- Zotero.Sync.Storage.QueueManager.addConflict(
- request.name, localData, remoteData
- );
Zotero.debug("Conflict -- last synced file mod time "
+ "does not match time on storage server"
+ " (" + smtime + " != " + mtime + ")");
- request.finish();
- return;
+ return {
+ localChanges: false,
+ remoteChanges: false,
+ conflict: {
+ local: { modTime: fmtime },
+ remote: { modTime: mtime }
+ }
+ };
}
}
else {
@@ -180,26 +183,20 @@ Zotero.Sync.Storage.ZFS = (function () {
}
}
- getFileUploadParameters(
+ return getFileUploadParameters(
item,
function (item, target, uploadKey, params) {
- try {
- postFile(request, item, target, uploadKey, params);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
+ return postFile(request, item, target, uploadKey, params);
},
function () {
updateItemFileInfo(item);
- request.finish();
+ return {
+ localChanges: true,
+ remoteChanges: false
+ };
}
);
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
- });
+ });
}
@@ -212,6 +209,8 @@ Zotero.Sync.Storage.ZFS = (function () {
* on server and uploading isn't necessary
*/
function getFileUploadParameters(item, uploadCallback, existsCallback) {
+ var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
+
var uri = getItemURI(item);
if (Zotero.Attachments.getNumFiles(item) > 1) {
@@ -236,145 +235,137 @@ Zotero.Sync.Storage.ZFS = (function () {
body += "&zip=1";
}
- Zotero.HTTP.doPost(uri, body, function (req) {
- var funcName = "Zotero.Sync.Storage.ZFS.getFileUploadParameters()";
-
- if (req.status == 413) {
- var retry = req.getResponseHeader('Retry-After');
- if (retry) {
- var minutes = Math.round(retry / 60);
- // TODO: localize
- var e = new Zotero.Error(
- "You have too many queued uploads. "
- + "Please try again in " + minutes + " minutes.",
- "ZFS_UPLOAD_QUEUE_LIMIT"
- );
- Zotero.Sync.Storage.EventManager.error(e);
+ return Zotero.HTTP.promise("POST", uri, { body: body, debug: true })
+ .then(function (req) {
+ try {
+ // Strip XML declaration and convert to E4X
+ var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
+ }
+ catch (e) {
+ throw new Error("Invalid response retrieving file upload parameters");
}
- var text, buttonText = null, buttonCallback;
-
- // Group file
- if (item.libraryID) {
- var group = Zotero.Groups.getByLibraryID(item.libraryID);
- text = Zotero.getString('sync.storage.error.zfs.groupQuotaReached1', group.name) + "\n\n"
- + Zotero.getString('sync.storage.error.zfs.groupQuotaReached2');
+ if (xml.name() != 'upload' && xml.name() != 'exists') {
+ throw new Error("Invalid response retrieving file upload parameters");
}
- // Personal file
- else {
- text = Zotero.getString('sync.storage.error.zfs.personalQuotaReached1') + "\n\n"
- + Zotero.getString('sync.storage.error.zfs.personalQuotaReached2');
- buttonText = Zotero.getString('sync.storage.openAccountSettings');
- buttonCallback = function () {
- var url = "https://www.zotero.org/settings/storage";
+
+ // File was already available, so uploading isn't required
+ if (xml.name() == 'exists') {
+ existsCallback();
+ return false;
+ }
+
+ var url = xml.url.toString();
+ var uploadKey = xml.key.toString();
+ var params = {}, p = '';
+ for each(var param in xml.params.children()) {
+ params[param.name()] = param.toString();
+ }
+ Zotero.debug(params);
+ return uploadCallback(item, url, uploadKey, params);
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ if (e.status == 413) {
+ var retry = e.xmlhttp.getResponseHeader('Retry-After');
+ if (retry) {
+ var minutes = Math.round(retry / 60);
+ // TODO: localize
+ var e = new Zotero.Error(
+ "You have too many queued uploads. "
+ + "Please try again in " + minutes + " minutes.",
+ "ZFS_UPLOAD_QUEUE_LIMIT"
+ );
+ throw e;
+ }
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var win = wm.getMostRecentWindow("navigator:browser");
- var browser = win.getBrowser();
- browser.selectedTab = browser.addTab(url);
+ var text, buttonText = null, buttonCallback;
+
+ // Group file
+ if (item.libraryID) {
+ var group = Zotero.Groups.getByLibraryID(item.libraryID);
+ text = Zotero.getString('sync.storage.error.zfs.groupQuotaReached1', group.name) + "\n\n"
+ + Zotero.getString('sync.storage.error.zfs.groupQuotaReached2');
+ }
+ // Personal file
+ else {
+ text = Zotero.getString('sync.storage.error.zfs.personalQuotaReached1') + "\n\n"
+ + Zotero.getString('sync.storage.error.zfs.personalQuotaReached2');
+ buttonText = Zotero.getString('sync.storage.openAccountSettings');
+ buttonCallback = function () {
+ var url = "https://www.zotero.org/settings/storage";
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ var browser = win.getBrowser();
+ browser.selectedTab = browser.addTab(url);
+ }
+ }
+
+ // TODO: localize
+ text += "\n\n" + filename + " (" + Math.round(file.fileSize / 1024) + "KB)";
+
+ var e = new Zotero.Error(
+ "The file '" + filename + "' would exceed your Zotero File Storage quota",
+ "ZFS_OVER_QUOTA",
+ {
+ dialogText: text,
+ dialogButtonText: buttonText,
+ dialogButtonCallback: buttonCallback
+ }
+ );
+ Zotero.debug(e, 2);
+ Components.utils.reportError(e);
+ // Stop uploads from this library, log warning, and continue
+ Zotero.Sync.Storage.QueueManager.get('upload', item.libraryID).stop();
+ Zotero.Sync.Storage.EventLog.warning(e, item.libraryID);
+ return false;
}
+ else if (e.status == 403) {
+ var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
+ var e = new Zotero.Error(
+ "File editing denied for group",
+ "ZFS_FILE_EDITING_DENIED",
+ {
+ groupID: groupID
+ }
+ );
+ throw e;
+ }
+ else if (e.status == 404) {
+ Components.utils.reportError("Unexpected status code 404 in " + funcName
+ + " (" + Zotero.Items.getLibraryKeyHash(item) + ")");
+ if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
+ Components.utils.reportError("Skipping automatic client reset due to debug pref");
+ return;
+ }
+ if (!Zotero.Sync.Server.canAutoResetClient) {
+ Components.utils.reportError("Client has already been auto-reset -- manual sync required");
+ return;
+ }
+ Zotero.Sync.Server.resetClient();
+ Zotero.Sync.Server.canAutoResetClient = false;
+ throw new Error(Zotero.Sync.Storage.defaultError);
+ }
+
+ var msg = "Unexpected status code " + e.status + " in " + funcName
+ + " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
+ Zotero.debug(msg, 1);
+ Zotero.debug(e.xmlhttp.getAllResponseHeaders());
+ Components.utils.reportError(msg);
+ throw new Error(Zotero.Sync.Storage.defaultError);
}
- // TODO: localize
- text += "\n\n" + filename + " (" + Math.round(file.fileSize / 1024) + "KB)";
-
- Zotero.debug(req.responseText);
-
- var e = new Zotero.Error(
- "The file '" + filename + "' would exceed your Zotero File Storage quota",
- "ZFS_OVER_QUOTA",
- {
- dialogText: text,
- dialogButtonText: buttonText,
- dialogButtonCallback: buttonCallback
- }
- );
- Zotero.debug(e, 2);
- Components.utils.reportError(e);
- // Stop uploads, log warning, and continue
- Zotero.Sync.Storage.QueueManager.get('upload').stop();
- Zotero.Sync.Storage.EventManager.warning(e);
- Zotero.Sync.Storage.EventManager.success();
- return;
- }
- else if (req.status == 403) {
- Zotero.debug(req.responseText);
-
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
- var e = new Zotero.Error(
- "File editing denied for group",
- "ZFS_FILE_EDITING_DENIED",
- {
- groupID: groupID
- }
- );
- Zotero.Sync.Storage.EventManager.error(e);
- }
- else if (req.status == 404) {
- Components.utils.reportError("Unexpected status code 404 in " + funcName
- + " (" + Zotero.Items.getLibraryKeyHash(item) + ")");
- if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
- Components.utils.reportError("Skipping automatic client reset due to debug pref");
- return;
- }
- if (!Zotero.Sync.Server.canAutoResetClient) {
- Components.utils.reportError("Client has already been auto-reset -- manual sync required");
- return;
- }
- Zotero.Sync.Server.resetClient();
- Zotero.Sync.Server.canAutoResetClient = false;
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
- }
- else if (req.status != 200) {
- var msg = "Unexpected status code " + req.status + " in " + funcName
- + " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
- Zotero.debug(msg, 1);
- Zotero.debug(req.responseText);
- Zotero.debug(req.getAllResponseHeaders());
- Components.utils.reportError(msg);
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
- }
-
- Zotero.debug(req.responseText);
-
- try {
- // Strip XML declaration and convert to E4X
- var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, '')));
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(
- "Invalid response retrieving file upload parameters"
- );
- }
-
- if (xml.name() != 'upload' && xml.name() != 'exists') {
- Zotero.Sync.Storage.EventManager.error(
- "Invalid response retrieving file upload parameters"
- );
- }
- // File was already available, so uploading isn't required
- if (xml.name() == 'exists') {
- existsCallback();
- return;
- }
-
- var url = xml.url.toString();
- var uploadKey = xml.key.toString();
- var params = {}, p = '';
- for each(var param in xml.params.children()) {
- params[param.name()] = param.toString();
- }
- Zotero.debug(params);
- uploadCallback(item, url, uploadKey, params);
- });
+ throw e;
+ });
}
function postFile(request, item, url, uploadKey, params) {
if (request.isFinished()) {
Zotero.debug("Upload request " + request.name + " is no longer running after getting upload parameters");
- return;
+ return false;
}
var file = getUploadFile(item);
@@ -458,13 +449,22 @@ Zotero.Sync.Storage.ZFS = (function () {
request.setChannel(channel);
+ var deferred = Q.defer();
+
var listener = new Zotero.Sync.Storage.StreamListener(
{
onProgress: function (a, b, c) {
request.onProgress(a, b, c);
},
- onStop: function (httpRequest, status, response, data) { onUploadComplete(httpRequest, status, response, data); },
- onCancel: function (httpRequest, status, data) { onUploadCancel(httpRequest, status, data); },
+ onStop: function (httpRequest, status, response, data) {
+ deferred.resolve(
+ onUploadComplete(httpRequest, status, response, data)
+ );
+ },
+ onCancel: function (httpRequest, status, data) {
+ onUploadCancel(httpRequest, status, data)
+ deferred.resolve(false);
+ },
request: request,
item: item,
uploadKey: uploadKey,
@@ -480,6 +480,8 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug("HTTP POST of " + file.leafName + " to " + dispURI.spec);
channel.asyncOpen(listener, null);
+
+ return deferred.promise;
}
@@ -498,9 +500,7 @@ Zotero.Sync.Storage.ZFS = (function () {
break;
case 500:
- Zotero.Sync.Storage.EventManager.error(
- "File upload failed. Please try again."
- );
+ throw new Error("File upload failed. Please try again.");
default:
var msg = "Unexpected file upload status " + status
@@ -509,30 +509,31 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
Components.utils.reportError(response);
- Components.utils.reportError(msg);
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
+ throw new Error(Zotero.Sync.Storage.defaultError);
}
var uri = getItemURI(item);
var body = "update=" + uploadKey + "&mtime=" + item.attachmentModificationTime;
// Register upload on server
- Zotero.HTTP.doPost(uri, body, function (req) {
- if (req.status != 204) {
- var msg = "Unexpected file registration status " + req.status
- + " in Zotero.Sync.Storage.ZFS.onUploadComplete()"
+ return Zotero.HTTP.promise("POST", uri, { body: body, successCodes: [204] })
+ .then(function (req) {
+ updateItemFileInfo(item);
+ return {
+ localChanges: true,
+ remoteChanges: true
+ };
+ })
+ .fail(function (e) {
+ var msg = "Unexpected file registration status " + e.status
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
- Zotero.debug(req.responseText);
- Zotero.debug(req.getAllResponseHeaders());
+ Zotero.debug(e.xmlhttp.responseText);
+ Zotero.debug(e.xmlhttp.getAllResponseHeaders());
Components.utils.reportError(msg);
- Components.utils.reportError(req.responseText);
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
- }
-
- updateItemFileInfo(item);
- request.finish();
- });
+ Components.utils.reportError(e.xmlhttp.responseText);
+ throw new Error(Zotero.Sync.Storage.defaultError);
+ });
}
@@ -563,8 +564,6 @@ Zotero.Sync.Storage.ZFS = (function () {
catch (e) {
Components.utils.reportError(e);
}
-
- Zotero.Sync.Storage.EventManager.changesMade();
}
@@ -584,8 +583,6 @@ Zotero.Sync.Storage.ZFS = (function () {
catch (e) {
Components.utils.reportError(e);
}
-
- request.finish();
}
@@ -649,16 +646,12 @@ Zotero.Sync.Storage.ZFS = (function () {
}
});
- 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");
+ this._init();
}
return _rootURI.clone();
}
@@ -667,7 +660,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Object.defineProperty(obj, "userURI", {
get: function () {
if (!_userURI) {
- throw ("User URI not initialized in Zotero.Sync.Storage.ZFS.userURI");
+ this._init();
}
return _userURI.clone();
}
@@ -675,35 +668,23 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._init = function (url, username, password) {
+ _rootURI = false;
+ _userURI = false;
+
+ var url = ZOTERO_CONFIG.API_URL;
+ var username = Zotero.Sync.Server.username;
+ var password = Zotero.Sync.Server.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;
- }
+ var uri = ios.newURI(url, null, null);
+ uri.username = username;
+ uri.password = password;
_rootURI = uri;
uri = uri.clone();
uri.spec += 'users/' + Zotero.userID + '/';
_userURI = uri;
-
- 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);
};
@@ -719,20 +700,19 @@ Zotero.Sync.Storage.ZFS = (function () {
}
// 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 {
+ return getStorageFileInfo(item)
+ .then(function (info) {
+ if (!request.isRunning()) {
+ Zotero.debug("Download request '" + request.name
+ + "' is no longer running after getting remote file info");
+ return false;
+ }
+
+ if (!info) {
+ Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key);
+ return false;
+ }
+
var syncModTime = info.mtime;
var syncHash = info.hash;
@@ -749,9 +729,10 @@ Zotero.Sync.Storage.ZFS = (function () {
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;
+ return {
+ localChanges: true,
+ remoteChanges: false
+ };
}
// If not compressed, check hash, in case only timestamp changed
else if (!info.compressed && item.attachmentHash == syncHash) {
@@ -767,9 +748,10 @@ Zotero.Sync.Storage.ZFS = (function () {
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;
+ return {
+ localChanges: true,
+ remoteChanges: false
+ };
}
}
@@ -799,6 +781,8 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.File.checkFileAccessError(e, destFile, 'create');
}
+ var deferred = Q.defer();
+
var listener = new Zotero.Sync.Storage.StreamListener(
{
onStart: function (request, data) {
@@ -806,7 +790,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.debug("Download request " + data.request.name
+ " stopped before download started -- closing channel");
request.cancel(0x804b0002); // NS_BINDING_ABORTED
- return;
+ deferred.resolve(false);
}
},
onProgress: function (a, b, c) {
@@ -819,26 +803,31 @@ Zotero.Sync.Storage.ZFS = (function () {
+ " in Zotero.Sync.Storage.ZFS.downloadFile()";
Zotero.debug(msg, 1);
Components.utils.reportError(msg);
- Zotero.Sync.Storage.EventManager.error(Zotero.Sync.Storage.defaultError);
+ deferred.reject(Zotero.Sync.Storage.defaultError);
+ return;
}
// 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);
+ deferred.resolve(false);
return;
}
Zotero.debug("Finished download of " + destFile.path);
try {
- Zotero.Sync.Storage.processDownload(data);
- data.request.finish();
+ deferred.resolve(Zotero.Sync.Storage.processDownload(data));
}
catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
+ deferred.reject(e);
}
},
+ onCancel: function (request, status, data) {
+ Zotero.debug("Request cancelled");
+ deferred.resolve(false);
+ },
request: request,
item: item,
compressed: info.compressed,
@@ -868,151 +857,160 @@ Zotero.Sync.Storage.ZFS = (function () {
// XXX Always use when we no longer support Firefox < 18
wbp.saveURI(uri, null, null, null, null, destFile, null);
}
- }
- catch (e) {
- Zotero.Sync.Storage.EventManager.error(e);
- }
- });
+
+ return deferred.promise;
+ });
};
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); });
+ var deferred = Q.defer();
+ Zotero.Sync.Storage.createUploadFile(
+ request,
+ function (data) {
+ deferred.resolve(processUploadFile(data));
+ }
+ );
+ return deferred.promise;
}
else {
- processUploadFile({ request: request });
+ return processUploadFile({ request: request });
}
};
- obj._getLastSyncTime = function (callback) {
- var uri = this.userURI;
- var successFileURI = uri.clone();
- successFileURI.spec += "laststoragesync?auth=1";
+ /**
+ * @return {Promise} A promise for the last sync time
+ */
+ obj._getLastSyncTime = function (libraryID) {
+ var lastSyncURI = this._getLastSyncURI(libraryID);
- // 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) {
+ return Q.fcall(function () {
+ // Cache the credentials at the root
+ return self._cacheCredentials();
+ })
+ .then(function () {
+ return Zotero.HTTP.promise("GET", lastSyncURI,
+ { debug: true, successCodes: [200, 404] });
+ })
+ .then(function (req) {
+ // Not yet synced
+ if (req.status == 404) {
+ Zotero.debug("No last sync time for library " + libraryID);
+ return null;
+ }
+
+ var ts = req.responseText;
+ var date = new Date(ts * 1000);
+ Zotero.debug("Last successful ZFS sync for library "
+ + libraryID + " was " + date);
+ return ts;
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ if (e.status == 401 || e.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);
- });
+ return Q.reject(e);
+ }
+ // TODO: handle browser offline exception
+ else {
+ throw e;
+ }
});
};
- 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();
- }
-
+ obj._setLastSyncTime = function (libraryID, localLastSyncTime) {
+ if (localLastSyncTime) {
+ var sql = "REPLACE INTO version VALUES (?, ?)";
+ Zotero.DB.query(
+ sql, ['storage_zfs_' + libraryID, { int: localLastSyncTime }]
+ );
return;
}
- _lastSyncTime = null;
- var uri = this.userURI;
- var successFileURI = uri.clone();
- successFileURI.spec += "laststoragesync?auth=1";
+ var lastSyncURI = this._getLastSyncURI(libraryID);
- 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";
+ return Zotero.HTTP.promise("POST", lastSyncURI, { debug: true })
+ .then(function (req) {
+ var ts = req.responseText;
+
+ var sql = "REPLACE INTO version VALUES (?, ?)";
+ Zotero.DB.query(
+ sql, ['storage_zfs_' + libraryID, { int: ts }]
+ );
+ })
+ .fail(function (e) {
+ var msg = "Unexpected status code " + e.xmlhttp.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();
- }
- });
+ throw new Error(Zotero.Sync.Storage.defaultError);
+ });
};
- obj._cacheCredentials = function (callback) {
+ obj._getLastSyncURI = function (libraryID) {
+ if (libraryID === 0) {
+ var lastSyncURI = this.userURI;
+ }
+ else if (libraryID) {
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+ var uri = ios.newURI(Zotero.URI.getLibraryURI(libraryID), null, null);
+ var path = uri.path;
+ // We don't want the user URI, but it already has the right domain
+ // and credentials, so just start with that and replace the path
+ var lastSyncURI = this.userURI;
+ lastSyncURI.path = path + "/";
+ }
+ else {
+ throw new Error("libraryID not specified");
+ }
+ lastSyncURI.spec += "laststoragesync";
+ return lastSyncURI;
+ }
+
+
+ obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("Credentials are already cached");
- setTimeout(function () {
- callback();
- }, 0);
- return false;
+ return;
}
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;
+
+ return Zotero.HTTP.promise("GET", uri).
+ then(function (req) {
+ Zotero.debug("Credentials are cached");
+ _cachedCredentials = true;
+ })
+ .fail(function (e) {
+ if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
+ if (e.status == 401) {
+ var msg = "File sync login failed\n\n"
+ + "Check your username and password in the Sync "
+ + "pane of the Zotero preferences.";
+ throw (msg);
+ }
+
+ var msg = "Unexpected status code " + e.status + " "
+ + "caching ZFS credentials";
+ Zotero.debug(msg, 1);
+ throw (msg);
+ }
+ else {
+ throw (e);
+ }
+ });
};
@@ -1022,17 +1020,17 @@ Zotero.Sync.Storage.ZFS = (function () {
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;
+ return false;
}
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;
+ return false;
}
+ // TODO: promisify
+
Zotero.debug("Unlinking synced files on ZFS");
var uri = this.userURI;
@@ -1049,9 +1047,8 @@ Zotero.Sync.Storage.ZFS = (function () {
break;
default:
- Zotero.Sync.Storage.EventManager.error(
- "Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()"
- );
+ throw "Invalid zfsPurge value '" + value
+ + "' in ZFS purgeDeletedStorageFiles()";
}
}
uri.spec = uri.spec.substr(0, uri.spec.length - 1);
@@ -1061,9 +1058,7 @@ Zotero.Sync.Storage.ZFS = (function () {
if (callback) {
callback(false);
}
- Zotero.Sync.Storage.EventManager.error(
- "Unexpected status code " + xmlhttp.status + " purging ZFS files"
- );
+ throw "Unexpected status code " + xmlhttp.status + " purging ZFS files";
}
var sql = "DELETE FROM settings WHERE setting=? AND key=?";
@@ -1072,8 +1067,6 @@ Zotero.Sync.Storage.ZFS = (function () {
if (callback) {
callback(true);
}
-
- Zotero.Sync.Storage.EventManager.success();
});
};
diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js
index eaab772cf..863e8b383 100644
--- a/chrome/content/zotero/xpcom/sync.js
+++ b/chrome/content/zotero/xpcom/sync.js
@@ -505,12 +505,12 @@ Zotero.Sync.Runner = new function () {
var _autoSyncTimer;
var _queue;
- var _running;
var _background;
var _lastSyncStatus;
var _currentSyncStatusLabel;
var _currentLastSyncLabel;
+ var _errorsByLibrary = {};
var _warning = null;
@@ -526,16 +526,9 @@ Zotero.Sync.Runner = new function () {
this.clearSyncTimeout(); // DEBUG: necessary?
var msg = "Zotero cannot sync while " + Zotero.appName + " is in offline mode.";
var e = new Zotero.Error(msg, 0, { dialogButtonText: null })
- this.setSyncIcon('error', e);
- return false;
- }
-
- if (_running) {
- // TODO: show status in all windows
- var msg = "A sync process is already running. To view progress, check "
- + "the window in which the sync began or restart " + Zotero.appName + ".";
- var e = new Zotero.Error(msg, 0, { dialogButtonText: null, frontWindowOnly: true })
- this.setSyncIcon('error', e);
+ Components.utils.reportError(e);
+ Zotero.debug(e, 1);
+ this.setSyncIcon(e);
return false;
}
@@ -543,7 +536,6 @@ Zotero.Sync.Runner = new function () {
Zotero.purgeDataObjects(true);
_background = !!background;
- _running = true;
this.setSyncIcon('animate');
var finalCallbacks = {
@@ -554,61 +546,30 @@ Zotero.Sync.Runner = new function () {
};
var storageSync = function () {
- var syncNeeded = false;
-
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
- var zfsSync = function (skipSyncNeeded) {
- Zotero.Sync.Storage.ZFS.sync({
- // ZFS success
- onSuccess: function () {
- setTimeout(function () {
- Zotero.Sync.Server.sync(finalCallbacks);
- }, 0);
- },
-
- // ZFS skip
- onSkip: function () {
- setTimeout(function () {
- if (skipSyncNeeded) {
- Zotero.Sync.Server.sync(finalCallbacks);
- }
- else {
- Zotero.Sync.Runner.stop();
- }
- }, 0);
- },
-
- // ZFS cancel
- onStop: function () {
- setTimeout(function () {
- Zotero.Sync.Runner.stop();
- }, 0);
- },
-
- // ZFS failure
- onError: Zotero.Sync.Runner.error,
-
- onWarning: Zotero.Sync.Runner.warning
- })
- };
-
- Zotero.Sync.Storage.WebDAV.sync({
- // WebDAV success
- onSuccess: function () {
- zfsSync(true);
- },
+ Zotero.Sync.Storage.sync()
+ .then(function (results) {
+ Zotero.debug("File sync is finished");
- // WebDAV skip
- onSkip: function () {
- zfsSync();
- },
+ if (results.errors.length) {
+ Zotero.Sync.Runner.setErrors(results.errors);
+
+ return;
+ }
- // WebDAV cancel
- onStop: Zotero.Sync.Runner.stop,
-
- // WebDAV failure
- onError: Zotero.Sync.Runner.error
+ if (results.changesMade) {
+ Zotero.debug("Changes made during file sync "
+ + "-- performing additional data sync");
+ Zotero.Sync.Server.sync(finalCallbacks);
+ }
+ else {
+ Zotero.Sync.Runner.stop();
+ }
+ })
+ .fail(function (e) {
+ Zotero.debug("File sync failed", 1);
+ Zotero.Sync.Runner.error(e);
});
};
@@ -620,23 +581,26 @@ Zotero.Sync.Runner = new function () {
onSkip: storageSync,
// Sync 1 stop
- onStop: Zotero.Sync.Runner.stop,
+ onStop: function () {
+ Zotero.Sync.Runner.stop();
+ },
// Sync 1 error
- onError: Zotero.Sync.Runner.error
+ onError: function (e) {
+ Zotero.Sync.Runner.error(e);
+ }
});
}
this.stop = function () {
if (_warning) {
- Zotero.Sync.Runner.setSyncIcon('warning', _warning);
+ Zotero.Sync.Runner.setSyncIcon(_warning);
_warning = null;
}
else {
Zotero.Sync.Runner.setSyncIcon();
}
- _running = false;
}
@@ -644,14 +608,17 @@ Zotero.Sync.Runner = new function () {
* Log a warning, but don't throw an error
*/
this.warning = function (e) {
+ Zotero.debug(e, 2);
Components.utils.reportError(e);
+ e.status = 'warning';
_warning = e;
}
this.error = function (e) {
- Zotero.Sync.Runner.setSyncIcon('error', e);
- _running = false;
+ Components.utils.reportError(e);
+ Zotero.debug(e, 1);
+ Zotero.Sync.Runner.setSyncIcon(e);
throw (e);
}
@@ -740,60 +707,85 @@ Zotero.Sync.Runner = new function () {
}
- this.setSyncIcon = function (status, e) {
- var message;
- var buttonText;
- var buttonCallback;
- var frontWindowOnly = false;
+ /**
+ * Trigger updating of the main sync icon, the sync error icon, and
+ * library-specific sync error icons across all windows
+ */
+ this.setErrors = function (errors) {
+ Zotero.debug(errors);
+ errors = [this.parseSyncError(e) for each(e in errors)];
+ Zotero.debug(errors);
+ _errorsByLibrary = {};
- status = status ? status : '';
+ var primaryError = this.getPrimaryError(errors);
+ Zotero.debug(primaryError);
+ this.setSyncIcon(primaryError);
- switch (status) {
- case '':
- case 'animate':
- case 'warning':
- case 'error':
- break;
+ // Store other errors by libraryID to be shown in the source list
+ for each(var e in errors) {
+ // Skip non-library-specific errors
+ if (typeof e.libraryID == 'undefined') {
+ continue;
+ }
- default:
- throw ("Invalid sync icon status '" + status
- + "' in Zotero.Sync.Runner.setSyncIcon()");
+ if (!_errorsByLibrary[e.libraryID]) {
+ _errorsByLibrary[e.libraryID] = [];
+ }
+ _errorsByLibrary[e.libraryID].push(e);
}
- if (e) {
- if (e.data) {
- if (e.data.dialogText) {
- message = e.data.dialogText;
- }
- if (typeof e.data.dialogButtonText != 'undefined') {
- buttonText = e.data.dialogButtonText;
- buttonCallback = e.data.dialogButtonCallback;
- }
- if (e.data.frontWindowOnly) {
- frontWindowOnly = e.data.frontWindowOnly;
- }
+ // Refresh source list
+ Zotero.Notifier.trigger('redraw', 'collection', []);
+ }
+
+
+ this.getErrors = function (libraryID) {
+ if (!_errorsByLibrary[libraryID]) {
+ return false;
+ }
+ return _errorsByLibrary[libraryID];
+ }
+
+
+ this.getPrimaryError = function (errors) {
+ errors = [this.parseSyncError(e) for each(e in errors)];
+
+ // Set highest priority error as the primary (sync error icon)
+ var statusPriorities = {
+ info: 1,
+ warning: 2,
+ error: 3,
+ upgrade: 4,
+
+ // Skip these
+ animate: -1
+ };
+ var primaryError = false;
+ for each(var error in errors) {
+ if (!error.status || statusPriorities[error.status] == -1) {
+ continue;
}
- if (!message) {
- if (e.message) {
- message = e.message;
- }
- else {
- message = e;
- }
+ if (!primaryError || statusPriorities[error.status]
+ > statusPriorities[primaryError.status]) {
+ primaryError = error;
}
}
+ return primaryError;
+ }
+
+
+ /**
+ * Set the main sync error icon across all windows
+ */
+ this.setSyncIcon = function (e) {
+ e = this.parseSyncError(e);
- var upgradeRequired = false;
if (Zotero.Sync.Server.upgradeRequired) {
- upgradeRequired = true;
+ e.status = 'upgrade';
Zotero.Sync.Server.upgradeRequired = false;
}
- if (status == 'error') {
- var errorsLogged = Zotero.getErrors().length > 0;
- }
-
- if (frontWindowOnly) {
+ if (e.frontWindowOnly) {
// Fake an nsISimpleEnumerator with just the topmost window
var enumerator = {
_returned: false,
@@ -820,100 +812,17 @@ Zotero.Sync.Runner = new function () {
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
- if(!win.ZoteroPane) continue;
- var warning = win.ZoteroPane.document.getElementById('zotero-tb-sync-warning');
- var icon = win.ZoteroPane.document.getElementById('zotero-tb-sync');
+ if (!win.ZoteroPane) continue;
+ var doc = win.ZoteroPane.document;
- if (status == 'warning' || status == 'error') {
- icon.setAttribute('status', '');
- warning.hidden = false;
- if (upgradeRequired) {
- warning.setAttribute('mode', 'upgrade');
- buttonText = null;
- }
- else {
- warning.setAttribute('mode', status);
- }
- warning.tooltipText = message;
- warning.onclick = function () {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var win = wm.getMostRecentWindow("navigator:browser");
-
- var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
- // Warning
- if (status == 'warning') {
- var title = Zotero.getString('general.warning');
-
- // If secondary button not specified, just use an alert
- if (!buttonText) {
- ps.alert(null, title, message);
- return;
- }
-
- var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
- + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
- var index = ps.confirmEx(
- null,
- title,
- message,
- buttonFlags,
- "",
- buttonText,
- "", null, {}
- );
-
- if (index == 1) {
- setTimeout(function () { buttonCallback(); }, 1);
- }
- }
-
- // Error
- else if (status == 'error') {
- // Probably not necessary, but let's be sure
- if (!errorsLogged) {
- Components.utils.reportError(message);
- }
-
- if (typeof buttonText == 'undefined') {
- buttonText = Zotero.getString('errorReport.reportError');
- buttonCallback = function () {
- win.ZoteroPane.reportErrors();
- }
- }
- // If secondary button is explicitly null, just use an alert
- else if (buttonText === null) {
- ps.alert(null, title, message);
- return;
- }
-
- var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
- + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
- var index = ps.confirmEx(
- null,
- Zotero.getString('general.error'),
- message,
- buttonFlags,
- "",
- buttonText,
- "", null, {}
- );
-
- if (index == 1) {
- setTimeout(function () { buttonCallback(); }, 1);
- }
- }
- }
- }
- else {
- icon.setAttribute('status', status);
- warning.hidden = true;
- warning.onclick = null;
- }
+ var button = doc.getElementById('zotero-tb-sync-error');
+ this.setErrorIcon(button, [e]);
+ var syncIcon = doc.getElementById('zotero-tb-sync');
+ // Update sync icon state
+ syncIcon.setAttribute('status', e.status ? e.status : "");
// Disable button while spinning
- icon.disabled = status == 'animate';
+ syncIcon.disabled = e.status == 'animate';
}
// Clear status
@@ -921,6 +830,9 @@ Zotero.Sync.Runner = new function () {
}
+ /**
+ * Set the sync icon tooltip message
+ */
this.setSyncStatus = function (msg) {
_lastSyncStatus = msg;
@@ -931,6 +843,132 @@ Zotero.Sync.Runner = new function () {
}
+ this.parseSyncError = function (e) {
+ if (!e) {
+ return { parsed: true };
+ }
+
+ var parsed = {
+ parsed: true
+ };
+
+ // In addition to actual errors, string states (e.g., 'animate')
+ // can be passed
+ if (typeof e == 'string') {
+ parsed.status = e;
+ return parsed;
+ }
+
+ // Already parsed
+ if (e.parsed) {
+ return e;
+ }
+
+ if (typeof e.libraryID != 'undefined') {
+ parsed.libraryID = e.libraryID;
+ }
+ parsed.status = e.status ? e.status : 'error';
+
+ if (e.data) {
+ if (e.data.dialogText) {
+ parsed.message = e.data.dialogText;
+ }
+ if (typeof e.data.dialogButtonText != 'undefined') {
+ parsed.buttonText = e.data.dialogButtonText;
+ parsed.buttonCallback = e.data.dialogButtonCallback;
+ }
+ }
+ if (!parsed.message) {
+ parsed.message = e.message ? e.message : e;
+ }
+
+ parsed.frontWindowOnly = !!(e && e.data && e.data.frontWindowOnly);
+
+ return parsed;
+ }
+
+
+ /**
+ * Set the state of the sync error icon and add an onclick to populate
+ * the error panel
+ */
+ this.setErrorIcon = function (icon, errors) {
+ if (!errors || !errors.length) {
+ icon.hidden = true;
+ icon.onclick = null;
+ return;
+ }
+
+ // TEMP: for now, use the first error
+ var e = this.getPrimaryError(errors);
+
+ if (!e.status) {
+ icon.hidden = true;
+ icon.onclick = null;
+ return;
+ }
+
+ icon.hidden = false;
+ icon.setAttribute('mode', e.status);
+ icon.onclick = function () {
+ var doc = this.ownerDocument;
+
+ var panel = Zotero.Sync.Runner.updateErrorPanel(doc, errors);
+
+ panel.openPopup(this, "after_end", 4, 0, false, false);
+ }
+ }
+
+
+ this.updateErrorPanel = function (doc, errors) {
+ var panel = doc.getElementById('zotero-sync-error-panel');
+ var panelContent = doc.getElementById('zotero-sync-error-panel-content');
+ var panelButtons = doc.getElementById('zotero-sync-error-panel-buttons');
+
+ // Clear existing panel content
+ while (panelContent.hasChildNodes()) {
+ panelContent.removeChild(panelContent.firstChild);
+ }
+ while (panelButtons.hasChildNodes()) {
+ panelButtons.removeChild(panelButtons.firstChild);
+ }
+
+ // TEMP: for now, we only show one error
+ var e = errors.concat().shift();
+ e = this.parseSyncError(e);
+
+ var desc = doc.createElement('description');
+ desc.textContent = e.message;
+ panelContent.appendChild(desc);
+
+ // If not an error and there's no explicit button text, don't show
+ // button to report errors
+ if (e.status != 'error' && typeof e.buttonText == 'undefined') {
+ e.buttonText = null;
+ }
+
+ if (e.buttonText !== null) {
+ if (typeof e.buttonText == 'undefined') {
+ var buttonText = Zotero.getString('errorReport.reportError');
+ var buttonCallback = function () {
+ doc.defaultView.ZoteroPane.reportErrors();
+ };
+ }
+ else {
+ var buttonText = e.buttonText;
+ var buttonCallback = e.buttonCallback;
+ }
+
+ var button = doc.createElement('button');
+ button.setAttribute('label', buttonText);
+ button.onclick = buttonCallback;
+ panelButtons.appendChild(button);
+ }
+
+ return panel;
+ }
+
+
/**
* Register label in sync icon tooltip to receive updates
*
@@ -1440,7 +1478,7 @@ Zotero.Sync.Server = new function () {
Zotero.suppressUIUpdates = true;
_updatesInProgress = true;
- var errorHandler = function (e) {
+ var errorHandler = function (e, rethrow) {
Zotero.DB.rollbackTransaction();
Zotero.UnresponsiveScriptIndicator.enable();
@@ -1451,6 +1489,9 @@ Zotero.Sync.Server = new function () {
Zotero.suppressUIUpdates = false;
_updatesInProgress = false;
+ if (rethrow) {
+ throw (e);
+ }
_error(e);
}
@@ -1662,7 +1703,7 @@ Zotero.Sync.Server = new function () {
Zotero.pumpGenerator(gen, false, errorHandler);
}
catch (e) {
- errorHandler(e);
+ errorHandler(e, true);
}
}
catch (e) {
@@ -2987,17 +3028,16 @@ Zotero.Sync.Server.Data = new function() {
obj.attachmentSyncState =
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
}
- // Set existing attachments mtime update check
+ // Set existing attachments for mtime update check
else {
var mtime = objectNode.getAttribute('storageModTime');
if (mtime) {
- var lk = Zotero.Items.getLibraryKeyHash(obj)
// Convert previously used Unix timestamps to ms-based timestamps
if (mtime < 10000000000) {
Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
mtime = mtime * 1000;
}
- itemStorageModTimes[lk] = parseInt(mtime);
+ itemStorageModTimes[obj.id] = parseInt(mtime);
}
}
}
@@ -3313,18 +3353,8 @@ Zotero.Sync.Server.Data = new function() {
// Check mod times and hashes of updated items against stored values to see
// if they've been updated elsewhere and mark for download if so
- if (type == 'item') {
- var ids = [];
- var modTimes = {};
- for (var libraryKeyHash in itemStorageModTimes) {
- var lk = Zotero.Items.parseLibraryKeyHash(libraryKeyHash);
- var item = Zotero.Items.getByLibraryAndKey(lk.libraryID, lk.key);
- ids.push(item.id);
- modTimes[item.id] = itemStorageModTimes[libraryKeyHash];
- }
- if (ids.length > 0) {
- Zotero.Sync.Storage.checkForUpdatedFiles(ids, modTimes);
- }
+ if (type == 'item' && Object.keys(itemStorageModTimes).length) {
+ Zotero.Sync.Storage.checkForUpdatedFiles(itemStorageModTimes);
}
}
diff --git a/chrome/content/zotero/xpcom/uri.js b/chrome/content/zotero/xpcom/uri.js
index 6eb53393f..38511214a 100644
--- a/chrome/content/zotero/xpcom/uri.js
+++ b/chrome/content/zotero/xpcom/uri.js
@@ -83,12 +83,9 @@ Zotero.URI = new function () {
* Get path portion of library URI (e.g., users/6 or groups/1)
*/
this.getLibraryPath = function (libraryID) {
- if (libraryID) {
- var libraryType = Zotero.Libraries.getType(libraryID);
- }
- else {
- libraryType = 'user';
- }
+ libraryID = libraryID ? parseInt(libraryID) : 0;
+ var libraryType = Zotero.Libraries.getType(libraryID);
+
switch (libraryType) {
case 'user':
var id = Zotero.userID;
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 77f21169d..bf206d409 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -153,6 +153,7 @@ var ZoteroPane = new function()
var collectionsTree = document.getElementById('zotero-collections-tree');
collectionsTree.view = ZoteroPane_Local.collectionsView;
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
+ collectionsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
var itemsTree = document.getElementById('zotero-items-tree');
@@ -2509,11 +2510,32 @@ var ZoteroPane = new function()
var t = event.originalTarget;
var tree = t.parentNode;
- var itemGroup = ZoteroPane_Local.getItemGroup();
+ var row = {}, col = {}, obj = {};
+ tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
+ if (row.value == -1) {
+ return;
+ }
+
+ var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
+
+ // Prevent the tree's select event from being called for a click
+ // on a library sync error icon
+ if (tree.id == 'zotero-collections-tree') {
+ if (itemGroup.isLibrary(true)) {
+ if (col.value.id == 'zotero-collections-sync-status-column') {
+ var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
+ var errors = Zotero.Sync.Runner.getErrors(libraryID);
+ if (errors) {
+ event.stopPropagation();
+ return;
+ }
+ }
+ }
+ }
// Automatically select all equivalent items when clicking on an item
// in duplicates view
- if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
+ else if (tree.id == 'zotero-items-tree' && itemGroup.isDuplicates()) {
// Trigger only on primary-button single clicks with modifiers
// (so that items can still be selected and deselected manually)
if (!event || event.detail != 1 || event.button != 0 || event.metaKey || event.shiftKey) {
@@ -2558,22 +2580,52 @@ var ZoteroPane = new function()
var tree = t.parentNode;
+ var row = {}, col = {}, obj = {};
+ tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
+
// We care only about primary-button double and triple clicks
if (!event || (event.detail != 2 && event.detail != 3) || event.button != 0) {
+ if (row.value == -1) {
+ return;
+ }
+ var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
+
+ // Show the error panel when clicking a library-specific
+ // sync error icon
+ if (itemGroup.isLibrary(true)) {
+ if (col.value.id == 'zotero-collections-sync-status-column') {
+ var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
+ var errors = Zotero.Sync.Runner.getErrors(libraryID);
+ if (!errors) {
+ return;
+ }
+
+ var panel = Zotero.Sync.Runner.updateErrorPanel(window.document, errors);
+
+ var anchor = document.getElementById('zotero-collections-tree-shim');
+
+ var x = {}, y = {}, width = {}, height = {};
+ tree.treeBoxObject.getCoordsForCellItem(row.value, col.value, 'image', x, y, width, height);
+
+ x = x.value + Math.round(width.value / 2);
+ y = y.value + height.value + 3;
+
+ panel.openPopup(anchor, "after_start", x, y, false, false);
+ }
+
+ return;
+ }
+
// The Mozilla tree binding fires select() in mousedown(),
// but if when it gets to click() the selection differs from
// what it expects (say, because multiple items had been
- // selected during mousedown()), it fires select() again.
- // We prevent that here.
- var itemGroup = ZoteroPane_Local.getItemGroup();
- if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
+ // selected during mousedown(), as is the case in duplicates mode),
+ // it fires select() again. We prevent that here.
+ else if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
if (event.metaKey || event.shiftKey) {
return;
}
- // Allow twisty click to work in duplicates mode
- var row = {}, col = {}, obj = {};
- tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
if (obj.value == 'twisty') {
return;
}
@@ -2597,9 +2649,6 @@ var ZoteroPane = new function()
}
}
- var row = {}, col = {}, obj = {};
- tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
-
// obj.value == 'cell'/'text'/'image'
if (!obj.value) {
return;
@@ -3424,6 +3473,8 @@ var ZoteroPane = new function()
function viewAttachment(itemIDs, event, noLocateOnMissing, forceExternalViewer) {
+ Components.utils.import("resource://zotero/q.js");
+
// If view isn't editable, don't show Locate button, since the updated
// path couldn't be sent back up
if (!this.collectionsView.editable) {
@@ -3478,38 +3529,39 @@ var ZoteroPane = new function()
}
}
else {
- if (item.isImportedAttachment() && Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
- let downloadedItem = item;
- var started = Zotero.Sync.Storage.downloadFile(item, {
- onStart: function (request) {
- if (!(request instanceof Zotero.Sync.Storage.Request)) {
- throw new Error("Invalid request object");
- }
- },
-
- onProgress: function (progress, progressMax) {
-
- },
-
- onStop: function () {
- if (!downloadedItem.getFile()) {
- ZoteroPane_Local.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
- return;
- }
-
- // check if unchanged?
- // maybe not necessary, since we'll get an error if there's an error
-
- ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
- },
- });
-
- if (started) {
- continue;
- }
+ if (!item.isImportedAttachment() || !Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
+ this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
+ return;
}
- this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
+ let downloadedItem = item;
+ Q.fcall(function () {
+ return Zotero.Sync.Storage.downloadFile(
+ downloadedItem,
+ {
+ onProgress: function (progress, progressMax) {}
+ });
+ })
+ .then(function () {
+ if (!downloadedItem.getFile()) {
+ ZoteroPane_Local.showAttachmentNotFoundDialog(downloadedItem.id, noLocateOnMissing);
+ return;
+ }
+
+ // check if unchanged?
+ // maybe not necessary, since we'll get an error if there's an error
+
+
+ Zotero.Notifier.trigger('redraw', 'item', []);
+
+ ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
+ })
+ .fail(function (e) {
+ // TODO: show error somewhere else
+ Zotero.debug(e, 1);
+ ZoteroPane_Local.syncAlert(e);
+ })
+ .end();
}
}
}
@@ -3744,6 +3796,83 @@ var ZoteroPane = new function()
}
+ this.syncAlert = function (e) {
+ e = Zotero.Sync.Runner.parseSyncError(e);
+
+ var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK
+ + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
+
+ // Warning
+ if (e.status == 'warning') {
+ var title = Zotero.getString('general.warning');
+
+ // If secondary button not specified, just use an alert
+ if (e.buttonText) {
+ var buttonText = e.buttonText;
+ }
+ else {
+ ps.alert(null, title, e.message);
+ return;
+ }
+
+ var index = ps.confirmEx(
+ null,
+ title,
+ e.message,
+ buttonFlags,
+ "",
+ buttonText,
+ "", null, {}
+ );
+
+ if (index == 1) {
+ setTimeout(function () { buttonCallback(); }, 1);
+ }
+ }
+ // Error
+ else if (e.status == 'error') {
+ var title = Zotero.getString('general.error');
+
+ // If secondary button is explicitly null, just use an alert
+ if (buttonText === null) {
+ ps.alert(null, title, e.message);
+ return;
+ }
+
+ if (typeof buttonText == 'undefined') {
+ var buttonText = Zotero.getString('errorReport.reportError');
+ var buttonCallback = function () {
+ ZoteroPane.reportErrors();
+ };
+ }
+ else {
+ var buttonText = e.buttonText;
+ var buttonCallback = e.buttonCallback;
+ }
+
+ var index = ps.confirmEx(
+ null,
+ title,
+ e.message,
+ buttonFlags,
+ "",
+ buttonText,
+ "", null, {}
+ );
+
+ if (index == 1) {
+ setTimeout(function () { buttonCallback(); }, 1);
+ }
+ }
+ // Upgrade
+ else if (e.status == 'upgrade') {
+ ps.alert(null, "", e.message);
+ }
+ };
+
+
this.createParentItemsFromSelected = function () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
index 84a7ba4dd..aebfe7f96 100644
--- a/chrome/content/zotero/zoteroPane.xul
+++ b/chrome/content/zotero/zoteroPane.xul
@@ -192,32 +192,22 @@
value="0" tooltip="zotero-tb-sync-progress-tooltip">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index a0b9eabae..8da819807 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -683,9 +683,12 @@ sync.status.uploadingData = Uploading data to sync server
sync.status.uploadAccepted = Upload accepted \u2014 waiting for sync server
sync.status.syncingFiles = Syncing files
+sync.storage.mbRemaining = %SMB remaining
sync.storage.kbRemaining = %SKB remaining
sync.storage.filesRemaining = %1$S/%2$S files
sync.storage.none = None
+sync.storage.downloads = Downloads:
+sync.storage.uploads = Uploads:
sync.storage.localFile = Local File
sync.storage.remoteFile = Remote File
sync.storage.savedFile = Saved File
diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css
index 47bb89181..929335af7 100644
--- a/chrome/skin/default/zotero/overlay.css
+++ b/chrome/skin/default/zotero/overlay.css
@@ -20,11 +20,19 @@
min-height: 5.2em;
}
-#zotero-collections-tree treechildren::-moz-tree-image
+#zotero-collections-tree treechildren::-moz-tree-image(primary)
{
margin-right: 5px;
}
+#zotero-collections-tree #zotero-collections-sync-status-column {
+ width: 35px;
+}
+
+#zotero-collections-tree[hidevscroll] #zotero-collections-sync-status-column {
+ width: 21px;
+}
+
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
#zotero-collections-tree treechildren::-moz-tree-row(highlighted)
{
@@ -54,6 +62,145 @@
margin-right: 5px;
}
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie)
+{
+ margin: 1px 0 0;
+ list-style-image: url(chrome://zotero/skin/pie.png);
+ height: 16px;
+ -moz-image-region: rect(0px, 32px, 32px, 0px);
+}
+
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie1) { -moz-image-region: rect(0px, 32px, 32px, 0x); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie2) { -moz-image-region: rect(0px, 64px, 32px, 32px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie3) { -moz-image-region: rect(0px, 96px, 32px, 64px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie4) { -moz-image-region: rect(0px, 128px, 32px, 96px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie5) { -moz-image-region: rect(0px, 160px, 32px, 128px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie6) { -moz-image-region: rect(0px, 192px, 32px, 160px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie7) { -moz-image-region: rect(0px, 224px, 32px, 192px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie8) { -moz-image-region: rect(0px, 256px, 32px, 224px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie9) { -moz-image-region: rect(0px, 288px, 32px, 256px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie10) { -moz-image-region: rect(0px, 320px, 32px, 288px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie11) { -moz-image-region: rect(0px, 352px, 32px, 320px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie12) { -moz-image-region: rect(0px, 384px, 32px, 352px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie13) { -moz-image-region: rect(0px, 416px, 32px, 384px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie14) { -moz-image-region: rect(0px, 448px, 32px, 416px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie15) { -moz-image-region: rect(0px, 480px, 32px, 448px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie16) { -moz-image-region: rect(0px, 512px, 32px, 480px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie17) { -moz-image-region: rect(0px, 544px, 32px, 512px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie18) { -moz-image-region: rect(0px, 576px, 32px, 544px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie19) { -moz-image-region: rect(0px, 608px, 32px, 576px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie20) { -moz-image-region: rect(0px, 640px, 32px, 608px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie21) { -moz-image-region: rect(0px, 672px, 32px, 640px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie22) { -moz-image-region: rect(0px, 704px, 32px, 672px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie23) { -moz-image-region: rect(0px, 736px, 32px, 704px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie24) { -moz-image-region: rect(0px, 768px, 32px, 736px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie25) { -moz-image-region: rect(0px, 800px, 32px, 768px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie26) { -moz-image-region: rect(0px, 832px, 32px, 800px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie27) { -moz-image-region: rect(0px, 864px, 32px, 832px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie28) { -moz-image-region: rect(0px, 896px, 32px, 864px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie29) { -moz-image-region: rect(0px, 928px, 32px, 896px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie30) { -moz-image-region: rect(0px, 960px, 32px, 928px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie31) { -moz-image-region: rect(0px, 992px, 32px, 960px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie32) { -moz-image-region: rect(0px, 1024px, 32px, 992px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie33) { -moz-image-region: rect(0px, 1056px, 32px, 1024px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie34) { -moz-image-region: rect(0px, 1088px, 32px, 1056px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie35) { -moz-image-region: rect(0px, 1120px, 32px, 1088px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie36) { -moz-image-region: rect(0px, 1152px, 32px, 1120px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie37) { -moz-image-region: rect(0px, 1184px, 32px, 1152px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie38) { -moz-image-region: rect(0px, 1216px, 32px, 1184px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie39) { -moz-image-region: rect(0px, 1248px, 32px, 1216px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie40) { -moz-image-region: rect(0px, 1280px, 32px, 1248px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie41) { -moz-image-region: rect(0px, 1312px, 32px, 1280px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie42) { -moz-image-region: rect(0px, 1344px, 32px, 1312px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie43) { -moz-image-region: rect(0px, 1376px, 32px, 1344px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie44) { -moz-image-region: rect(0px, 1408px, 32px, 1376px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie45) { -moz-image-region: rect(0px, 1440px, 32px, 1408px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie46) { -moz-image-region: rect(0px, 1472px, 32px, 1440px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie47) { -moz-image-region: rect(0px, 1504px, 32px, 1472px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie48) { -moz-image-region: rect(0px, 1536px, 32px, 1504px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie49) { -moz-image-region: rect(0px, 1568px, 32px, 1536px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie50) { -moz-image-region: rect(0px, 1600px, 32px, 1568px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie51) { -moz-image-region: rect(0px, 1632px, 32px, 1600px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie52) { -moz-image-region: rect(0px, 1664px, 32px, 1632px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie53) { -moz-image-region: rect(0px, 1696px, 32px, 1664px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie54) { -moz-image-region: rect(0px, 1728px, 32px, 1696px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie55) { -moz-image-region: rect(0px, 1760px, 32px, 1728px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie56) { -moz-image-region: rect(0px, 1792px, 32px, 1760px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie57) { -moz-image-region: rect(0px, 1824px, 32px, 1792px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie58) { -moz-image-region: rect(0px, 1856px, 32px, 1824px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie59) { -moz-image-region: rect(0px, 1888px, 32px, 1856px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie60) { -moz-image-region: rect(0px, 1920px, 32px, 1888px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie61) { -moz-image-region: rect(0px, 1952px, 32px, 1920px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie62) { -moz-image-region: rect(0px, 1984px, 32px, 1952px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie63) { -moz-image-region: rect(0px, 2016px, 32px, 1984px); }
+#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie64) { -moz-image-region: rect(0px, 2048px, 32px, 2016px); }
+
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie1) { -moz-image-region: rect(32px, 32px, 64px, 0px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie2) { -moz-image-region: rect(32px, 64px, 64px, 32px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie3) { -moz-image-region: rect(32px, 96px, 64px, 64px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie4) { -moz-image-region: rect(32px, 128px, 64px, 96px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie5) { -moz-image-region: rect(32px, 160px, 64px, 128px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie6) { -moz-image-region: rect(32px, 192px, 64px, 160px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie7) { -moz-image-region: rect(32px, 224px, 64px, 192px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie8) { -moz-image-region: rect(32px, 256px, 64px, 224px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie9) { -moz-image-region: rect(32px, 288px, 64px, 256px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie10) { -moz-image-region: rect(32px, 320px, 64px, 288px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie11) { -moz-image-region: rect(32px, 352px, 64px, 320px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie12) { -moz-image-region: rect(32px, 384px, 64px, 352px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie13) { -moz-image-region: rect(32px, 416px, 64px, 384px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie14) { -moz-image-region: rect(32px, 448px, 64px, 416px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie15) { -moz-image-region: rect(32px, 480px, 64px, 448px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie16) { -moz-image-region: rect(32px, 512px, 64px, 480px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie17) { -moz-image-region: rect(32px, 544px, 64px, 512px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie18) { -moz-image-region: rect(32px, 576px, 64px, 544px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie19) { -moz-image-region: rect(32px, 608px, 64px, 576px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie20) { -moz-image-region: rect(32px, 640px, 64px, 608px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie21) { -moz-image-region: rect(32px, 672px, 64px, 640px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie22) { -moz-image-region: rect(32px, 704px, 64px, 672px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie23) { -moz-image-region: rect(32px, 736px, 64px, 704px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie24) { -moz-image-region: rect(32px, 768px, 64px, 736px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie25) { -moz-image-region: rect(32px, 800px, 64px, 768px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie26) { -moz-image-region: rect(32px, 832px, 64px, 800px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie27) { -moz-image-region: rect(32px, 864px, 64px, 832px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie28) { -moz-image-region: rect(32px, 896px, 64px, 864px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie29) { -moz-image-region: rect(32px, 928px, 64px, 896px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie30) { -moz-image-region: rect(32px, 960px, 64px, 928px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie31) { -moz-image-region: rect(32px, 992px, 64px, 960px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie32) { -moz-image-region: rect(32px, 1024px, 64px, 992px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie33) { -moz-image-region: rect(32px, 1056px, 64px, 1024px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie34) { -moz-image-region: rect(32px, 1088px, 64px, 1056px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie35) { -moz-image-region: rect(32px, 1120px, 64px, 1088px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie36) { -moz-image-region: rect(32px, 1152px, 64px, 1120px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie37) { -moz-image-region: rect(32px, 1184px, 64px, 1152px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie38) { -moz-image-region: rect(32px, 1216px, 64px, 1184px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie39) { -moz-image-region: rect(32px, 1248px, 64px, 1216px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie40) { -moz-image-region: rect(32px, 1280px, 64px, 1248px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie41) { -moz-image-region: rect(32px, 1312px, 64px, 1280px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie42) { -moz-image-region: rect(32px, 1344px, 64px, 1312px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie43) { -moz-image-region: rect(32px, 1376px, 64px, 1344px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie44) { -moz-image-region: rect(32px, 1408px, 64px, 1376px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie45) { -moz-image-region: rect(32px, 1440px, 64px, 1408px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie46) { -moz-image-region: rect(32px, 1472px, 64px, 1440px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie47) { -moz-image-region: rect(32px, 1504px, 64px, 1472px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie48) { -moz-image-region: rect(32px, 1536px, 64px, 1504px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie49) { -moz-image-region: rect(32px, 1568px, 64px, 1536px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie50) { -moz-image-region: rect(32px, 1600px, 64px, 1568px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie51) { -moz-image-region: rect(32px, 1632px, 64px, 1600px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie52) { -moz-image-region: rect(32px, 1664px, 64px, 1632px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie53) { -moz-image-region: rect(32px, 1696px, 64px, 1664px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie54) { -moz-image-region: rect(32px, 1728px, 64px, 1696px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie55) { -moz-image-region: rect(32px, 1760px, 64px, 1728px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie56) { -moz-image-region: rect(32px, 1792px, 64px, 1760px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie57) { -moz-image-region: rect(32px, 1824px, 64px, 1792px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie58) { -moz-image-region: rect(32px, 1856px, 64px, 1824px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie59) { -moz-image-region: rect(32px, 1888px, 64px, 1856px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie60) { -moz-image-region: rect(32px, 1920px, 64px, 1888px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie61) { -moz-image-region: rect(32px, 1952px, 64px, 1920px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie62) { -moz-image-region: rect(32px, 1984px, 64px, 1952px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie63) { -moz-image-region: rect(32px, 2016px, 64px, 1984px); }
+#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie64) { -moz-image-region: rect(32px, 2048px, 64px, 2016px); }
+
+
/* Set tag colors */
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
@@ -380,28 +527,58 @@
margin-left: 0;
}
-#zotero-tb-sync-progress-tooltip row label:first-child
-{
- text-align: right;
- font-weight: bold;
+#zotero-tb-sync-progress-tooltip-progress {
+ margin-bottom: 5px;
}
-#zotero-tb-sync-warning, #zotero-tb-sync-warning[mode=warning]
+/* Library names */
+#zotero-tb-sync-progress-tooltip rows > label
+{
+ font-weight: bold;
+ margin-top: 8px;
+}
+
+/* Queue names */
+#zotero-tb-sync-progress-tooltip row:not(.library-name) label:first-child
+{
+ text-align: right;
+}
+
+/* Sync error icon */
+#zotero-tb-sync-error, #zotero-tb-sync-error[mode=warning]
{
list-style-image: url(chrome://zotero/skin/error.png);
margin-right: -5px;
}
-#zotero-tb-sync-warning[mode=error]
+#zotero-tb-sync-error[mode=error]
{
list-style-image: url(chrome://zotero/skin/exclamation.png);
}
-#zotero-tb-sync-warning[mode=upgrade]
+#zotero-tb-sync-error[mode=upgrade]
{
list-style-image: url(chrome://zotero/skin/bell_error.png);
}
+#zotero-tb-sync-error {
+ /*border: 1px orange dashed;*/
+}
+
+/* Sync error panel */
+#zotero-sync-error-panel {
+ margin-right: 0px;
+}
+
+#zotero-sync-error-panel description {
+ width: 350px;
+ white-space: pre-wrap;
+}
+
+#zotero-sync-error-panel-buttons {
+ -moz-box-pack: end;
+}
+
#zotero-tb-sync {
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
margin-left: -6px;
diff --git a/chrome/skin/default/zotero/pie.png b/chrome/skin/default/zotero/pie.png
new file mode 100644
index 000000000..26037e3ba
Binary files /dev/null and b/chrome/skin/default/zotero/pie.png differ
diff --git a/chrome/skin/default/zotero/zotero.css b/chrome/skin/default/zotero/zotero.css
index e1963ad1e..ed6e9f3a6 100644
--- a/chrome/skin/default/zotero/zotero.css
+++ b/chrome/skin/default/zotero/zotero.css
@@ -156,6 +156,11 @@ zoteroguidancepanel
-moz-binding: url('chrome://zotero/content/bindings/columnpicker.xml#extended-columnpicker');
}
+zoterofilesyncstatus {
+ -moz-binding: url('chrome://zotero/content/bindings/filesyncstatus.xml#file-sync-status');
+}
+
+
label.zotero-text-link {
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
-moz-user-focus: normal;
diff --git a/components/zotero-service.js b/components/zotero-service.js
index 589d14cf3..ed58fbe11 100644
--- a/components/zotero-service.js
+++ b/components/zotero-service.js
@@ -95,7 +95,7 @@ const xpcomFilesLocal = [
'sync',
'storage',
'storage/streamListener',
- 'storage/eventManager',
+ 'storage/eventLog',
'storage/queueManager',
'storage/queue',
'storage/request',