diff --git a/chrome.manifest b/chrome.manifest
index 8fb8da2b7..b3210ceb1 100644
--- a/chrome.manifest
+++ b/chrome.manifest
@@ -54,6 +54,7 @@ overlay chrome://zotero/content/preferences/preferences.xul chrome://zotero/cont
overlay chrome://zotero/content/preferences/preferences.xul#cite chrome://zotero/content/preferences/preferences_firefox.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
overlay chrome://zotero/content/preferences/preferences_general.xul chrome://zotero/content/preferences/preferences_general_firefox.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
overlay chrome://zotero/content/preferences/preferences_export.xul chrome://zotero/content/preferences/preferences_export_firefox.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+overlay chrome://zotero/content/preferences/preferences_keys.xul chrome://zotero/content/preferences/preferences_keys_firefox.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
overlay chrome://zotero/content/preferences/preferences_advanced.xul chrome://zotero/content/preferences/preferences_advanced_firefox.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
overlay chrome://zotero/content/preferences/preferences_advanced.xul chrome://zotero/content/preferences/preferences_advanced_standalone.xul application=zotero@chnm.gmu.edu
diff --git a/chrome/content/zotero/bindings/attachmentbox.xml b/chrome/content/zotero/bindings/attachmentbox.xml
index a6f902cf1..0630c6fd8 100644
--- a/chrome/content/zotero/bindings/attachmentbox.xml
+++ b/chrome/content/zotero/bindings/attachmentbox.xml
@@ -538,7 +538,7 @@
-
+
diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul
index 61091ca1e..126a714fd 100644
--- a/chrome/content/zotero/overlay.xul
+++ b/chrome/content/zotero/overlay.xul
@@ -94,6 +94,10 @@
+ modifiers="accel shift" />
+
diff --git a/chrome/content/zotero/preferences/preferences_keys.js b/chrome/content/zotero/preferences/preferences_keys.js
index 3c544cd75..04ee513f4 100644
--- a/chrome/content/zotero/preferences/preferences_keys.js
+++ b/chrome/content/zotero/preferences/preferences_keys.js
@@ -27,10 +27,10 @@
Zotero_Preferences.Keys = {
init: function () {
- // Display the appropriate modifier keys for the platform
var rows = document.getElementById('zotero-prefpane-keys').getElementsByTagName('row');
for (var i=0; i
+
+
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -87,9 +64,21 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -109,9 +98,9 @@
-
+
-
+
diff --git a/chrome/content/zotero/preferences/preferences_keys_firefox.xul b/chrome/content/zotero/preferences/preferences_keys_firefox.xul
new file mode 100644
index 000000000..5b402e599
--- /dev/null
+++ b/chrome/content/zotero/preferences/preferences_keys_firefox.xul
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js
index 0d6e66698..a94e81277 100644
--- a/chrome/content/zotero/standalone/standalone.js
+++ b/chrome/content/zotero/standalone/standalone.js
@@ -38,6 +38,7 @@ const ZoteroStandalone = new function() {
window.close();
return;
}
+ _checkRoot();
ZoteroPane.init();
ZoteroPane.makeVisible();
@@ -146,6 +147,32 @@ const ZoteroStandalone = new function() {
this.onUnload = function() {
ZoteroPane.destroy();
}
+
+ /**
+ * Warn if Zotero Standalone is running as root and clobber the cache directory
+ */
+ function _checkRoot() {
+ if(!Zotero.isWin) {
+ var env = Components.classes["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ var user = env.get("USER") || env.get("USERNAME");
+ if(user === "root") {
+ // Zap cache files
+ try {
+ Services.dirsvc.get("ProfLD", Components.interfaces.nsIFile).remove(true);
+ } catch(e) {}
+ // Warn user never to do this again
+ if(Services.prompt.confirmEx(null, "", Zotero.getString("standalone.rootWarning"),
+ Services.prompt.BUTTON_POS_0*Services.prompt.BUTTON_TITLE_IS_STRING |
+ Services.prompt.BUTTON_POS_1*Services.prompt.BUTTON_TITLE_IS_STRING,
+ Zotero.getString("standalone.rootWarning.exit"),
+ Zotero.getString("standalone.rootWarning.continue"),
+ null, null, {}) == 0) {
+ goQuitApplication();
+ }
+ }
+ }
+ }
}
/** Taken from browser.js **/
diff --git a/chrome/content/zotero/standalone/updatesOverlay.xul b/chrome/content/zotero/standalone/updatesOverlay.xul
new file mode 100644
index 000000000..3c9049ebc
--- /dev/null
+++ b/chrome/content/zotero/standalone/updatesOverlay.xul
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/test/test.xul b/chrome/content/zotero/test/test.xul
index c06b8a25b..fa428c4b2 100644
--- a/chrome/content/zotero/test/test.xul
+++ b/chrome/content/zotero/test/test.xul
@@ -11,7 +11,7 @@
style="padding:2em">
-
+
diff --git a/chrome/content/zotero/xpcom/citeproc.js b/chrome/content/zotero/xpcom/citeproc.js
index 988806ddb..37b751f5b 100644
--- a/chrome/content/zotero/xpcom/citeproc.js
+++ b/chrome/content/zotero/xpcom/citeproc.js
@@ -57,7 +57,7 @@ if (!Array.indexOf) {
};
}
var CSL = {
- PROCESSOR_VERSION: "1.0.470",
+ PROCESSOR_VERSION: "1.0.471",
CONDITION_LEVEL_TOP: 1,
CONDITION_LEVEL_BOTTOM: 2,
PLAIN_HYPHEN_REGEX: /(?:[^\\]-|\u2013)/,
@@ -2860,7 +2860,7 @@ CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
if (blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
span_split = ret.length;
}
- var blobs_start = state.output.renderBlobs(ret.slice(0, span_split), blob_delimiter, true);
+ var blobs_start = state.output.renderBlobs(ret.slice(0, span_split), blob_delimiter, true, blob);
if (blobs_start && blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
if (!state.tmp.suppress_decorations) {
for (i = 0, ilen = blob.decorations.length; i < ilen; i += 1) {
@@ -2925,7 +2925,7 @@ CSL.Output.Queue.prototype.clearlevel = function () {
blob.blobs.pop();
}
};
-CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite) {
+CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent) {
var state, ret, ret_last_char, use_delim, i, blob, pos, len, ppos, llen, pppos, lllen, res, str, params, txt_esc;
txt_esc = CSL.getSafeEscape(this.state);
if (!delim) {
@@ -2936,6 +2936,11 @@ CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite) {
ret_last_char = [];
use_delim = "";
len = blobs.length;
+ if (this.state.tmp.area === "citation" && !this.state.tmp.just_looking && len === 1 && typeof blobs[0] === "object" && parent) {
+ blobs[0].strings.prefix = parent.strings.prefix + blobs[0].strings.prefix;
+ blobs[0].strings.suffix = blobs[0].strings.suffix + parent.strings.suffix;
+ return blobs[0];
+ }
var start = true;
for (pos = 0; pos < len; pos += 1) {
if (blobs[pos].checkNext) {
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
index 17af51602..23864d2e0 100644
--- a/chrome/content/zotero/xpcom/collectionTreeView.js
+++ b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -1725,7 +1725,16 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
try {
Zotero.DB.beginTransaction();
- var itemID = Zotero.Attachments.importFromFile(file, false, targetLibraryID);
+ if (dragData.dropEffect == 'link') {
+ var itemID = Zotero.Attachments.linkFromFile(file);
+ }
+ else {
+ if (dragData.dropEffect != 'copy') {
+ Components.utils.reportError("Invalid dropEffect '" + dragData.dropEffect + "' dropping file");
+ }
+ var itemID = Zotero.Attachments.importFromFile(file, false, targetLibraryID);
+ }
+
if (parentCollectionID) {
var col = Zotero.Collections.get(parentCollectionID);
if (col) {
@@ -1751,17 +1760,38 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
Zotero.CollectionTreeView.prototype.onDragEnter = function (event) {
- //Zotero.debug("Storing current drag data");
Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
+ return false;
}
/*
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
-Zotero.CollectionTreeView.prototype.onDragOver = function (event, dropdata, session) {
+Zotero.CollectionTreeView.prototype.onDragOver = function (event) {
+ Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
+ if (event.dataTransfer.types.contains("application/x-moz-file")) {
+ // As of Aug. 2013 nightlies:
+ //
+ // - Setting the dropEffect only works on Linux and OS X.
+ //
+ // - Modifier keys don't show up in the drag event on OS X until the
+ // drop, so since we can't show a correct effect, we leave it at
+ // the default 'move', the least misleading option.
+ //
+ // - The cursor effect gets set by the system on Windows 7 and can't
+ // be overridden.
+ if (!Zotero.isMac) {
+ if (event.ctrlKey && event.shiftKey) {
+ event.dataTransfer.dropEffect = "link";
+ }
+ else {
+ event.dataTransfer.dropEffect = "copy";
+ }
+ }
+ }
// Show copy symbol when dragging an item over a collection
- if (event.dataTransfer.getData("zotero/item")) {
+ else if (event.dataTransfer.getData("zotero/item")) {
event.dataTransfer.dropEffect = "copy";
}
return false;
@@ -1771,7 +1801,8 @@ Zotero.CollectionTreeView.prototype.onDragOver = function (event, dropdata, sess
/*
* Called by HTML 5 Drag and Drop when dropping onto the tree
*/
-Zotero.CollectionTreeView.prototype.onDrop = function (event, dropdata, session) {
+Zotero.CollectionTreeView.prototype.onDrop = function (event) {
+ Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
return false;
}
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
index 243feaa5c..d4531d182 100644
--- a/chrome/content/zotero/xpcom/data/item.js
+++ b/chrome/content/zotero/xpcom/data/item.js
@@ -132,6 +132,7 @@ Zotero.Item.prototype.__defineSetter__('relatedItems', function (arr) { this._se
Zotero.Item.prototype.__defineGetter__('relatedItemsReverse', function () { var ids = this._getRelatedItemsReverse(); return ids; });
Zotero.Item.prototype.__defineGetter__('relatedItemsBidirectional', function () { var ids = this._getRelatedItemsBidirectional(); return ids; });
+Zotero.Item.prototype.__defineGetter__('libraryKey', function () this.libraryIDInt + "/" + this.key);
Zotero.Item.prototype.getID = function() {
Zotero.debug('Item.getID() is deprecated -- use Item.id');
@@ -2792,31 +2793,6 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
};
}
- // Update file existence state of this item
- // and best attachment state of parent item
- var self = this;
- var updateAttachmentStates = function (exists) {
- self._fileExists = exists;
-
- if (self.isTopLevelItem()) {
- return;
- }
-
- try {
- var parentKey = self.getSource();
- }
- // This can happen during classic sync conflict resolution, if a
- // standalone attachment was modified locally and remotely was changed
- // into a child attachment
- catch (e) {
- Zotero.debug("Attachment parent doesn't exist for source key "
- + "in Zotero.Item.updateAttachmentStates()", 1);
- return;
- }
-
- Zotero.Items.get(parentKey).updateBestAttachmentState();
- };
-
// No associated files for linked URLs
if (row.linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
return false;
@@ -2824,7 +2800,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
if (!row.path) {
Zotero.debug("Attachment path is empty", 2);
- updateAttachmentStates(false);
+ this._updateAttachmentStates(false);
return false;
}
@@ -2873,7 +2849,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
}
catch (e) {
Zotero.debug('Invalid persistent descriptor', 2);
- updateAttachmentStates(false);
+ this._updateAttachmentStates(false);
return false;
}
}
@@ -2883,7 +2859,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
row.path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
var file = Zotero.Attachments.resolveRelativePath(row.path);
if (!file) {
- updateAttachmentStates(false);
+ this._updateAttachmentStates(false);
return false;
}
}
@@ -2909,7 +2885,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
}
catch (e) {
Zotero.debug('Invalid relative descriptor', 2);
- updateAttachmentStates(false);
+ this._updateAttachmentStates(false);
return false;
}
}
@@ -2917,15 +2893,41 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
if (!skipExistsCheck && !file.exists()) {
Zotero.debug("Attachment file '" + file.path + "' not found", 2);
- updateAttachmentStates(false);
+ this._updateAttachmentStates(false);
return false;
}
- updateAttachmentStates(true);
+ this._updateAttachmentStates(true);
return file;
}
+/**
+ * Update file existence state of this item and best attachment state of parent item
+ */
+Zotero.Item.prototype._updateAttachmentStates = function (exists) {
+ this._fileExists = exists;
+
+ if (this.isTopLevelItem()) {
+ return;
+ }
+
+ try {
+ var parentKey = this.getSource();
+ }
+ // This can happen during classic sync conflict resolution, if a
+ // standalone attachment was modified locally and remotely was changed
+ // into a child attachment
+ catch (e) {
+ Zotero.debug("Attachment parent doesn't exist for source key "
+ + "in Zotero.Item.updateAttachmentStates()", 1);
+ return;
+ }
+
+ Zotero.Items.get(parentKey).updateBestAttachmentState();
+}
+
+
Zotero.Item.prototype.getFilename = function () {
if (!this.isAttachment()) {
throw ("getFileName() can only be called on attachment items in Zotero.Item.getFilename()");
diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js
index b5acc583e..a38255204 100644
--- a/chrome/content/zotero/xpcom/data/libraries.js
+++ b/chrome/content/zotero/xpcom/data/libraries.js
@@ -36,7 +36,7 @@ Zotero.Libraries = new function () {
break;
default:
- throw ("Invalid library type '" + type + "' in Zotero.Libraries.add()");
+ throw new Error("Invalid library type '" + type + "' in Zotero.Libraries.add()");
}
var sql = "INSERT INTO libraries (libraryID, libraryType) VALUES (?, ?)";
@@ -57,7 +57,7 @@ Zotero.Libraries = new function () {
return group.name;
default:
- throw ("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
+ throw new Error("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
}
}
@@ -69,7 +69,7 @@ Zotero.Libraries = new function () {
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
var libraryType = Zotero.DB.valueQuery(sql, libraryID);
if (!libraryType) {
- throw ("Library " + libraryID + " does not exist in Zotero.Libraries.getType()");
+ throw new Error("Library " + libraryID + " does not exist in Zotero.Libraries.getType()");
}
return libraryType;
}
@@ -87,7 +87,7 @@ Zotero.Libraries = new function () {
return group.editable;
default:
- throw ("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
+ throw new Error("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
}
}
@@ -104,7 +104,7 @@ Zotero.Libraries = new function () {
return group.filesEditable;
default:
- throw ("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
+ throw new Error("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
}
}
}
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
index 77a13f851..26d1b14f3 100644
--- a/chrome/content/zotero/xpcom/integration.js
+++ b/chrome/content/zotero/xpcom/integration.js
@@ -1553,7 +1553,7 @@ Zotero.Integration.Fields.prototype._updateDocument = function(forceCitations, f
} catch(e) {
Zotero.logError(e);
}
- yield;
+ yield undefined;
}
var citation = this._session.citationsByIndex[i];
@@ -1661,7 +1661,7 @@ Zotero.Integration.Fields.prototype._updateDocument = function(forceCitations, f
} catch(e) {
Zotero.logError(e);
}
- yield;
+ yield undefined;
}
if(bibliographyText) {
@@ -2649,7 +2649,7 @@ Zotero.Integration.Session.prototype._updateCitations = function() {
}
this.citeprocCitationIDs[citation.citationID] = true;
delete this.newIndices[index];
- yield;
+ yield undefined;
}
}
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
index 04ff52950..fde9c11b3 100644
--- a/chrome/content/zotero/xpcom/itemTreeView.js
+++ b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -2990,7 +2990,15 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
try {
Zotero.DB.beginTransaction();
- var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID);
+ if (dragData.dropEffect == 'link') {
+ var itemID = Zotero.Attachments.linkFromFile(file, sourceItemID);
+ }
+ else {
+ if (dragData.dropEffect != 'copy') {
+ Components.utils.reportError("Invalid dropEffect '" + dragData.dropEffect + "' dropping file");
+ }
+ var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID);
+ }
if (parentCollectionID) {
var col = Zotero.Collections.get(parentCollectionID);
if (col) {
@@ -3012,21 +3020,46 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
}
Zotero.ItemTreeView.prototype.onDragEnter = function (event) {
- //Zotero.debug("Storing current drag data");
Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
+ return false;
}
/*
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
-Zotero.ItemTreeView.prototype.onDragOver = function (event, dropdata, session) {
+Zotero.ItemTreeView.prototype.onDragOver = function (event) {
+ Zotero.DragDrop.currentDataTransfer = event.dataTransfer;
+ if (event.dataTransfer.types.contains("application/x-moz-file")) {
+ // As of Aug. 2013 nightlies:
+ //
+ // - Setting the dropEffect only works on Linux and OS X.
+ //
+ // - Modifier keys don't show up in the drag event on OS X until the
+ // drop, so since we can't show a correct effect, we leave it at
+ // the default 'move', the least misleading option.
+ //
+ // - The cursor effect gets set by the system on Windows 7 and can't
+ // be overridden.
+ if (!Zotero.isMac) {
+ if (event.ctrlKey && event.shiftKey) {
+ event.dataTransfer.dropEffect = "link";
+ }
+ else {
+ event.dataTransfer.dropEffect = "copy";
+ }
+ }
+ }
+ // Show copy symbol when dragging an item over a collection
+ else if (event.dataTransfer.getData("zotero/item")) {
+ event.dataTransfer.dropEffect = "copy";
+ }
return false;
}
/*
* Called by HTML 5 Drag and Drop when dropping onto the tree
*/
-Zotero.ItemTreeView.prototype.onDrop = function (event, dropdata, session) {
+Zotero.ItemTreeView.prototype.onDrop = function (event) {
return false;
}
diff --git a/chrome/content/zotero/xpcom/notifier.js b/chrome/content/zotero/xpcom/notifier.js
index 0845ccb6c..b83cd0b67 100644
--- a/chrome/content/zotero/xpcom/notifier.js
+++ b/chrome/content/zotero/xpcom/notifier.js
@@ -27,7 +27,7 @@ Zotero.Notifier = new function(){
var _observers = {};
var _disabled = false;
var _types = [
- 'collection', 'creator', 'search', 'share', 'share-items', 'item',
+ 'collection', 'creator', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
];
var _inTransaction;
diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js
index 95e4e6e22..e6cd79a7c 100644
--- a/chrome/content/zotero/xpcom/storage.js
+++ b/chrome/content/zotero/xpcom/storage.js
@@ -75,17 +75,23 @@ Zotero.Sync.Storage = new function () {
}
}
+ Zotero.Notifier.registerObserver(this, ['file']);
+
+
//
// Private properties
//
+ var _maxCheckAgeInSeconds = 10800; // maximum age for upload modification check (3 hours)
var _syncInProgress;
var _updatesInProgress;
var _itemDownloadPercentages = {};
+ var _uploadCheckFiles = [];
+ var _lastFullFileCheck = {};
- this.sync = function (libraries) {
- if (libraries) {
- Zotero.debug("Starting file sync for libraries " + libraries);
+ this.sync = function (options) {
+ if (options.libraries) {
+ Zotero.debug("Starting file sync for libraries " + options.libraries);
}
else {
Zotero.debug("Starting file sync");
@@ -100,7 +106,7 @@ Zotero.Sync.Storage = new function () {
return Q.fcall(function () {
// TODO: Make sure modes are active
- if (libraries && libraries.indexOf(0) == -1) {
+ if (options.libraries && options.libraries.indexOf(0) == -1) {
return;
}
@@ -116,7 +122,7 @@ Zotero.Sync.Storage = new function () {
if (Zotero.Sync.Storage.ZFS.includeGroupFiles) {
var groups = Zotero.Groups.getAll();
for each(var group in groups) {
- if (libraries && libraries.indexOf(group.libraryID) == -1) {
+ if (options.libraries && options.libraries.indexOf(group.libraryID) == -1) {
continue;
}
// TODO: if library file syncing enabled
@@ -136,9 +142,9 @@ Zotero.Sync.Storage = new function () {
&& !Zotero.Sync.Storage.WebDAV.verified) {
Zotero.debug("WebDAV file sync is not active");
var promise = Zotero.Sync.Storage.checkServerPromise(Zotero.Sync.Storage.WebDAV)
- .then(function () {
- mode.cacheCredentials();
- });
+ .then(function () {
+ return mode.cacheCredentials();
+ });
}
else {
var promise = mode.cacheCredentials();
@@ -146,6 +152,7 @@ Zotero.Sync.Storage = new function () {
promises.push(Q.allResolved([mode, promise]));
}
}
+
return Q.all(promises)
// Get library last-sync times
.then(function (cacheCredentialsPromises) {
@@ -212,79 +219,111 @@ Zotero.Sync.Storage = new function () {
}
});
- // Queue files to download and upload from each library
+ // Check for updated files to upload in each library
+ var promises = [];
for (let libraryID in librarySyncTimes) {
- var lastSyncTime = librarySyncTimes[libraryID];
+ let promise;
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
- // or doesn't exist on the server (meaning there are no files)
- if (downloadAll && !downloadForced) {
- if (lastSyncTime) {
- var version = self.getStoredLastSyncTime(
- libraryModes[libraryID], libraryID
- );
- if (version == lastSyncTime) {
- Zotero.debug("Last " + libraryModes[libraryID].name
- + " sync id hasn't changed for library "
- + libraryID + " -- skipping file downloads");
+ if (!Zotero.Libraries.isFilesEditable(libraryID)) {
+ Zotero.debug("No file editing access -- skipping file "
+ + "modification check for library " + libraryID);
+ continue;
+ }
+ // If this is a background sync, it's not the first sync of
+ // the session, the library has had at least one full check
+ // this session, and it's been less than _maxCheckAgeInSeconds
+ // since the last full check of this library, check only files
+ // that were previously modified or opened recently
+ else if (options.background
+ && !options.firstInSession
+ && _lastFullFileCheck[libraryID]
+ && (_lastFullFileCheck[libraryID] + (_maxCheckAgeInSeconds * 1000))
+ > new Date().getTime()) {
+ let itemIDs = _getFilesToCheck(libraryID);
+ promise = self.checkForUpdatedFiles(libraryID, itemIDs);
+ }
+ // Otherwise check all files in the library
+ else {
+ _lastFullFileCheck[libraryID] = new Date().getTime();
+ promise = self.checkForUpdatedFiles(libraryID);
+ }
+ promises.push(promise);
+ }
+ return Q.all(promises)
+ .then(function () {
+ // Queue files to download and upload from each library
+ for (let libraryID in librarySyncTimes) {
+ libraryID = parseInt(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
+ // or doesn't exist on the server (meaning there are no files)
+ if (downloadAll && !downloadForced) {
+ let lastSyncTime = librarySyncTimes[libraryID];
+ if (lastSyncTime) {
+ var version = self.getStoredLastSyncTime(
+ libraryModes[libraryID], libraryID
+ );
+ if (version == lastSyncTime) {
+ Zotero.debug("Last " + libraryModes[libraryID].name
+ + " sync id hasn't changed for library "
+ + libraryID + " -- skipping file downloads");
+ downloadAll = false;
+ }
+ }
+ else {
+ Zotero.debug("No last " + libraryModes[libraryID].name
+ + " sync time for library " + libraryID
+ + " -- skipping file downloads");
downloadAll = false;
}
}
+
+ if (downloadAll || downloadForced) {
+ for each(var itemID in _getFilesToDownload(libraryID, !downloadAll)) {
+ var item = Zotero.Items.get(itemID);
+ self.queueItem(item);
+ }
+ }
+
+ // Get files to upload
+ if (Zotero.Libraries.isFilesEditable(libraryID)) {
+ for each(var itemID in _getFilesToUpload(libraryID)) {
+ var item = Zotero.Items.get(itemID);
+ self.queueItem(item);
+ }
+ }
else {
- Zotero.debug("No last " + libraryModes[libraryID].name
- + " sync time for library " + libraryID
- + " -- skipping file downloads");
- downloadAll = false;
+ Zotero.debug("No file editing access -- skipping file uploads for library " + libraryID);
}
}
- if (downloadAll || downloadForced) {
- for each(var itemID in _getFilesToDownload(libraryID, !downloadAll)) {
- var item = Zotero.Items.get(itemID);
- self.queueItem(item);
- }
+ // Start queues for each library
+ for (let libraryID in librarySyncTimes) {
+ libraryID = parseInt(libraryID);
+ libraryQueues.push(Q.allResolved(
+ [libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
+ ));
}
- // Get files to upload
- if (Zotero.Libraries.isFilesEditable(libraryID)) {
- for each(var itemID in _getFilesToUpload(libraryID)) {
- var item = Zotero.Items.get(itemID);
- self.queueItem(item);
- }
- }
- else {
- Zotero.debug("No file editing access -- skipping file uploads for library " + libraryID);
- }
- }
-
- // Start queues for each library
- for (let libraryID in librarySyncTimes) {
- libraryID = parseInt(libraryID);
- libraryQueues.push(Q.allResolved(
- [libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
- ));
- }
-
- // The promise is done when all libraries are done
- return Q.all(libraryQueues);
+ // The promise is done when all libraries are done
+ return Q.all(libraryQueues);
+ });
})
.then(function (promises) {
Zotero.debug('Queue manager is finished');
@@ -642,209 +681,405 @@ Zotero.Sync.Storage = new function () {
* Scans local files and marks any that have changed for uploading
* and any that are missing for downloading
*
- * @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
- * matching the stored time will
- * be marked for download
- * @param {Boolean} [includePersonalItems=false]
- * @param {Boolean} [includeGroupItems=false]
- * @return {Boolean} TRUE if any items changed state,
- * FALSE otherwise
+ * @param {Integer} [libraryID]
+ * @param {Integer[]} [itemIDs]
+ * @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
+ * matching the stored time will
+ * be marked for download
+ * @return {Promise} Promise resolving to TRUE if any items changed state,
+ * FALSE otherwise
*/
- this.checkForUpdatedFiles = function (itemModTimes, libraryID) {
- var msg = "Checking for locally changed attachment files";
-
- if (typeof libraryID != 'undefined') {
- msg += " in library " + libraryID;
- if (itemModTimes) {
- throw new Error("libraryID is not allowed when itemModTimes is set");
- }
+ this.checkForUpdatedFiles = function (libraryID, itemIDs, itemModTimes) {
+ libraryID = parseInt(libraryID);
+ if (isNaN(libraryID)) {
+ libraryID = false;
}
- else {
- if (!itemModTimes) {
- return false;
- }
- }
- Zotero.debug(msg);
- var changed = false;
-
- var itemIDs = Object.keys(itemModTimes ? itemModTimes : {});
-
- // Can only handle 999 bound parameters at a time
- var numIDs = itemIDs.length;
- var maxIDs = 990;
- var done = 0;
- var rows = [];
-
- Zotero.DB.beginTransaction();
-
- do {
- var chunk = itemIDs.splice(0, maxIDs);
- var sql = "SELECT itemID, linkMode, path, storageModTime, storageHash, syncState "
- + "FROM itemAttachments JOIN items USING (itemID) "
- + "WHERE linkMode IN (?,?) AND syncState IN (?,?)";
- 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);
- }
- var chunkRows = Zotero.DB.query(sql, params);
- if (chunkRows) {
- rows = rows.concat(chunkRows);
- }
- done += chunk.length;
- }
- while (done < numIDs);
-
- // 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') {
+ Components.utils.import("resource://gre/modules/Task.jsm");
+ return Q(Task.spawn(function () {
+ var msg = "Checking for locally changed attachment files";
+
+ var memmgr = Components.classes["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Components.interfaces.nsIMemoryReporterManager);
+ memmgr.init();
+ Zotero.debug("Memory usage: " + memmgr.resident);
+
+ if (libraryID !== false) {
+ if (itemIDs) {
+ if (!itemIDs.length) {
+ var msg = "No files to check for local changes in library " + libraryID;
+ Zotero.debug(msg);
+ throw new Task.Result(false);
+ }
+ }
+ if (itemModTimes) {
+ throw new Error("itemModTimes is not allowed when libraryID is set");
+ }
+
msg += " in library " + libraryID;
}
- Zotero.debug(msg);
- Zotero.DB.commitTransaction();
- return changed;
- }
-
- // Index attachment data by item id
- var itemIDs = [];
- var attachmentData = {};
- for each(var row in rows) {
- var id = row.itemID;
- itemIDs.push(id);
- attachmentData[id] = {
- linkMode: row.linkMode,
- path: row.path,
- mtime: row.storageModTime,
- hash: row.storageHash,
- state: row.syncState
- };
- }
- 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 " + lk + " as missing");
- updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
- continue;
+ else if (itemIDs) {
+ throw new Error("libraryID not provided");
}
-
- // 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);
-
- // Download-marking mode
- if (itemModTimes) {
- Zotero.debug("Remote mod time for item " + lk + " is " + itemModTimes[item.id]);
-
- // 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 item " + lk);
- continue;
+ else if (itemModTimes) {
+ if (!Object.keys(itemModTimes).length) {
+ throw new Task.Result(false);
}
-
- Zotero.debug("Marking attachment " + lk + " for download");
- updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD;
- }
-
- var mtime = attachmentData[item.id].mtime;
-
- // If stored time matches file, it hasn't changed locally
- if (mtime == fmtime) {
- continue;
- }
-
- // 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
- + " for item " + lk + " -- ignoring");
- continue;
- }
-
- // Allow timestamp to be exactly one hour off to get around
- // time zone issues -- there may be a proper way to fix this
- 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) {
- 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 hash matches stored hash, only the mod time changed, so skip
- var f = item.getFile();
- if (f) {
- Zotero.debug(f.path);
+ msg += " in download-marking mode";
}
else {
- Zotero.debug("File for item " + lk + " missing before getting hash");
+ throw new Error("libraryID, itemIDs, or itemModTimes must be provided");
}
- 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 + " for item " + lk
- + " -- updating file mod time");
- try {
- file.lastModifiedTime = attachmentData[item.id].mtime;
- }
- catch (e) {
- Zotero.File.checkFileAccessError(e, file, 'update');
- }
- continue;
+ Zotero.debug(msg);
+
+ var changed = false;
+
+ if (!itemIDs) {
+ itemIDs = Object.keys(itemModTimes ? itemModTimes : {});
}
- // Mark file for upload
- Zotero.debug("Marking attachment " + lk + " as changed "
- + "(" + mtime + " != " + fmtime + ")");
- updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD;
- }
-
- for (var itemID in updatedStates) {
- Zotero.Sync.Storage.setSyncState(itemID, updatedStates[itemID]);
- changed = true;
- }
-
- if (!changed) {
- Zotero.debug("No synced files have changed locally");
- }
-
- Zotero.DB.commitTransaction();
- return changed;
- }
+ // Can only handle 999 bound parameters at a time
+ var numIDs = itemIDs.length;
+ var maxIDs = 990;
+ var done = 0;
+ var rows = [];
+
+ Zotero.DB.beginTransaction();
+
+ do {
+ var chunk = itemIDs.splice(0, maxIDs);
+ var sql = "SELECT itemID, linkMode, path, storageModTime, storageHash, syncState "
+ + "FROM itemAttachments JOIN items USING (itemID) "
+ + "WHERE linkMode IN (?,?) AND syncState IN (?,?)";
+ 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 (libraryID !== false) {
+ sql += " AND libraryID=?";
+ params.push(libraryID == 0 ? null : libraryID);
+ }
+ if (chunk.length) {
+ sql += " AND itemID IN (" + chunk.map(function () '?').join() + ")";
+ params = params.concat(chunk);
+ }
+ var chunkRows = Zotero.DB.query(sql, params);
+ if (chunkRows) {
+ rows = rows.concat(chunkRows);
+ }
+ done += chunk.length;
+ }
+ while (done < numIDs);
+
+ Zotero.DB.commitTransaction();
+
+ // 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 (libraryID !== false) {
+ msg += " in library " + libraryID;
+ }
+ Zotero.debug(msg);
+ throw new Task.Result(changed);
+ }
+
+ // Index attachment data by item id
+ itemIDs = [];
+ var attachmentData = {};
+ for each(let row in rows) {
+ var id = row.itemID;
+ itemIDs.push(id);
+ attachmentData[id] = {
+ linkMode: row.linkMode,
+ path: row.path,
+ mtime: row.storageModTime,
+ hash: row.storageHash,
+ state: row.syncState
+ };
+ }
+ rows = null;
+
+ var t = new Date();
+ var items = Zotero.Items.get(itemIDs);
+ var numItems = items.length;
+ var updatedStates = {};
+
+ // OS.File didn't work reliably before Firefox 23, so use the old code
+ if (Zotero.platformMajorVersion < 23) {
+ Zotero.debug("Performing synchronous file update check");
+
+ for each(var item in items) {
+ // Spin the event loop during synchronous file access
+ yield Q.delay(1);
+
+ Zotero.debug("Memory usage: " + memmgr.resident);
+
+ let row = attachmentData[item.id];
+ let lk = item.libraryID + "/" + item.key;
+ Zotero.debug("Checking attachment file for item " + lk);
+
+ var file = item.getFile(row);
+ if (!file) {
+ 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 (row.state == Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD) {
+ continue;
+ }
+
+ var fmtime = item.attachmentModificationTime;
+
+ //Zotero.debug("Stored mtime is " + row.mtime);
+ //Zotero.debug("File mtime is " + fmtime);
+
+ // Download-marking mode
+ if (itemModTimes) {
+ Zotero.debug("Remote mod time for item " + lk + " is " + itemModTimes[item.id]);
+
+ // 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 item " + lk);
+ continue;
+ }
+
+ Zotero.debug("Marking attachment " + lk + " for download");
+ updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD;
+ }
+
+ var mtime = row.mtime;
+
+ // If stored time matches file, it hasn't changed locally
+ if (mtime == fmtime) {
+ continue;
+ }
+
+ // 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
+ + " for item " + lk + " -- ignoring");
+ continue;
+ }
+
+ // Allow timestamp to be exactly one hour off to get around
+ // time zone issues -- there may be a proper way to fix this
+ 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) {
+ 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 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 for item " + lk + " missing before getting hash");
+ }
+ var fileHash = item.attachmentHash;
+ if (row.hash && row.hash == fileHash) {
+ Zotero.debug("Mod time didn't match (" + fmtime + "!=" + mtime + ") "
+ + "but hash did for " + file.leafName + " for item " + lk
+ + " -- updating file mod time");
+ try {
+ file.lastModifiedTime = row.mtime;
+ }
+ catch (e) {
+ Zotero.File.checkFileAccessError(e, file, 'update');
+ }
+ continue;
+ }
+
+ // Mark file for upload
+ Zotero.debug("Marking attachment " + lk + " as changed "
+ + "(" + mtime + " != " + fmtime + ")");
+ updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD;
+ }
+
+ for (var itemID in updatedStates) {
+ Zotero.Sync.Storage.setSyncState(itemID, updatedStates[itemID]);
+ changed = true;
+ }
+
+ if (!changed) {
+ Zotero.debug("No synced files have changed locally");
+ }
+
+ Zotero.debug("Checked " + numItems + " files in " + (new Date() - t) + "ms");
+
+ throw new Task.Result(changed);
+ }
+
+ Components.utils.import("resource://gre/modules/osfile.jsm")
+
+ let checkItems = function () {
+ if (!items.length) return;
+
+ Zotero.debug("Memory usage: " + memmgr.resident);
+
+ let item = items.shift();
+ let row = attachmentData[item.id];
+ let lk = item.libraryKey;
+ Zotero.debug("Checking attachment file for item " + lk);
+
+ let nsIFile = item.getFile(row, true);
+ let file = null;
+ return Q(OS.File.open(nsIFile.path))
+ .then(function (promisedFile) {
+ file = promisedFile;
+ return file.stat()
+ .then(function (info) {
+ Zotero.debug("Memory usage: " + memmgr.resident);
+
+ var fmtime = info.lastModificationDate.getTime();
+ Zotero.debug("File modification time for item " + lk + " is " + fmtime);
+
+ if (fmtime < 1) {
+ Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2);
+ fmtime = 1;
+ }
+
+ // 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 (row.state == Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD) {
+ return;
+ }
+
+ //Zotero.debug("Stored mtime is " + row.mtime);
+ //Zotero.debug("File mtime is " + fmtime);
+
+ // Download-marking mode
+ if (itemModTimes) {
+ Zotero.debug("Remote mod time for item " + lk + " is " + itemModTimes[item.id]);
+
+ // 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 item " + lk);
+ return;
+ }
+
+ Zotero.debug("Marking attachment " + lk + " for download");
+ updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD;
+ }
+
+ var mtime = row.mtime;
+
+ // If stored time matches file, it hasn't changed locally
+ if (mtime == fmtime) {
+ return;
+ }
+
+ // 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
+ + " for item " + lk + " -- ignoring");
+ return;
+ }
+
+ // Allow timestamp to be exactly one hour off to get around
+ // time zone issues -- there may be a proper way to fix this
+ 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) {
+ Zotero.debug("File mod time (" + fmtime + ") is exactly one "
+ + "hour off remote file (" + mtime + ") for item " + lk
+ + "-- assuming time zone issue and skipping upload");
+ return;
+ }
+
+ // If file hash matches stored hash, only the mod time changed, so skip
+ return Zotero.Utilities.Internal.md5Async(file)
+ .then(function (fileHash) {
+ if (row.hash && row.hash == fileHash) {
+ Zotero.debug("Mod time didn't match (" + fmtime + "!=" + mtime + ") "
+ + "but hash did for " + file.leafName + " for item " + lk
+ + " -- updating file mod time");
+ try {
+ nsIFile.lastModifiedTime = row.mtime;
+ }
+ catch (e) {
+ Zotero.File.checkFileAccessError(e, nsIFile, 'update');
+ }
+ return;
+ }
+
+ // Mark file for upload
+ Zotero.debug("Marking attachment " + lk + " as changed "
+ + "(" + mtime + " != " + fmtime + ")");
+ updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD;
+ });
+ });
+ })
+ .finally(function () {
+ if (file) {
+ Zotero.debug("Closing file for item " + lk);
+ file.close();
+ }
+ })
+ .catch(function (e) {
+ if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
+ Zotero.debug("Marking attachment " + lk + " as missing");
+ updatedStates[item.id] = Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
+ return;
+ }
+
+ if (e instanceof OS.File.Error && e.becauseClosed) {
+ Zotero.debug("File was closed", 2);
+ }
+ else {
+ Zotero.debug(e);
+ Zotero.debug(e.toString());
+ }
+ throw new Error("Error " + e.operation + " " + nsIFile.path);
+ })
+ .then(function () {
+ return checkItems();
+ });
+ };
+
+ throw new Task.Result(checkItems()
+ .then(function () {
+ for (let itemID in updatedStates) {
+ Zotero.Sync.Storage.setSyncState(itemID, updatedStates[itemID]);
+ changed = true;
+ }
+
+ if (!changed) {
+ Zotero.debug("No synced files have changed locally");
+ }
+
+ Zotero.debug("Checked " + numItems + " files in " + (new Date() - t) + "ms");
+
+ return changed;
+ }));
+ }));
+ };
/**
@@ -1144,6 +1379,20 @@ Zotero.Sync.Storage = new function () {
}
+ this.notify = function(event, type, ids, extraData) {
+ if (event == 'open' && type == 'file') {
+ let timestamp = new Date().getTime();
+
+ for each(let id in ids) {
+ _uploadCheckFiles.push({
+ itemID: id,
+ timestamp: timestamp
+ });
+ }
+ }
+ }
+
+
//
// Private methods
//
@@ -1774,6 +2023,36 @@ Zotero.Sync.Storage = new function () {
}
+ /**
+ * Get files to check for local modifications for uploading
+ *
+ * This includes files previously modified and files opened externally
+ * via Zotero within _maxCheckAgeInSeconds.
+ */
+ function _getFilesToCheck(libraryID) {
+ var minTime = new Date().getTime() - (_maxCheckAgeInSeconds * 1000);
+
+ // Get files by modification time
+ var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
+ + "WHERE libraryID=? AND linkMode IN (?,?) AND syncState IN (?) AND "
+ + "storageModTime>=?";
+ var params = [
+ libraryID == 0 ? null : libraryID,
+ Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
+ Zotero.Attachments.LINK_MODE_IMPORTED_URL,
+ Zotero.Sync.Storage.SYNC_STATE_IN_SYNC,
+ minTime
+ ];
+ var itemIDs = Zotero.DB.columnQuery(sql, params) || [];
+
+ // Get files by open time
+ _uploadCheckFiles.filter(function (x) x.timestamp >= minTime);
+ itemIDs = itemIDs.concat([x.itemID for each(x in _uploadCheckFiles)])
+
+ return Zotero.Utilities.arrayUnique(itemIDs);
+ }
+
+
/**
* @inner
* @return {String[]|FALSE} Array of keys, or FALSE if none
diff --git a/chrome/content/zotero/xpcom/storage/mode.js b/chrome/content/zotero/xpcom/storage/mode.js
index 303c517e7..472b6a101 100644
--- a/chrome/content/zotero/xpcom/storage/mode.js
+++ b/chrome/content/zotero/xpcom/storage/mode.js
@@ -74,8 +74,8 @@ Zotero.Sync.Storage.Mode.prototype.checkServerCallback = function (uri, status,
return this._checkServerCallback(uri, status, window, skipSuccessMessage);
}
-Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function (callback) {
- return this._cacheCredentials(callback);
+Zotero.Sync.Storage.Mode.prototype.cacheCredentials = function () {
+ return this._cacheCredentials();
}
Zotero.Sync.Storage.Mode.prototype.purgeDeletedStorageFiles = function (callback) {
diff --git a/chrome/content/zotero/xpcom/storage/streamListener.js b/chrome/content/zotero/xpcom/storage/streamListener.js
index e4e6779f3..28f8c32eb 100644
--- a/chrome/content/zotero/xpcom/storage/streamListener.js
+++ b/chrome/content/zotero/xpcom/storage/streamListener.js
@@ -44,17 +44,12 @@ Zotero.Sync.Storage.StreamListener.prototype = {
// nsIProgressEventSink
onProgress: function (request, context, progress, progressMax) {
- // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=451991
- // (fixed in Fx3.1)
- if (progress > progressMax) {
- progress = progressMax;
- }
- //Zotero.debug("onProgress with " + progress + "/" + progressMax);
+ Zotero.debug("onProgress with " + progress + "/" + progressMax);
this._onProgress(request, progress, progressMax);
},
onStatus: function (request, context, status, statusArg) {
- //Zotero.debug('onStatus');
+ Zotero.debug('onStatus with ' + status);
},
// nsIRequestObserver
@@ -67,7 +62,7 @@ Zotero.Sync.Storage.StreamListener.prototype = {
},
onStopRequest: function (request, context, status) {
- Zotero.debug('onStopRequest');
+ Zotero.debug('onStopRequest with ' + status);
switch (status) {
case 0:
@@ -84,7 +79,7 @@ Zotero.Sync.Storage.StreamListener.prototype = {
// nsIWebProgressListener
onProgressChange: function (wp, request, curSelfProgress,
maxSelfProgress, curTotalProgress, maxTotalProgress) {
- //Zotero.debug("onProgressChange with " + curTotalProgress + "/" + maxTotalProgress);
+ Zotero.debug("onProgressChange with " + curTotalProgress + "/" + maxTotalProgress);
// onProgress gets called too, so this isn't necessary
//this._onProgress(request, curTotalProgress, maxTotalProgress);
@@ -108,8 +103,12 @@ Zotero.Sync.Storage.StreamListener.prototype = {
onStatusChange: function (progress, request, status, message) {
Zotero.debug("onStatusChange with '" + message + "'");
},
- onLocationChange: function () {},
- onSecurityChange: function () {},
+ onLocationChange: function () {
+ Zotero.debug('onLocationChange');
+ },
+ onSecurityChange: function () {
+ Zotero.debug('onSecurityChange');
+ },
// nsIStreamListener
onDataAvailable: function (request, context, stream, sourceOffset, length) {
@@ -119,7 +118,9 @@ Zotero.Sync.Storage.StreamListener.prototype = {
.createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableInputStream.init(stream);
- this._response += scriptableInputStream.read(length);
+ var data = scriptableInputStream.read(length);
+ Zotero.debug(data);
+ this._response += data;
},
// nsIChannelEventSink
diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js
index a35aa9fb0..d88389bae 100644
--- a/chrome/content/zotero/xpcom/storage/webdav.js
+++ b/chrome/content/zotero/xpcom/storage/webdav.js
@@ -1102,7 +1102,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
obj._cacheCredentials = function () {
if (_cachedCredentials) {
- Zotero.debug("Credentials are already cached");
+ Zotero.debug("WebDAV credentials are already cached");
return;
}
diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js
index 59dd06670..422c3ef51 100644
--- a/chrome/content/zotero/xpcom/storage/zfs.js
+++ b/chrome/content/zotero/xpcom/storage/zfs.js
@@ -86,7 +86,7 @@ Zotero.Sync.Storage.ZFS = (function () {
}
else {
var msg = "Unexpected status code " + e.xmlhttp.status
- + " getting storage file info";
+ + " getting storage file info for item " + item.libraryKey;
}
Zotero.debug(msg, 1);
Zotero.debug(e.xmlhttp.responseText);
@@ -1002,8 +1002,8 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._cacheCredentials = function () {
if (_cachedCredentials) {
- Zotero.debug("Credentials are already cached");
- return;
+ Zotero.debug("ZFS credentials are already cached");
+ return Q();
}
var uri = this.rootURI;
diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js
index 60e4a4d4d..bde044c4e 100644
--- a/chrome/content/zotero/xpcom/sync.js
+++ b/chrome/content/zotero/xpcom/sync.js
@@ -520,6 +520,7 @@ Zotero.Sync.Runner = new function () {
var _autoSyncTimer;
var _queue;
var _background;
+ var _firstInSession = true;
var _lastSyncStatus;
var _currentSyncStatusLabel;
@@ -533,7 +534,13 @@ Zotero.Sync.Runner = new function () {
this.IdleListener.init();
}
- this.sync = function (background) {
+ this.sync = function (options) {
+ if (!options) options = {};
+ if (_firstInSession) {
+ options.firstInSession = true;
+ _firstInSession = false;
+ }
+
_warning = null;
if (Zotero.HTTP.browserIsOffline()){
@@ -549,7 +556,7 @@ Zotero.Sync.Runner = new function () {
// Purge deleted objects so they don't cause sync errors (e.g., long tags)
Zotero.purgeDataObjects(true);
- _background = !!background;
+ _background = !!options.background;
this.setSyncIcon('animate');
var finalCallbacks = {
@@ -562,7 +569,7 @@ Zotero.Sync.Runner = new function () {
var storageSync = function () {
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
- Zotero.Sync.Storage.sync()
+ Zotero.Sync.Storage.sync(options)
.then(function (results) {
Zotero.debug("File sync is finished");
@@ -692,7 +699,9 @@ Zotero.Sync.Runner = new function () {
return;
}
- Zotero.Sync.Runner.sync(background);
+ Zotero.Sync.Runner.sync({
+ background: background
+ });
}
}
@@ -1143,8 +1152,10 @@ Zotero.Sync.Runner.IdleListener = {
Zotero.debug("Beginning idle sync");
- Zotero.Sync.Runner.sync(true);
- Zotero.Sync.Runner.setSyncTimeout(this._idleTimeout, true);
+ Zotero.Sync.Runner.sync({
+ background: true
+ });
+ Zotero.Sync.Runner.setSyncTimeout(this._idleTimeout, true, true);
},
_backObserver: {
@@ -1160,7 +1171,9 @@ Zotero.Sync.Runner.IdleListener = {
return;
}
Zotero.debug("Beginning return-from-idle sync");
- Zotero.Sync.Runner.sync(true);
+ Zotero.Sync.Runner.sync({
+ background: true
+ });
}
},
@@ -1561,219 +1574,216 @@ Zotero.Sync.Server = new function () {
_error(e);
}
- try {
- var gen = Zotero.Sync.Server.Data.processUpdatedXML(
- responseNode.getElementsByTagName('updated')[0],
- lastLocalSyncDate,
- syncSession,
- libraryID,
- function (xmlstr) {
- Zotero.UnresponsiveScriptIndicator.enable();
-
- if (Zotero.locked) {
- Zotero.hideZoteroPaneOverlay();
- }
- Zotero.suppressUIUpdates = false;
- _updatesInProgress = false;
-
- if (xmlstr === false) {
- Zotero.debug("Sync cancelled");
- Zotero.DB.rollbackTransaction();
- Zotero.reloadDataObjects();
- Zotero.Sync.EventListener.resetIgnored();
- _syncInProgress = false;
- _callbacks.onStop();
+ Components.utils.import("resource://gre/modules/Task.jsm");
+
+ Task.spawn(Zotero.Sync.Server.Data.processUpdatedXML(
+ responseNode.getElementsByTagName('updated')[0],
+ lastLocalSyncDate,
+ syncSession,
+ libraryID,
+ function (xmlstr) {
+ Zotero.UnresponsiveScriptIndicator.enable();
+
+ if (Zotero.locked) {
+ Zotero.hideZoteroPaneOverlay();
+ }
+ Zotero.suppressUIUpdates = false;
+ _updatesInProgress = false;
+
+ if (xmlstr === false) {
+ Zotero.debug("Sync cancelled");
+ Zotero.DB.rollbackTransaction();
+ Zotero.reloadDataObjects();
+ Zotero.Sync.EventListener.resetIgnored();
+ _syncInProgress = false;
+ _callbacks.onStop();
+ return;
+ }
+
+ if (xmlstr) {
+ Zotero.debug(xmlstr);
+ }
+
+ if (Zotero.Prefs.get('sync.debugBreak')) {
+ Zotero.debug('===============');
+ throw ("break");
+ }
+
+ if (!xmlstr) {
+ Zotero.debug("Nothing to upload to server");
+ Zotero.Sync.Server.lastRemoteSyncTime = response.getAttribute('timestamp');
+ Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
+ Zotero.Sync.Server.nextLocalSyncDate = false;
+ Zotero.DB.commitTransaction();
+ _syncInProgress = false;
+ _callbacks.onSuccess();
+ return;
+ }
+
+ Zotero.DB.commitTransaction();
+
+ Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.uploadingData'));
+
+ var url = _serverURL + 'upload';
+ var body = _apiVersionComponent
+ + '&' + Zotero.Sync.Server.sessionIDComponent
+ + '&updateKey=' + updateKey
+ + '&data=' + encodeURIComponent(xmlstr);
+
+ //var file = Zotero.getZoteroDirectory();
+ //file.append('lastupload.txt');
+ //Zotero.File.putContents(file, body);
+
+ var uploadCallback = function (xmlhttp) {
+ if (xmlhttp.status == 409) {
+ Zotero.debug("Upload key is no longer valid -- restarting sync");
+ setTimeout(function () {
+ Zotero.Sync.Server.sync(_callbacks, true, true);
+ }, 1);
return;
}
- if (xmlstr) {
- Zotero.debug(xmlstr);
+ _checkResponse(xmlhttp);
+
+ Zotero.debug(xmlhttp.responseText);
+ var response = xmlhttp.responseXML.childNodes[0];
+
+ if (_checkServerLock(response, function (mode) {
+ switch (mode) {
+ // If the upload was queued, keep checking back
+ case 'queued':
+ Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.uploadAccepted'));
+
+ var url = _serverURL + 'uploadstatus';
+ var body = _apiVersionComponent
+ + '&' + Zotero.Sync.Server.sessionIDComponent;
+ Zotero.HTTP.doPost(url, body, function (xmlhttp) {
+ uploadCallback(xmlhttp);
+ });
+ break;
+
+ // If affected libraries were locked, restart sync,
+ // since the upload key would be out of date anyway
+ case 'locked':
+ setTimeout(function () {
+ Zotero.Sync.Server.sync(_callbacks, true, true);
+ }, 1);
+ break;
+
+ default:
+ throw ("Unexpected server lock mode '" + mode + "' in Zotero.Sync.Server.upload()");
+ }
+ })) { return; }
+
+ if (response.firstChild.tagName == 'error') {
+ // handle error
+ _error(response.firstChild.firstChild.nodeValue);
}
- if (Zotero.Prefs.get('sync.debugBreak')) {
- Zotero.debug('===============');
- throw ("break");
+ if (response.firstChild.localName != 'uploaded') {
+ _error("Unexpected upload response '" + response.firstChild.localName
+ + "' in Zotero.Sync.Server.sync()");
}
- if (!xmlstr) {
- Zotero.debug("Nothing to upload to server");
- Zotero.Sync.Server.lastRemoteSyncTime = response.getAttribute('timestamp');
- Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
- Zotero.Sync.Server.nextLocalSyncDate = false;
- Zotero.DB.commitTransaction();
- _syncInProgress = false;
- _callbacks.onSuccess();
- return;
- }
+ Zotero.DB.beginTransaction();
+ Zotero.Sync.purgeDeletedObjects(nextLocalSyncTime);
+ Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
+ Zotero.Sync.Server.nextLocalSyncDate = false;
+ Zotero.Sync.Server.lastRemoteSyncTime = response.getAttribute('timestamp');
+
+ var sql = "UPDATE syncedSettings SET synced=1";
+ Zotero.DB.query(sql);
+
+ //throw('break2');
Zotero.DB.commitTransaction();
- Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.uploadingData'));
-
- var url = _serverURL + 'upload';
- var body = _apiVersionComponent
- + '&' + Zotero.Sync.Server.sessionIDComponent
- + '&updateKey=' + updateKey
- + '&data=' + encodeURIComponent(xmlstr);
-
- //var file = Zotero.getZoteroDirectory();
- //file.append('lastupload.txt');
- //Zotero.File.putContents(file, body);
-
- var uploadCallback = function (xmlhttp) {
- if (xmlhttp.status == 409) {
- Zotero.debug("Upload key is no longer valid -- restarting sync");
- setTimeout(function () {
- Zotero.Sync.Server.sync(_callbacks, true, true);
- }, 1);
- return;
- }
-
- _checkResponse(xmlhttp);
-
- Zotero.debug(xmlhttp.responseText);
- var response = xmlhttp.responseXML.childNodes[0];
-
- if (_checkServerLock(response, function (mode) {
- switch (mode) {
- // If the upload was queued, keep checking back
- case 'queued':
- Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.uploadAccepted'));
-
- var url = _serverURL + 'uploadstatus';
- var body = _apiVersionComponent
- + '&' + Zotero.Sync.Server.sessionIDComponent;
- Zotero.HTTP.doPost(url, body, function (xmlhttp) {
- uploadCallback(xmlhttp);
- });
- break;
-
- // If affected libraries were locked, restart sync,
- // since the upload key would be out of date anyway
- case 'locked':
- setTimeout(function () {
- Zotero.Sync.Server.sync(_callbacks, true, true);
- }, 1);
- break;
-
- default:
- throw ("Unexpected server lock mode '" + mode + "' in Zotero.Sync.Server.upload()");
- }
- })) { return; }
-
- if (response.firstChild.tagName == 'error') {
- // handle error
- _error(response.firstChild.firstChild.nodeValue);
- }
-
- if (response.firstChild.localName != 'uploaded') {
- _error("Unexpected upload response '" + response.firstChild.localName
- + "' in Zotero.Sync.Server.sync()");
- }
-
- Zotero.DB.beginTransaction();
- Zotero.Sync.purgeDeletedObjects(nextLocalSyncTime);
- Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
- Zotero.Sync.Server.nextLocalSyncDate = false;
- Zotero.Sync.Server.lastRemoteSyncTime = response.getAttribute('timestamp');
-
- var sql = "UPDATE syncedSettings SET synced=1";
- Zotero.DB.query(sql);
-
- //throw('break2');
-
- Zotero.DB.commitTransaction();
-
- // Check if any items were modified during /upload,
- // and restart the sync if so
- if (Zotero.Items.getNewer(nextLocalSyncDate, true)) {
- Zotero.debug("Items were modified during upload -- restarting sync");
- Zotero.Sync.Server.sync(_callbacks, true, true);
- return;
- }
-
- _syncInProgress = false;
- _callbacks.onSuccess();
+ // Check if any items were modified during /upload,
+ // and restart the sync if so
+ if (Zotero.Items.getNewer(nextLocalSyncDate, true)) {
+ Zotero.debug("Items were modified during upload -- restarting sync");
+ Zotero.Sync.Server.sync(_callbacks, true, true);
+ return;
}
- var compress = Zotero.Prefs.get('sync.server.compressData');
- // Compress upload data
- if (compress) {
- // Callback when compressed data is available
- var bufferUploader = function (data) {
- var gzurl = url + '?gzip=1';
-
- var oldLen = body.length;
- var newLen = data.length;
- var savings = Math.round(((oldLen - newLen) / oldLen) * 100)
- Zotero.debug("HTTP POST " + newLen + " bytes to " + gzurl
- + " (gzipped from " + oldLen + " bytes; "
- + savings + "% savings)");
-
- if (Zotero.HTTP.browserIsOffline()) {
- Zotero.debug('Browser is offline');
- return false;
- }
-
- var req =
- Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
- createInstance();
- req.open('POST', gzurl, true);
- req.setRequestHeader('Content-Type', "application/octet-stream");
- req.setRequestHeader('Content-Encoding', 'gzip');
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- uploadCallback(req);
- }
- };
- try {
- req.sendAsBinary(data);
- }
- catch (e) {
- _error(e);
- }
- }
-
- // Get input stream from POST data
- var unicodeConverter =
- Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
- unicodeConverter.charset = "UTF-8";
- var bodyStream = unicodeConverter.convertToInputStream(body);
-
- // Get listener for when compression is done
- var listener = new Zotero.BufferedInputListener(bufferUploader);
-
- // Initialize stream converter
- var converter =
- Components.classes["@mozilla.org/streamconv;1?from=uncompressed&to=gzip"]
- .createInstance(Components.interfaces.nsIStreamConverter);
- converter.asyncConvertData("uncompressed", "gzip", listener, null);
-
- // Send input stream to stream converter
- var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
- createInstance(Components.interfaces.nsIInputStreamPump);
- pump.init(bodyStream, -1, -1, 0, 0, true);
- pump.asyncRead(converter, null);
- }
-
- // Don't compress upload data
- else {
- Zotero.HTTP.doPost(url, body, uploadCallback);
- }
+ _syncInProgress = false;
+ _callbacks.onSuccess();
+ }
+
+ var compress = Zotero.Prefs.get('sync.server.compressData');
+ // Compress upload data
+ if (compress) {
+ // Callback when compressed data is available
+ var bufferUploader = function (data) {
+ var gzurl = url + '?gzip=1';
+
+ var oldLen = body.length;
+ var newLen = data.length;
+ var savings = Math.round(((oldLen - newLen) / oldLen) * 100)
+ Zotero.debug("HTTP POST " + newLen + " bytes to " + gzurl
+ + " (gzipped from " + oldLen + " bytes; "
+ + savings + "% savings)");
+
+ if (Zotero.HTTP.browserIsOffline()) {
+ Zotero.debug('Browser is offline');
+ return false;
+ }
+
+ var req =
+ Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance();
+ req.open('POST', gzurl, true);
+ req.setRequestHeader('Content-Type', "application/octet-stream");
+ req.setRequestHeader('Content-Encoding', 'gzip');
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ uploadCallback(req);
+ }
+ };
+ try {
+ req.sendAsBinary(data);
+ }
+ catch (e) {
+ _error(e);
+ }
+ }
+
+ // Get input stream from POST data
+ var unicodeConverter =
+ Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ var bodyStream = unicodeConverter.convertToInputStream(body);
+
+ // Get listener for when compression is done
+ var listener = new Zotero.BufferedInputListener(bufferUploader);
+
+ // Initialize stream converter
+ var converter =
+ Components.classes["@mozilla.org/streamconv;1?from=uncompressed&to=gzip"]
+ .createInstance(Components.interfaces.nsIStreamConverter);
+ converter.asyncConvertData("uncompressed", "gzip", listener, null);
+
+ // Send input stream to stream converter
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Components.interfaces.nsIInputStreamPump);
+ pump.init(bodyStream, -1, -1, 0, 0, true);
+ pump.asyncRead(converter, null);
+ }
+
+ // Don't compress upload data
+ else {
+ Zotero.HTTP.doPost(url, body, uploadCallback);
}
- );
-
- try {
- gen.next();
}
- catch (e if e.toString() === "[object StopIteration]") {}
- Zotero.pumpGenerator(gen, false, errorHandler);
- }
- catch (e) {
- errorHandler(e, true);
- }
+ ))
+ .then(
+ null,
+ function (e) {
+ errorHandler(e);
+ }
+ );
}
catch (e) {
_error(e);
@@ -2024,7 +2034,9 @@ Zotero.Sync.Server = new function () {
Zotero.Sync.Server.resetClient();
Zotero.Sync.Server.canAutoResetClient = false;
- Zotero.Sync.Runner.sync(background);
+ Zotero.Sync.Runner.sync({
+ background: background
+ });
}, 1);
break;
@@ -2127,7 +2139,9 @@ Zotero.Sync.Server = new function () {
Zotero.Sync.Server.canAutoResetClient = false;
}
- Zotero.Sync.Runner.sync(background);
+ Zotero.Sync.Runner.sync({
+ background: background
+ });
}, 1);
break;
@@ -2362,7 +2376,9 @@ Zotero.Sync.Server = new function () {
}
Zotero.Sync.Server.resetClient();
Zotero.Sync.Server.canAutoResetClient = false;
- Zotero.Sync.Runner.sync(background);
+ Zotero.Sync.Runner.sync({
+ background: background
+ });
}, 1);
break;
@@ -3433,14 +3449,14 @@ 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' && Object.keys(itemStorageModTimes).length) {
- Zotero.Sync.Storage.checkForUpdatedFiles(itemStorageModTimes);
+ yield Zotero.Sync.Storage.checkForUpdatedFiles(null, null, itemStorageModTimes);
}
}
if (_timeToYield()) yield true;
callback(Zotero.Sync.Server.Data.buildUploadXML(syncSession));
- }
+ };
/**
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
index 45e90681b..af9ed28d2 100644
--- a/chrome/content/zotero/xpcom/utilities_internal.js
+++ b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -80,9 +80,6 @@ Zotero.Utilities.Internal = {
return hash;
}
- /*
- // This created 36-character hashes
-
// return the two-digit hexadecimal code for a byte
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
@@ -90,18 +87,98 @@ Zotero.Utilities.Internal = {
// convert the binary hash data to a hex string.
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
- */
+ },
+
+
+ /**
+ * @param {OS.File|nsIFile|String} file File or file path
+ * @param {Boolean} [base64=FALSE] Return as base-64-encoded string
+ * rather than hex string
+ */
+ "md5Async": function (file, base64) {
+ Components.utils.import("resource://gre/modules/osfile.jsm");
+ const CHUNK_SIZE = 16384;
- // From http://rcrowley.org/2007/11/15/md5-in-xulrunner-or-firefox-extensions/
- var ascii = [];
- var ii = hash.length;
- for (var i = 0; i < ii; ++i) {
- var c = hash.charCodeAt(i);
- var ones = c % 16;
- var tens = c >> 4;
- ascii.push(String.fromCharCode(tens + (tens > 9 ? 87 : 48)) + String.fromCharCode(ones + (ones > 9 ? 87 : 48)));
+ var deferred = Q.defer();
+
+ function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
}
- return ascii.join('');
+
+ var ch = Components.classes["@mozilla.org/security/hash;1"]
+ .createInstance(Components.interfaces.nsICryptoHash);
+ ch.init(ch.MD5);
+
+ // Recursively read chunks of the file, and resolve the promise
+ // with the hash when done
+ let readChunk = function readChunk(file) {
+ file.read(CHUNK_SIZE)
+ .then(
+ function readSuccess(data) {
+ ch.update(data, data.length);
+ if (data.length == CHUNK_SIZE) {
+ readChunk(file);
+ }
+ else {
+ let hash = ch.finish(base64);
+
+ // Base64
+ if (base64) {
+ deferred.resolve(hash);
+ }
+ // Hex string
+ else {
+ deferred.resolve(
+ [toHexString(hash.charCodeAt(i))
+ for (i in hash)].join("")
+ );
+ }
+ }
+ },
+ function (e) {
+ try {
+ ch.finish(false);
+ }
+ catch (e) {}
+
+ deferred.reject(e);
+ }
+ )
+ .then(
+ null,
+ function (e) {
+ try {
+ ch.finish(false);
+ }
+ catch (e) {}
+
+ deferred.reject(e);
+ }
+ );
+ }
+
+ if (file instanceof OS.File) {
+ readChunk(file);
+ }
+ else {
+ if (file instanceof Components.interfaces.nsIFile) {
+ var path = file.path;
+ }
+ else {
+ var path = file;
+ }
+ OS.File.open(path)
+ .then(
+ function opened(file) {
+ readChunk(file);
+ },
+ function (e) {
+ deferred.reject(e);
+ }
+ );
+ }
+
+ return deferred.promise;
},
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
index 34e9ad526..ebdb12777 100644
--- a/chrome/content/zotero/xpcom/zotero.js
+++ b/chrome/content/zotero/xpcom/zotero.js
@@ -219,6 +219,11 @@ Components.utils.import("resource://gre/modules/Services.jsm");
Zotero.Debug.init();
this.mainThread = Services.tm.mainThread;
+
+ var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo);
+ this.platformVersion = appInfo.platformVersion;
+ this.platformMajorVersion = parseInt(appInfo.platformVersion.match(/^[0-9]+/)[0]);
this.isFx = true;
this.isStandalone = Services.appinfo.ID == ZOTERO_CONFIG['GUID'];
@@ -1892,13 +1897,13 @@ Components.utils.import("resource://gre/modules/Services.jsm");
'[JavaScript Error: "this._uiElement is null',
'Error: a._updateVisibleText is not a function',
'[JavaScript Error: "Warning: unrecognized command line flag ',
- '[JavaScript Error: "Warning: unrecognized command line flag -foreground',
'LibX:',
'function skype_',
'[JavaScript Error: "uncaught exception: Permission denied to call method Location.toString"]',
'CVE-2009-3555',
'OpenGL LayerManager',
- 'trying to re-register CID'
+ 'trying to re-register CID',
+ 'Services.HealthReport'
];
for (var i=0; icommand mappings from the prefs
for each(var action in actions) {
var action = action.substr(5); // strips 'keys.'
+ // Remove old pref
if (action == 'overrideGlobal') {
Zotero.Prefs.clear('keys.overrideGlobal');
continue;
@@ -2246,26 +2252,35 @@ Zotero.Keys = new function() {
* Called by ZoteroPane.onLoad()
*/
function windowInit(document) {
- var useShift = Zotero.isMac;
+ var globalKeys = [
+ {
+ name: 'openZotero',
+ defaultKey: 'Z'
+ },
+ {
+ name: 'saveToZotero',
+ defaultKey: 'S'
+ }
+ ];
- // Zotero pane shortcut
- var keyElem = document.getElementById('key_openZotero');
- if(keyElem) {
- var zKey = Zotero.Prefs.get('keys.openZotero');
- // Only override the default with the pref if the hasn't been manually changed
- // and the pref has been
- if (keyElem.getAttribute('key') == 'Z' && keyElem.getAttribute('modifiers') == 'accel alt'
- && (zKey != 'Z' || useShift)) {
- keyElem.setAttribute('key', zKey);
- if (useShift) {
- keyElem.setAttribute('modifiers', 'accel shift');
+ globalKeys.forEach(function (x) {
+ let keyElem = document.getElementById('key_' + x.name);
+ if (keyElem) {
+ let prefKey = Zotero.Prefs.get('keys.' + x.name);
+ // Only override the default with the pref if the hasn't
+ // been manually changed and the pref has been
+ if (keyElem.getAttribute('key') == x.defaultKey
+ && keyElem.getAttribute('modifiers') == 'accel shift'
+ && prefKey != x.defaultKey) {
+ keyElem.setAttribute('key', prefKey);
}
}
- }
+ });
}
function getCommand(key) {
+ key = key.toUpperCase();
return _keys[key] ? _keys[key] : false;
}
}
@@ -2310,17 +2325,19 @@ Zotero.DragDrop = {
currentDataTransfer: null,
getDragData: function (element, firstOnly) {
- var dragData = {
- dataType: '',
- data: []
- };
-
var dt = this.currentDataTransfer;
if (!dt) {
Zotero.debug("Drag data not available");
return false;
}
+ var dragData = {
+ dataType: '',
+ data: [],
+ dropEffect: dt.dropEffect
+ };
+
+
var len = firstOnly ? 1 : dt.mozItemCount;
if (dt.types.contains('zotero/collection')) {
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index caa793efc..784e4780f 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -433,7 +433,9 @@ var ZoteroPane = new function()
return;
}
- Zotero.Sync.Runner.sync(true);
+ Zotero.Sync.Runner.sync({
+ background: true
+ });
})
.done();
}
@@ -602,17 +604,14 @@ var ZoteroPane = new function()
}
}
- var useShift = Zotero.isMac;
-
var key = String.fromCharCode(event.which);
if (!key) {
Zotero.debug('No key');
return;
}
- // Ignore modifiers other than Ctrl-Alt or Cmd-Shift
- if (!((Zotero.isMac ? event.metaKey : event.ctrlKey) &&
- (useShift ? event.shiftKey : event.altKey))) {
+ // Ignore modifiers other than Ctrl-Shift/Cmd-Shift
+ if (!((Zotero.isMac ? event.metaKey : event.ctrlKey) && event.shiftKey)) {
return;
}
@@ -687,9 +686,7 @@ var ZoteroPane = new function()
}
}
// Use key that's not the modifier as the popup toggle
- ZoteroPane_Local.newNote(
- useShift ? event.altKey : event.shiftKey, parent
- );
+ ZoteroPane_Local.newNote(event.altKey, parent);
break;
case 'toggleTagSelector':
ZoteroPane_Local.toggleTagSelector();
@@ -2563,14 +2560,8 @@ var ZoteroPane = new function()
else if (tree.id == 'zotero-items-tree') {
let itemGroup = ZoteroPane_Local.getItemGroup();
if (itemGroup.isDuplicates()) {
- if (event.button == 0 && (event.metaKey || event.shiftKey
- || event.altKey || event.ctrlKey)) {
- return;
- }
-
- // Allow right-click on single items/attachments
- var items = ZoteroPane_Local.getSelectedItems();
- if (event.button != 0 && items.length == 1) {
+ if (event.button != 0 || event.metaKey || event.shiftKey
+ || event.altKey || event.ctrlKey) {
return;
}
@@ -3503,6 +3494,7 @@ var ZoteroPane = new function()
this.loadURI(url, event);
}
else {
+ Zotero.Notifier.trigger('open', 'file', itemID);
Zotero.launchFile(file);
}
}
@@ -3624,6 +3616,7 @@ var ZoteroPane = new function()
var parent = file.parent.QueryInterface(Components.interfaces.nsILocalFile);
Zotero.launchFile(parent);
}
+ Zotero.Notifier.trigger('open', 'file', attachment.id);
}
else {
this.showAttachmentNotFoundDialog(attachment.id, noLocateOnMissing)
diff --git a/chrome/locale/en-US/zotero/preferences.dtd b/chrome/locale/en-US/zotero/preferences.dtd
index 19a5147bd..5c4b48f68 100644
--- a/chrome/locale/en-US/zotero/preferences.dtd
+++ b/chrome/locale/en-US/zotero/preferences.dtd
@@ -126,16 +126,16 @@
+
-
-
+
+
-
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index 7635279f1..286563ee2 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -934,6 +934,10 @@ locate.manageLocateEngines = Manage Lookup Engines…
standalone.corruptInstallation = Your Zotero Standalone installation appears to be corrupted due to a failed auto-update. While Zotero may continue to function, to avoid potential bugs, please download the latest version of Zotero Standalone from http://zotero.org/support/standalone as soon as possible.
standalone.addonInstallationFailed.title = Add-on Installation Failed
standalone.addonInstallationFailed.body = The add-on "%S" could not be installed. It may be incompatible with this version of Zotero Standalone.
+standalone.rootWarning = You appear to be running Zotero Standalone as root. This is insecure and may prevent Zotero from functioning when launched from your user account.\n\nIf you wish to install an automatic update, modify the Zotero program directory to be writeable by your user account.
+standalone.rootWarning.exit = Exit
+standalone.rootWarning.continue = Continue
+standalone.updateMessage = A recommended update is available, but you do not have permission to install it. To update automatically, modify the Zotero program directory to be writeable by your user account.
connector.error.title = Zotero Connector Error
connector.standaloneOpen = Your database cannot be accessed because Zotero Standalone is currently open. Please view your items in Zotero Standalone.
diff --git a/chrome/locale/nn-NO/zotero/standalone.dtd b/chrome/locale/nn-NO/zotero/standalone.dtd
index 88ddaa5dc..63822d0e2 100644
--- a/chrome/locale/nn-NO/zotero/standalone.dtd
+++ b/chrome/locale/nn-NO/zotero/standalone.dtd
@@ -1,10 +1,10 @@
-
+
-
+
-
+
-
+
@@ -12,52 +12,52 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
@@ -69,7 +69,7 @@
-
+
@@ -88,14 +88,14 @@
-
+
-
+
-
+
-
+
-
+
diff --git a/chrome/skin/default/zotero/bindings/attachmentbox.css b/chrome/skin/default/zotero/bindings/attachmentbox.css
index 71c884dee..71cbe9a98 100644
--- a/chrome/skin/default/zotero/bindings/attachmentbox.css
+++ b/chrome/skin/default/zotero/bindings/attachmentbox.css
@@ -20,19 +20,10 @@
#reindex
{
- margin: 0;
- padding: 0;
+ padding-left: 5px;
list-style-image: url(chrome://zotero/skin/arrow_refresh.png);
}
-#reindex .toolbarbutton-icon
-{
- margin: 0 0 0 2px;
- padding: 0;
- width: 14px;
- height: 14px;
-}
-
#index-box > button
{
font-size: .95em;
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
index d4a57800d..5e4b710c5 100644
--- a/defaults/preferences/zotero.js
+++ b/defaults/preferences/zotero.js
@@ -65,14 +65,15 @@ pref("extensions.zotero.tagCloud", false);
// Keyboard shortcuts
pref("extensions.zotero.keys.openZotero", 'Z');
pref("extensions.zotero.keys.toggleFullscreen", 'F');
-pref("extensions.zotero.keys.library", 'L');
-pref("extensions.zotero.keys.quicksearch", 'K');
+pref("extensions.zotero.keys.saveToZotero", 'S');
pref("extensions.zotero.keys.newItem", 'N');
pref("extensions.zotero.keys.newNote", 'O');
-pref("extensions.zotero.keys.toggleTagSelector", 'T');
+pref("extensions.zotero.keys.importFromClipboard", 'V');
+pref("extensions.zotero.keys.library", 'L');
+pref("extensions.zotero.keys.quicksearch", 'K');
pref("extensions.zotero.keys.copySelectedItemCitationsToClipboard", 'A');
pref("extensions.zotero.keys.copySelectedItemsToClipboard", 'C');
-pref("extensions.zotero.keys.importFromClipboard", 'V');
+pref("extensions.zotero.keys.toggleTagSelector", 'T');
// Fulltext indexing
pref("extensions.zotero.fulltext.textMaxLength", 500000);
diff --git a/resource/q.js b/resource/q.js
index f73a91336..47b3118c2 100644
--- a/resource/q.js
+++ b/resource/q.js
@@ -602,7 +602,9 @@ array_reduce(
"dispatch",
"when", "spread",
"get", "put", "set", "del", "delete",
- "post", "send", "invoke",
+ // .send() disabled by Zotero for Mozilla Task.jsm compatibility
+ //"post", "send", "invoke",
+ "post", "invoke",
"keys",
"fapply", "fcall", "fbind",
"all", "allResolved",
@@ -1146,7 +1148,8 @@ var post = Q.post = dispatcher("post");
* @param ...args array of invocation arguments
* @return promise for the return value
*/
-Q.send = send;
+// Disabled by Zotero for Mozilla Task.jsm compatibility
+//Q.send = send;
Q.invoke = send; // synonyms
function send(value, name) {
var args = array_slice(arguments, 2);
diff --git a/resource/schema/renamed-styles.json b/resource/schema/renamed-styles.json
index 80fa8e5be..f5a8d48e2 100644
--- a/resource/schema/renamed-styles.json
+++ b/resource/schema/renamed-styles.json
@@ -151,6 +151,7 @@
"mla-underline": "modern-language-association-underline",
"mla-url": "modern-language-association-with-url",
"mla": "modern-language-association",
+ "modern-language-association-note": "modern-language-association-6th-edition-note",
"molecular-biochemical-parasitology": "molecular-and-biochemical-parasitology",
"national-library-of-medicine-grant": "national-library-of-medicine-grant-proposals",
"nature-neuroscience-brief-communication": "nature-neuroscience-brief-communications",
diff --git a/resource/schema/repotime.txt b/resource/schema/repotime.txt
index 09ed58509..1b610cc67 100644
--- a/resource/schema/repotime.txt
+++ b/resource/schema/repotime.txt
@@ -1 +1 @@
-2013-07-02 00:00:00
+2013-07-22 02:55:00
diff --git a/styles b/styles
index b5a55500a..c536a2c5c 160000
--- a/styles
+++ b/styles
@@ -1 +1 @@
-Subproject commit b5a55500a81e470ec56a10c1757657a050e2c8fe
+Subproject commit c536a2c5c28ca465b511733a849ae452822fd363
diff --git a/translators b/translators
index 089b8a1ae..10e7c43ed 160000
--- a/translators
+++ b/translators
@@ -1 +1 @@
-Subproject commit 089b8a1ae7d08ffa420b768509f2ae33cce3ab7f
+Subproject commit 10e7c43ed0e70cdccebf575d4cdccd81b8914ffe