commit 514547ab3b16866a0a78e3e2a581662f673f6d30
parent 9b231169b2efedba12ab5214ea5bb05251912cf7
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 1 Apr 2016 02:24:50 -0400
Reinstate auto-sync, and remove lots of old sync code
The on-change auto-sync now syncs only the modified library, and does so
quite efficiently (and should be able to be made more efficient), so we
might be able to reduce the timeout below 15 seconds.
Diffstat:
10 files changed, 138 insertions(+), 2035 deletions(-)
diff --git a/chrome/content/zotero/preferences/preferences_sync.js b/chrome/content/zotero/preferences/preferences_sync.js
@@ -498,7 +498,7 @@ Zotero_Preferences.Sync = {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
- if (!Zotero.Sync.Server.enabled) {
+ if (!Zotero.Sync.Runner.enabled) {
ps.alert(
null,
Zotero.getString('general.error'),
diff --git a/chrome/content/zotero/preferences/preferences_sync.xul b/chrome/content/zotero/preferences/preferences_sync.xul
@@ -127,13 +127,13 @@
<row>
<box/>
<checkbox label="&zotero.preferences.sync.syncAutomatically;"
- disabled="true"/>
+ preference="pref-sync-autosync"/>
</row>
<row>
<box/>
<checkbox label="&zotero.preferences.sync.syncFullTextContent;"
- tooltiptext="&zotero.preferences.sync.syncFullTextContent.desc;"
- disabled="true"/>
+ preference="pref-sync-fulltext-enabled"
+ tooltiptext="&zotero.preferences.sync.syncFullTextContent.desc;"/>
</row>
</rows>
</grid>
diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js
@@ -800,13 +800,6 @@ Zotero.DataObjects.prototype.isEditable = function (obj) {
return true;
}
-
-Zotero.DataObjects.prototype.editCheck = function (obj) {
- if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) {
- throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
- }
-}
-
Zotero.defineProperty(Zotero.DataObjects.prototype, "primaryDataSQL", {
get: function () {
return "SELECT "
diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js
@@ -481,12 +481,6 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
else if (req.status == 404) {
Components.utils.reportError("Unexpected status code 404 in upload authorization "
+ "request (" + item.libraryKey + ")");
- if (Zotero.Prefs.get('sync.debugNoAutoResetClient')) {
- Components.utils.reportError("Skipping automatic client reset due to debug pref");
- }
- if (!Zotero.Sync.Server.canAutoResetClient) {
- Components.utils.reportError("Client has already been auto-reset -- manual sync required");
- }
// TODO: Make an API request to fix this
diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js
@@ -62,64 +62,6 @@ Zotero.Sync = new function() {
}
};
});
-
- var _typesLoaded = false;
- var _objectTypeIDs = {};
- var _objectTypeNames = {};
-
- var _deleteLogDays = 30;
-
-
- this.init = function () {
- Zotero.debug("Syncing is disabled", 1);
- return;
-
- Zotero.DB.beginTransaction();
-
- var sql = "SELECT version FROM version WHERE schema='syncdeletelog'";
- if (!Zotero.DB.valueQuery(sql)) {
- sql = "SELECT COUNT(*) FROM syncDeleteLog";
- if (Zotero.DB.valueQuery(sql)) {
- throw ('syncDeleteLog not empty and no timestamp in Zotero.Sync.delete()');
- }
-
- var syncInitTime = Zotero.DB.transactionDate;
- syncInitTime = Zotero.Date.toUnixTimestamp(syncInitTime);
-
- sql = "INSERT INTO version VALUES ('syncdeletelog', ?)";
- Zotero.DB.query(sql, syncInitTime);
- }
-
- Zotero.DB.commitTransaction();
- }
-
-
- /**
- * @param int deleteOlderThan Unix timestamp
- */
- this.purgeDeletedObjects = function (deleteOlderThan) {
- if (isNaN(parseInt(deleteOlderThan))) {
- throw ("Invalid timestamp '" + deleteOlderThan
- + "' in Zotero.Sync.purgeDeletedObjects");
- }
- var sql = "DELETE FROM syncDeleteLog WHERE timestamp<?";
- Zotero.DB.query(sql, { int: deleteOlderThan });
- }
-
-
- function _loadObjectTypes() {
- // TEMP: Take this out once system.sql > 31
- var sql = "UPDATE syncObjectTypes SET name='relation' WHERE syncObjectTypeID=6 AND name='relations'";
- Zotero.DB.query(sql);
-
- var sql = "SELECT * FROM syncObjectTypes";
- var types = Zotero.DB.query(sql);
- for each(var type in types) {
- _objectTypeNames[type.syncObjectTypeID] = type.name;
- _objectTypeIDs[type.name] = type.syncObjectTypeID;
- }
- _typesLoaded = true;
- }
}
@@ -127,558 +69,11 @@ Zotero.Sync = new function() {
* Methods for syncing with the Zotero Server
*/
Zotero.Sync.Server = new function () {
- this.login = login;
- this.sync = sync;
- this.clear = clear;
- this.resetClient = resetClient;
-
- this.__defineGetter__('enabled', function () {
- if (_throttleTimeout && new Date() < _throttleTimeout) {
- Zotero.debug("Auto-syncing is disabled until " + Zotero.Date.dateToSQL(_throttleTimeout) + " -- skipping sync");
- return false;
- }
- return this.username && this.password;
- });
-
- this.__defineGetter__("syncInProgress", function () _syncInProgress);
- this.__defineGetter__("updatesInProgress", function () _updatesInProgress);
- this.__defineGetter__("sessionIDComponent", function () {
- return 'sessionid=' + _sessionID;
- });
- this.__defineSetter__("lastLocalSyncTime", function (val) {
- Zotero.DB.query("REPLACE INTO version VALUES ('lastlocalsync', ?)", { int: val });
- });
-
-
this.canAutoResetClient = true;
this.manualSyncRequired = false;
this.upgradeRequired = false;
this.nextLocalSyncDate = false;
- var _syncInProgress;
- var _updatesInProgress;
- var _sessionID;
- var _throttleTimeout;
- var _checkTimer;
-
- var _callbacks = {
- onSuccess: function () {
- Zotero.Sync.Runner.setSyncIcon();
- },
- onSkip: function () {
- Zotero.Sync.Runner.setSyncIcon();
- },
- onStop: function () {
- Zotero.Sync.Runner.setSyncIcon();
- },
- onError: function (msg) {
- Zotero.Sync.Runner.error(msg);
- }
- };
-
- function login() {
- var url = _serverURL + "login";
-
- var username = Zotero.Sync.Server.username;
- if (!username) {
- var e = new Zotero.Error(Zotero.getString('sync.error.usernameNotSet'), "SYNC_USERNAME_NOT_SET");
- _error(e);
- }
-
- var password = Zotero.Sync.Server.password;
- if (!password) {
- var e = new Zotero.Error(Zotero.getString('sync.error.passwordNotSet'), "INVALID_SYNC_LOGIN");
- _error(e);
- }
-
- // TEMP
- if (Zotero.Prefs.get("sync.fulltext.enabled") &&
- Zotero.DB.valueQuery("SELECT version FROM version WHERE schema='userdata'") < 77) {
- // Don't show multiple times on idle
- _syncInProgress = true;
-
- let ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
- let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
- + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
- + ps.BUTTON_DELAY_ENABLE;
- let index = ps.confirmEx(
- null,
- Zotero.getString('sync.fulltext.upgradePrompt.title'),
- Zotero.getString('sync.fulltext.upgradePrompt.text') + "\n\n"
- + Zotero.getString('sync.fulltext.upgradePrompt.changeLater'),
- buttonFlags,
- Zotero.getString('sync.fulltext.upgradePrompt.enable'),
- Zotero.getString('general.notNow'),
- null, null, {}
- );
-
- _syncInProgress = false;
-
- // Enable
- if (index == 0) {
- Zotero.DB.backupDatabase(76, true);
- Zotero.DB.query("UPDATE version SET version=77 WHERE schema='userdata'");
- Zotero.wait(1000);
- }
- // Disable
- else {
- Zotero.Prefs.set("sync.fulltext.enabled", false);
- }
- }
-
- username = encodeURIComponent(username);
- password = encodeURIComponent(password);
- var body = _apiVersionComponent
- + "&username=" + username
- + "&password=" + password;
-
- Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.loggingIn'));
-
- return Zotero.HTTP.promise("POST", url,
- { body: body, successCodes: false, foreground: !Zotero.Sync.Runner.background })
- .then(function (xmlhttp) {
- _checkResponse(xmlhttp, true);
-
- var response = xmlhttp.responseXML.childNodes[0];
-
- if (response.firstChild.tagName == 'error') {
- if (response.firstChild.getAttribute('code') == 'INVALID_LOGIN') {
- var e = new Zotero.Error(Zotero.getString('sync.error.invalidLogin'), "INVALID_SYNC_LOGIN");
- _error(e, false, true);
- }
- _error(response.firstChild.firstChild.nodeValue, false, true);
- }
-
- if (_sessionID) {
- _error("Session ID already set in Zotero.Sync.Server.login()", false, true)
- }
-
- // <response><sessionID>[abcdefg0-9]{32}</sessionID></response>
- _sessionID = response.firstChild.firstChild.nodeValue;
-
- var re = /^[abcdefg0-9]{32}$/;
- if (!re.test(_sessionID)) {
- _sessionID = null;
- _error('Invalid session ID received from server', false, true);
- }
-
-
- //Zotero.debug('Got session ID ' + _sessionID + ' from server');
- });
- }
-
-
- function sync(callbacks, restart, upload) {
- for (var func in callbacks) {
- _callbacks[func] = callbacks[func];
- }
-
- var self = this;
-
- Zotero.Sync.Runner.setErrors();
- Zotero.Sync.Runner.setSyncIcon('animate');
-
- if (!_sessionID) {
- Zotero.debug("Session ID not available -- logging in");
- Zotero.Sync.Server.login()
- .then(function () {
- Zotero.Sync.Server.sync(_callbacks);
- })
- .done();
- return;
- }
-
- if (!restart) {
- if (_syncInProgress) {
- var msg = Zotero.localeJoin([
- Zotero.getString('sync.error.syncInProgress'),
- Zotero.getString('sync.error.syncInProgress.wait', Zotero.appName)
- ]);
- var e = new Zotero.Error(msg, 0, { dialogButtonText: null, frontWindowOnly: true });
- _error(e);
- }
-
- Zotero.debug("Beginning server sync");
- _syncInProgress = true;
- }
-
- // Get updated data
- var url = _serverURL + 'updated';
- var lastsync = Zotero.Sync.Server.lastRemoteSyncTime;
- if (!lastsync) {
- lastsync = 1;
- }
-
- // If no local timestamp is stored, don't use remote time
- //
- // This used to be possible when remote time was saved whether or not
- // the subsequent upload went through
- var lastLocalSyncTime = Zotero.Sync.Server.lastLocalSyncTime;
- if (!lastLocalSyncTime) {
- lastsync = 1;
- }
-
- var body = _apiVersionComponent
- + '&' + Zotero.Sync.Server.sessionIDComponent
- + '&lastsync=' + lastsync;
- // Tell server to check for read locks as well as write locks,
- // since we'll be uploading
- if (upload) {
- body += '&upload=1';
- }
-
- if (Zotero.Prefs.get("sync.fulltext.enabled")) {
- body += "&ft=1" + Zotero.Fulltext.getUndownloadedPostData();
- }
- else {
- body += "&ft=0";
- }
-
- Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.gettingUpdatedData'));
-
- Zotero.HTTP.doPost(url, body, function (xmlhttp) {
- Zotero.debug(xmlhttp.responseText);
-
- _checkResponse(xmlhttp, !restart);
-
- if (_invalidSession(xmlhttp)) {
- Zotero.debug("Invalid session ID -- logging in");
- _sessionID = false;
- _syncInProgress = false;
- Zotero.Sync.Server.login()
- .then(function () {
- Zotero.Sync.Server.sync(_callbacks);
- })
- .done();
- return;
- }
-
- var response = xmlhttp.responseXML.childNodes[0];
-
- // If server session is locked, keep checking back
- if (_checkServerLock(response, function () { Zotero.Sync.Server.sync(_callbacks, true, upload); })) {
- return;
- }
-
- // Error that's not handled by _checkResponse()
- if (response.firstChild.localName == 'error') {
- _error(response.firstChild.firstChild.nodeValue);
- }
-
- try {
- var responseNode = xmlhttp.responseXML.documentElement;
-
- var updateKey = responseNode.getAttribute('updateKey');
-
- // If no earliest date is provided by the server, the server
- // account is empty
- var earliestRemoteDate = responseNode.getAttribute('earliest');
- earliestRemoteDate = parseInt(earliestRemoteDate) ?
- new Date((earliestRemoteDate + 43200) * 1000) : false;
- var noServerData = !!earliestRemoteDate;
-
- // Check to see if we're syncing with a different user
- var userID = parseInt(responseNode.getAttribute('userID'));
- var libraryID = parseInt(responseNode.getAttribute('defaultLibraryID'));
- var c = _checkSyncUser(userID, libraryID, noServerData);
- if (c == 0) {
- // Groups were deleted, so restart sync
- Zotero.debug("Restarting sync");
- _syncInProgress = false;
- Zotero.Sync.Server.sync(_callbacks);
- return;
- }
- else if (c == -1) {
- Zotero.debug("Sync cancelled");
- _syncInProgress = false;
- _callbacks.onStop();
- return;
- }
-
- Zotero.DB.beginTransaction();
-
- Zotero.UnresponsiveScriptIndicator.disable();
-
- var lastLocalSyncDate = lastLocalSyncTime ?
- new Date(lastLocalSyncTime * 1000) : false;
-
- var syncSession = new Zotero.Sync.Server.Session;
-
- // Fetch old objects not on server (due to a clear) and new
- // objects added since last sync, or all local objects if neither is set
- Zotero.Sync.getObjectsByDate(
- earliestRemoteDate, lastLocalSyncDate, syncSession.uploadKeys.updated
- );
-
- var deleted = Zotero.Sync.getDeletedObjects(lastLocalSyncDate, syncSession.uploadKeys.deleted);
- if (deleted == -1) {
- var msg = "Sync delete log starts after last sync date in Zotero.Sync.Server.sync()";
- var e = new Zotero.Error(msg, "FULL_SYNC_REQUIRED");
- throw (e);
- }
-
- var nextLocalSyncDate = Zotero.DB.transactionDate;
- var nextLocalSyncTime = Zotero.Date.toUnixTimestamp(nextLocalSyncDate);
- Zotero.Sync.Server.nextLocalSyncDate = nextLocalSyncDate;
-
- Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.processingUpdatedData'));
-
- // Reconcile and save updated data from server and
- // prepare local data to upload
-
- Zotero.suppressUIUpdates = true;
- _updatesInProgress = true;
-
- var errorHandler = function (e, rethrow) {
- Zotero.DB.rollbackTransaction();
-
- Zotero.UnresponsiveScriptIndicator.enable();
-
- if (Zotero.locked) {
- Zotero.hideZoteroPaneOverlays();
- }
- Zotero.suppressUIUpdates = false;
- _updatesInProgress = false;
-
- if (rethrow) {
- throw (e);
- }
- _error(e);
- }
-
- var result = Q.async(Zotero.Sync.Server.Data.processUpdatedXML(
- responseNode.getElementsByTagName('updated')[0],
- lastLocalSyncDate,
- syncSession,
- libraryID,
- function (xmlstr) {
- Zotero.UnresponsiveScriptIndicator.enable();
-
- if (Zotero.locked) {
- Zotero.hideZoteroPaneOverlays();
- }
- 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;
- }
-
- _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);
-
- if (syncSession.fulltextItems && syncSession.fulltextItems.length) {
- let sql = "UPDATE fulltextItems SET synced=1 WHERE itemID=?";
- for each (let lk in syncSession.fulltextItems) {
- let item = Zotero.Items.getByLibraryAndKey(lk.libraryID, lk.key);
- Zotero.DB.query(sql, item.id);
- }
- }
-
- //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();
- }
-
- 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 {
- // Send binary data
- let numBytes = data.length, ui8Data = new Uint8Array(numBytes);
- for (let i = 0; i < numBytes; i++) {
- ui8Data[i] = data.charCodeAt(i) & 0xff;
- }
- req.send(ui8Data);
- }
- 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);
- }
- }
- ))();
-
- if (Q.isPromise(result)) {
- result.catch(errorHandler);
- }
- }
- catch (e) {
- _error(e);
- }
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
- });
-
- return;
- }
-
-
function clear(callback) {
if (!_sessionID) {
Zotero.debug("Session ID not available -- logging in");
@@ -1092,1286 +487,51 @@ Zotero.Sync.Server = new function () {
}
-
-
Zotero.Sync.Server.Data = new function() {
- var _noMergeTypes = ['search'];
-
/**
- * Pull out collections from delete queue in XML
- *
- * @param {DOMNode} xml
- * @return {String[]} Array of collection keys
+ * @param {String} itemTypes
+ * @param {String} localName
+ * @param {String} remoteName
+ * @param {Boolean} [remoteMoreRecent=false]
*/
- function _getDeletedCollectionKeys(updatedNode) {
- var keys = [];
- for each(var c in updatedNode.xpath("deleted/collections/collection")) {
- var libraryID = c.getAttribute('libraryID');
- libraryID = libraryID ? parseInt(libraryID) : null;
- keys.push({
- libraryID: libraryID,
- key: c.getAttribute('key')
- });
+ function _generateAutoChangeAlertMessage(itemTypes, localName, remoteName, remoteMoreRecent) {
+ if (localName === null) {
+ var localDelete = true;
+ }
+ else if (remoteName === null) {
+ var remoteDelete = true;
+ }
+
+ var msg = Zotero.getString('sync.conflict.autoChange.alert', itemTypes) + " ";
+ if (localDelete) {
+ msg += Zotero.getString('sync.conflict.remoteVersionsKept');
+ }
+ else if (remoteDelete) {
+ msg += Zotero.getString('sync.conflict.localVersionsKept');
+ }
+ else {
+ msg += Zotero.getString('sync.conflict.recentVersionsKept');
}
- return keys;
+ msg += "\n\n" + Zotero.getString('sync.conflict.viewErrorConsole',
+ (Zotero.isStandalone ? "" : "Firefox")).replace(/\s+/, " ");
+ return msg;
}
- this.processUpdatedXML = function (updatedNode, lastLocalSyncDate, syncSession, defaultLibraryID, callback) {
- updatedNode.xpath = function (path) {
- return Zotero.Utilities.xpath(this, path);
- };
-
- if (updatedNode.childNodes.length == 0) {
- Zotero.debug('No changes received from server');
- callback(Zotero.Sync.Server.Data.buildUploadXML(syncSession));
- return;
+ /**
+ * @param {String} itemType
+ * @param {String} localName
+ * @param {String} remoteName
+ * @param {Boolean} [remoteMoreRecent=false]
+ */
+ function _generateAutoChangeLogMessage(itemType, localName, remoteName, remoteMoreRecent) {
+ if (localName === null) {
+ localName = Zotero.getString('sync.conflict.deleted');
+ var localDelete = true;
}
-
- function _libID(libraryID) {
- return _getLibraryID(libraryID, defaultLibraryID);
- }
-
- function _timeToYield() {
- if (!progressMeter) {
- if (Date.now() - start > progressMeterThreshold) {
- Zotero.showZoteroPaneProgressMeter(
- Zotero.getString('sync.status.processingUpdatedData'),
- false,
- "chrome://zotero/skin/arrow_rotate_animated.png"
- );
- progressMeter = true;
- }
- }
- else if (Date.now() - lastRepaint > repaintTime) {
- lastRepaint = Date.now();
- return true;
- }
- return false;
- }
-
- var progressMeter = false;
- var progressMeterThreshold = 100;
- var start = Date.now();
- var repaintTime = 100;
- var lastRepaint = Date.now();
-
- var deletedCollectionKeys = _getDeletedCollectionKeys(updatedNode);
-
- var remoteCreatorStore = {};
- var relatedItemsStore = {};
- var itemStorageModTimes = {};
- var childItemStore = [];
-
- // Remotely changed groups
- var groupNodes = updatedNode.xpath("groups/group");
- if (groupNodes.length) {
- Zotero.debug("Processing remotely changed groups");
- for each(var groupNode in groupNodes) {
- var group = Zotero.Sync.Server.Data.xmlToGroup(groupNode);
- group.save();
- }
- }
-
- if (_timeToYield()) yield true;
-
- // Remotely deleted groups
- var deletedGroups = updatedNode.xpath("deleted/groups");
- if (deletedGroups.length && deletedGroups[0].textContent) {
- Zotero.debug("Processing remotely deleted groups");
- var groupIDs = deletedGroups[0].textContent.split(' ');
- Zotero.debug(groupIDs);
-
- for each(var groupID in groupIDs) {
- var group = Zotero.Groups.get(groupID);
- if (!group) {
- continue;
- }
-
- // TODO: prompt to save data to local library?
-
- group.erase();
- }
- }
-
- if (_timeToYield()) yield true;
-
-
- // TEMP: Resend tags requested by server
- try {
- for each(var tagsNode in updatedNode.xpath("fixtags/tags")) {
- var libraryID = _libID(tagsNode.getAttribute('libraryID'));
- if (libraryID && !Zotero.Libraries.isEditable(libraryID)) {
- continue;
- }
- var tagsKeys = tagsNode.textContent.split(' ');
- for each(var key in tagsKeys) {
- var sql = "SELECT tagID FROM tags WHERE libraryID=? AND key=?";
- var tagID = Zotero.DB.valueQuery(sql, [libraryID, key]);
-
- var sql = "SELECT COUNT(*) > 0 FROM itemTags WHERE tagID=?";
- if (tagID && Zotero.DB.valueQuery(sql, [tagID])) {
- var sql = "UPDATE tags SET clientDateModified=CURRENT_TIMESTAMP "
- + "WHERE tagID=?";
- Zotero.DB.query(sql, [tagID]);
- syncSession.addToUpdated({
- objectType: 'tag',
- libraryID: libraryID,
- key: key
- });
- }
- }
- }
- }
- catch (e) {
- Components.utils.reportError(e);
- Zotero.debug(e);
- }
- if (_timeToYield()) yield true;
-
-
- // Get unmodified creators embedded within items -- this is necessary if, say,
- // a creator was deleted locally and appears in a new/modified item remotely
- var embeddedCreators = {};
- for each(var creatorNode in updatedNode.xpath("items/item/creator/creator")) {
- var libraryID = _libID(creatorNode.getAttribute('libraryID'));
- var key = creatorNode.getAttribute('key');
-
- var creatorObj = Zotero.Creators.getByLibraryAndKey(libraryID, key);
- // If creator exists locally, we don't need it
- if (creatorObj) {
- continue;
- }
- // Note which embedded creators are available
- var lkh = Zotero.Creators.makeLibraryKeyHash(libraryID, key);
- if (!embeddedCreators[lkh]) {
- embeddedCreators[lkh] = true;
- }
- }
- // Make sure embedded creators aren't already provided in the <creators> node
- // This isn't necessary if the server data is correct
- for each(var creatorNode in updatedNode.xpath("creators/creator")) {
- var libraryID = _libID(creatorNode.getAttribute('libraryID'));
- var key = creatorNode.getAttribute('key');
- var lkh = Zotero.Creators.makeLibraryKeyHash(libraryID, key);
- if (embeddedCreators[lkh]) {
- var msg = "Creator " + libraryID + "/" + key + " was unnecessarily embedded in server response "
- + "in Zotero.Sync.Server.Data.processUpdatedXML()";
- Zotero.debug(msg, 2);
- Components.utils.reportError(msg)
- delete embeddedCreators[lkh];
- }
- }
- // For any embedded creators that don't exist locally and aren't already
- // included in the <creators> node, copy the node into <creators> for saving
- var creatorsNode = false;
- for each(var creatorNode in updatedNode.xpath("items/item/creator/creator")) {
- var libraryID = _libID(creatorNode.getAttribute('libraryID'));
- var key = creatorNode.getAttribute('key');
-
- var lkh = Zotero.Creators.makeLibraryKeyHash(libraryID, key);
- if (embeddedCreators[lkh]) {
- if (!creatorsNode) {
- creatorsNode = updatedNode.xpath("creators");
- if (creatorsNode.length) {
- creatorsNode = creatorsNode[0];
- }
- else {
- creatorsNode = updatedNode.ownerDocument.createElement("creators");
- updatedNode.appendChild(creatorsNode);
- }
- }
-
- Zotero.debug("Adding embedded creator " + libraryID + "/" + key + " to <creators>");
-
- creatorsNode.appendChild(creatorNode);
- delete embeddedCreators[lkh];
- }
- }
-
- if (_timeToYield()) yield true;
-
- // Other objects
- for each(var syncObject in Zotero.Sync.syncObjects) {
- var Type = syncObject.singular; // 'Item'
- var Types = syncObject.plural; // 'Items'
- var type = Type.toLowerCase(); // 'item'
- var types = Types.toLowerCase(); // 'items'
-
- var toSave = [];
- var toDelete = [];
- var toReconcile = [];
- var skipDateModifiedUpdateItems = {};
-
- // Display a warning once for each object type
- syncSession.suppressWarnings = false;
-
- //
- // Handle modified objects
- //
- Zotero.debug("Processing remotely changed " + types);
-
- typeloop:
- for each(var objectNode in updatedNode.xpath(types + "/" + type)) {
- var libraryID = _libID(objectNode.getAttribute('libraryID'));
-
- // Process remote settings
- if (type == 'setting') {
- var name = objectNode.getAttribute('name');
- if (!libraryID) {
- libraryID = 0;
- }
- Zotero.debug("Processing remote setting " + libraryID + "/" + name);
- var version = objectNode.getAttribute('version');
- var value = JSON.parse(objectNode.textContent);
- Zotero.SyncedSettings.setSynchronous(libraryID, name, value, version, true);
- continue;
- }
- else if (type == 'fulltext') {
- if (!libraryID) {
- libraryID = 0;
- }
- let key = objectNode.getAttribute('key');
- Zotero.debug("Processing remote full-text content for item " + libraryID + "/" + key);
- Zotero.Fulltext.setItemContent(
- libraryID,
- key,
- objectNode.textContent,
- {
- indexedChars: parseInt(objectNode.getAttribute('indexedChars')),
- totalChars: parseInt(objectNode.getAttribute('totalChars')),
- indexedPages: parseInt(objectNode.getAttribute('indexedPages')),
- totalPages: parseInt(objectNode.getAttribute('totalPages'))
- },
- parseInt(objectNode.getAttribute('version'))
- );
- continue;
- }
-
- var key = objectNode.getAttribute('key');
- var objLibraryKeyHash = Zotero[Types].makeLibraryKeyHash(libraryID, key);
-
- Zotero.debug("Processing remote " + type + " " + libraryID + "/" + key, 4);
- var isNewObject;
- var localDelete = false;
- var skipCR = false;
- var deletedItemKeys = null;
-
- // Get local object with same library and key
- var obj = Zotero[Types].getByLibraryAndKey(libraryID, key);
- if (obj) {
- Zotero.debug("Matching local " + type + " exists", 4);
- isNewObject = false;
-
- var objDate = Zotero.Date.sqlToDate(obj.dateModified, true);
-
- // Local object has been modified since last sync
- if ((objDate > lastLocalSyncDate &&
- objDate < Zotero.Sync.Server.nextLocalSyncDate)
- // Check for object in updated array, since it might
- // have been modified during sync process, making its
- // date equal to Zotero.Sync.Server.nextLocalSyncDate
- // and therefore excluded above
- || syncSession.objectInUpdated(obj)) {
-
- Zotero.debug("Local " + type + " " + obj.id
- + " has been modified since last sync", 4);
-
- // Merge and store related items, since CR doesn't
- // affect related items
- if (type == 'item') {
- // Remote
- var relKeys = _getFirstChildContent(objectNode, 'related');
- relKeys = relKeys ? relKeys.split(' ') : [];
- // Local
- for each(var relID in obj.relatedItems) {
- var relKey = Zotero.Items.get(relID).key;
- if (relKeys.indexOf(relKey) == -1) {
- relKeys.push(relKey);
- }
- }
- if (relKeys.length) {
- relatedItemsStore[objLibraryKeyHash] = relKeys;
- }
- Zotero.Sync.Server.Data.removeMissingRelatedItems(objectNode);
- }
-
- var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](objectNode, null, null, defaultLibraryID);
-
- // Some types we don't bother to reconcile
- if (_noMergeTypes.indexOf(type) != -1) {
- // If local is newer, send to server
- if (obj.dateModified > remoteObj.dateModified) {
- syncSession.addToUpdated(obj);
- continue;
- }
-
- // Overwrite local below
- }
- // Mark other types for conflict resolution
- else {
- // Skip item if dateModified is the only modified
- // field (and no linked creators changed)
- switch (type) {
- // Will be handled by item CR for now
- case 'creator':
- remoteCreatorStore[Zotero.Creators.getLibraryKeyHash(remoteObj)] = remoteObj;
- syncSession.removeFromUpdated(obj);
- continue;
-
- case 'item':
- var diff = obj.diff(remoteObj, false, ["dateAdded", "dateModified"]);
- if (diff) {
- Zotero.debug('Diff:');
- Zotero.debug(diff);
-
- try {
- let dateField;
- if (!Object.keys(diff[0].primary).length
- && !Object.keys(diff[1].primary).length
- && !diff[0].creators.length
- && !diff[1].creators.length
- && Object.keys(diff[0].fields).length == 1
- && (dateField = Object.keys(diff[0].fields)[0])
- && Zotero.ItemFields.isFieldOfBase(dateField, 'date')
- && /[0-9]{2}:[0-9]{2}:[0-9]{2}/.test(diff[0].fields[dateField])
- && Zotero.Date.isSQLDateTime(diff[1].fields[dateField])
- && diff[1].fields[dateField].substr(11).indexOf(diff[0].fields[dateField]) == 0) {
- Zotero.debug("Marking local item with corrupted SQL date for overwriting", 2);
- obj.setField(dateField, diff[1].fields[dateField]);
- skipDateModifiedUpdateItems[obj.id] = true;
- syncSession.removeFromUpdated(obj);
- skipCR = true;
- break;
- }
- }
- catch (e) {
- Components.utils.reportError(e);
- Zotero.debug(e, 1);
- }
- }
- else {
- // Check if creators changed
- var creatorsChanged = false;
-
- var creators = obj.getCreators();
- var remoteCreators = remoteObj.getCreators();
-
- if (creators.length != remoteCreators.length) {
- Zotero.debug('Creators have changed');
- creatorsChanged = true;
- }
- else {
- creators = creators.concat(remoteCreators);
- for each(var creator in creators) {
- var r = remoteCreatorStore[Zotero.Creators.getLibraryKeyHash(creator.ref)];
- // Doesn't include dateModified
- if (r && !r.equals(creator.ref)) {
- creatorsChanged = true;
- break;
- }
- }
- }
- if (!creatorsChanged) {
- syncSession.removeFromUpdated(obj);
- continue;
- }
- }
-
- // Always keep the parent item if there is one,
- // regardless of which side is chosen during CR
- var localParent = obj.getSourceKey();
- var remoteParent = remoteObj.getSourceKey();
- if (!localParent && remoteParent) {
- obj.setSourceKey(remoteParent);
- }
- else if (localParent && !remoteParent) {
- remoteObj.setSourceKey(localParent);
- }
-
- /*
- if (obj.deleted && !remoteObj.deleted) {
- obj = 'trashed';
- }
- else if (!obj.deleted && remoteObj.deleted) {
- remoteObj = 'trashed';
- }
- */
- break;
-
- case 'collection':
- var changed = _mergeCollection(obj, remoteObj, syncSession);
- if (!changed) {
- syncSession.removeFromUpdated(obj);
- continue;
- }
- // The merged collection needs to be saved
- skipCR = true;
- break;
-
- case 'tag':
- var changed = _mergeTag(obj, remoteObj, syncSession);
- if (!changed) {
- syncSession.removeFromUpdated(obj);
- }
- continue;
- }
-
- // TODO: order reconcile by parent/child?
-
- if (!skipCR) {
- toReconcile.push([
- obj,
- remoteObj
- ]);
-
- continue;
- }
- }
- }
- else {
- Zotero.debug("Local " + type + " has not changed", 4);
- }
-
- // Overwrite local below
- }
-
- // Object doesn't exist locally
- else {
- isNewObject = true;
-
- // Check if object has been deleted locally
- var fakeObj = {
- objectType: type,
- libraryID: libraryID,
- key: key
- };
-
- if (syncSession.objectInDeleted(fakeObj)) {
- // TODO: non-merged items
-
- switch (type) {
- case 'item':
- localDelete = true;
- break;
-
- // Auto-restore locally deleted tags and collections that
- // have changed remotely
- case 'tag':
- case 'collection':
- syncSession.removeFromDeleted(fakeObj);
-
- var msg = _generateAutoChangeLogMessage(
- type, null, objectNode.getAttribute('name')
- );
- Zotero.log(msg, 'warning');
-
- if (!syncSession.suppressWarnings) {
- var msg = _generateAutoChangeAlertMessage(
- types, null, objectNode.getAttribute('name')
- );
- alert(msg);
- syncSession.suppressWarnings = true;
- }
-
- deletedItemKeys = syncSession.getDeleted('item', libraryID);
- break;
-
- default:
- var msg = 'Cannot reconcile delete conflict for ' + type;
- var e = new Zotero.Error(msg, "FULL_SYNC_REQUIRED");
- throw (e);
- }
- }
- }
-
-
- // Temporarily remove and store related items that don't yet exist
- if (type == 'item') {
- var missing = Zotero.Sync.Server.Data.removeMissingRelatedItems(objectNode);
- if (missing.length) {
- relatedItemsStore[objLibraryKeyHash] = missing;
- }
- }
-
- // Create or overwrite locally
- //
- // If we skipped CR above, we already have an object to use
- if (!skipCR) {
- obj = Zotero.Sync.Server.Data['xmlTo' + Type](objectNode, obj, false, defaultLibraryID, deletedItemKeys);
- }
-
- if (isNewObject && type == 'tag') {
- // If a local tag matches the name of a different remote tag,
- // delete the local tag and add items linked to it to the
- // matching remote tag
- //
- // DEBUG: why use objectNode?
- var tagName = objectNode.getAttribute('name');
- var tagType = objectNode.getAttribute('type');
- tagType = tagType ? parseInt(tagType) : 0;
- var linkedItems = _deleteConflictingTag(syncSession, tagName, tagType, obj.libraryID);
- if (linkedItems) {
- var mod = false;
- for each(var id in linkedItems) {
- var added = obj.addItem(id);
- if (added) {
- mod = true;
- }
- }
- if (mod) {
- obj.dateModified = Zotero.DB.transactionDateTime;
- syncSession.addToUpdated({
- objectType: 'tag',
- libraryID: obj.libraryID,
- key: objectNode.getAttribute('key')
- });
- }
- }
- }
-
- if (localDelete) {
- // TODO: order reconcile by parent/child?
-
- toReconcile.push([
- 'deleted',
- obj
- ]);
- }
- else {
- toSave.push(obj);
- }
-
- if (type == 'item') {
- // Make sure none of the item's creators are marked as
- // deleted, which could happen if a creator was deleted
- // locally but attached to a new/modified item remotely
- // and added back in xmlToItem()
- if (obj.isRegularItem()) {
- var creators = obj.getCreators();
- for each(var creator in creators) {
- syncSession.removeFromDeleted(creator.ref);
- }
- }
- else if (obj.isImportedAttachment()) {
- // Mark new attachments for download
- if (isNewObject) {
- obj.attachmentSyncState =
- Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD;
- }
- // Set existing attachments for mtime update check
- else {
- var mtime = objectNode.getAttribute('storageModTime');
- if (mtime) {
- // Convert previously used Unix timestamps to ms-based timestamps
- if (mtime < 10000000000) {
- Zotero.debug("Converting Unix timestamp '" + mtime + "' to milliseconds");
- mtime = mtime * 1000;
- }
- itemStorageModTimes[obj.id] = parseInt(mtime);
- }
- }
- }
- }
- // Fix potential FK constraint error on fki_collectionItems_itemID_sourceItemID
- //
- // STR:
- //
- // Create an empty note on A.
- // Create an empty item on A.
- // Create a collection on A.
- // Drag item to collection on A.
- // Sync A.
- // Sync B.
- // Drag note to collection on B.
- // Sync B.
- // Drag note under item on A.
- // Sync A.
- //
- // Explanation:
- //
- // Dragging note to collection on B doesn't modify the note
- // and only sends the collection-items change.
- // When sync on A tries to add the note to the collection,
- // an error occurs, because the note is now a child note locally,
- // and a child note can't belong to a collection.
- //
- // We fix this by removing child items from collections they
- // would be in after saving, and we add their parents instead.
- else if (type == 'collection') {
- var childItems = obj.getChildItems(false, true);
- var existing = [];
- var toAdd = [];
- var toRemove = [];
- for each(var childItem in childItems) {
- existing.push(childItem.id);
- var parentItem = childItem.getSource();
- if (parentItem) {
- parentItem = Zotero.Items.get(parentItem);
- // Add parent to collection
- toAdd.push(parentItem.id);
- // Remove child from collection
- toRemove.push(childItem.id);
- }
- }
- // Add
- toAdd = Zotero.Utilities.arrayDiff(toAdd, existing);
- var changed = toAdd.length > 0;
- existing = existing.concat(toAdd);
- var origLen = existing.length;
- // Remove
- existing = Zotero.Utilities.arrayDiff(existing, toRemove);
- changed = changed || origLen != existing.length;
- // Set
- if (changed) {
- obj.childItems = existing;
- syncSession.addToUpdated(obj);
- }
- }
-
- if (_timeToYield()) yield true;
- }
-
- //
- // Handle remotely deleted objects
- //
- var deletedObjectNodes = updatedNode.xpath("deleted/" + types + "/" + type);
- if (deletedObjectNodes.length) {
- Zotero.debug("Processing remotely deleted " + types);
-
- syncSession.suppressWarnings = false;
-
- for each(var delNode in deletedObjectNodes) {
- var libraryID = _libID(delNode.getAttribute('libraryID'));
- var key = delNode.getAttribute('key');
-
- // Process remote settings deletions
- if (type == 'setting') {
- if (!libraryID) {
- libraryID = 0;
- }
- Zotero.debug("Processing remote setting " + libraryID + "/" + key);
- Zotero.Sync.EventListener.ignoreDeletions('setting', [libraryID + "/" + key]);
- Zotero.SyncedSettings.setSynchronous(libraryID, key);
- continue;
- }
-
- var obj = Zotero[Types].getByLibraryAndKey(libraryID, key);
- // Object can't be found
- if (!obj) {
- // Since it's already deleted remotely, don't include
- // the object in the deleted array if something else
- // caused its deletion during the sync
- syncSession.removeFromDeleted({
- objectType: type,
- libraryID: libraryID,
- key: key
- });
- continue;
- }
-
- var modDate = Zotero.Date.sqlToDate(obj.dateModified, true);
-
- // Local object hasn't been modified -- delete
- if (modDate < lastLocalSyncDate) {
- toDelete.push(obj.id);
- continue;
- }
-
- // Local object has been modified since last sync -- reconcile
- switch (type) {
- case 'item':
- // TODO: order reconcile by parent/child
- toReconcile.push([obj, 'deleted']);
- break;
-
- case 'tag':
- case 'collection':
- var msg = _generateAutoChangeLogMessage(
- type, obj.name, null
- );
- Zotero.log(msg, 'warning');
-
- if (!syncSession.suppressWarnings) {
- var msg = _generateAutoChangeAlertMessage(
- types, obj.name, null
- );
- alert(msg);
- syncSession.suppressWarnings = true;
- }
- continue;
-
- default:
- Components.utils.reportError('Delete reconciliation unimplemented for ' + types + ' -- ignoring');
- continue;
- }
- }
-
- if (_timeToYield()) yield true;
- }
-
-
- //
- // Reconcile objects that have changed locally and remotely
- //
- if (toReconcile.length) {
- if (Zotero.Sync.Runner.background) {
- Zotero.Sync.Server.manualSyncRequired = true;
-
- var msg = Zotero.getString('sync.error.manualInterventionRequired')
- + "\n\n"
- + Zotero.getString('sync.error.clickSyncIcon');
- var e = new Zotero.Error(msg, 0, { dialogButtonText: null });
- throw (e);
- }
-
- var mergeData = _reconcile(type, toReconcile, remoteCreatorStore);
- if (!mergeData) {
- Zotero.DB.rollbackTransaction();
- callback(false);
- return;
- }
- _processMergeData(
- syncSession,
- type,
- mergeData,
- toSave,
- toDelete,
- relatedItemsStore
- );
- }
-
- if (_timeToYield()) yield true;
-
- // Save objects
- Zotero.debug('Saving merged ' + types);
-
- if (type == 'collection') {
- for each(var col in toSave) {
- var changed = _removeChildItemsFromCollection(col, childItemStore);
- if (changed) {
- syncSession.addToUpdated(col);
- }
- }
-
- // Save collections recursively from the top down
- _saveCollections(toSave);
- }
- else if (type == 'item') {
- // Save parent items first
- for (var i=0; i<toSave.length; i++) {
- if (!toSave[i].getSourceKey()) {
- toSave[i].save({
- skipDateModifiedUpdate: !!skipDateModifiedUpdateItems[toSave[i].id]
- });
- toSave.splice(i, 1);
- i--;
- }
- }
-
- // Save the rest
- for each(var obj in toSave) {
- // Keep list of all child items being saved
- var store = false;
- if (!obj.isTopLevelItem()) {
- store = true;
- }
-
- obj.save();
-
- if (store) {
- childItemStore.push(obj.id)
- }
-
- if (_timeToYield()) yield true;
- }
-
- // Add back related items (which now exist)
- for (var libraryKeyHash in relatedItemsStore) {
- var lk = Zotero.Items.parseLibraryKeyHash(libraryKeyHash);
- var item = Zotero.Items.getByLibraryAndKey(lk.libraryID, lk.key);
- for each(var relKey in relatedItemsStore[libraryKeyHash]) {
- var relItem = Zotero.Items.getByLibraryAndKey(lk.libraryID, relKey);
- if (!relItem) {
- var msg = "Related item doesn't exist in Zotero.Sync.Server.Data.processUpdatedXML() "
- + "(" + lk.libraryID + "/" + relKey + ")";
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
- throw (e);
- }
- item.addRelatedItem(relItem.id);
- }
- item.save({
- skipDateModifiedUpdate: true
- });
- }
- }
- else if (type == 'tag') {
- // Use a special saving mode for tags to avoid an issue that
- // occurs if a tag has changed names remotely but another tag
- // conflicts with the local version after the first tag has been
- // updated in memory, causing a deletion of the local tag.
- // Using the normal save mode, when the first remote tag then
- // goes to save, the linked items aren't saved, since as far
- // as the in-memory object is concerned, they haven't changed,
- // even though they've been deleted from the DB.
- //
- // To replicate, add an item, add a tag, sync both sides,
- // rename the tag, add a new one with the old name, and sync.
- for each(var obj in toSave) {
- obj.save(true);
-
- if (_timeToYield()) yield true;
- }
- }
- else if (type == 'relation') {
- for each(var obj in toSave) {
- if (obj.exists()) {
- continue;
- }
- obj.save();
-
- if (_timeToYield()) yield true;
- }
- }
- else {
- for each(var obj in toSave) {
- obj.save();
-
- if (_timeToYield()) yield true;
- }
- }
-
- // Delete
- Zotero.debug('Deleting merged ' + types);
- if (toDelete.length) {
- // Items have to be deleted children-first
- if (type == 'item') {
- var parents = [];
- var children = [];
- for each(var id in toDelete) {
- var item = Zotero.Items.get(id);
- if (item.getSource()) {
- children.push(item.id);
- }
- else {
- parents.push(item.id);
- }
-
- if (_timeToYield()) yield true;
- }
-
- // Lock dateModified in local versions of remotely deleted
- // collections so that any deleted items within them don't
- // update them, which would trigger erroneous conflicts
- var collections = [];
- for each(var col in deletedCollectionKeys) {
- col = Zotero.Collections.getByLibraryAndKey(col.libraryID, col.key);
- // If collection never existed on this side
- if (!col) {
- continue;
- }
- col.lockDateModified();
- collections.push(col);
- }
-
- if (children.length) {
- Zotero.Sync.EventListener.ignoreDeletions('item', children);
- Zotero.Items.erase(children);
- }
- if (parents.length) {
- Zotero.Sync.EventListener.ignoreDeletions('item', parents);
- Zotero.Items.erase(parents);
- }
-
- // Unlock dateModified for deleted collections
- for each(var col in collections) {
- col.unlockDateModified();
- }
- collections = null;
- }
- else {
- Zotero.Sync.EventListener.ignoreDeletions(type, toDelete);
- Zotero[Types].erase(toDelete);
- }
- }
-
- if (_timeToYield()) yield true;
-
- // 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) {
- yield Zotero.Sync.Storage.checkForUpdatedFiles(null, null, itemStorageModTimes);
- }
- }
-
- if (_timeToYield()) yield true;
-
- callback(Zotero.Sync.Server.Data.buildUploadXML(syncSession));
- };
-
-
- /**
- * @param {Zotero.Sync.Server.Session} syncSession
- */
- this.buildUploadXML = function (syncSession) {
- //Zotero.debug(syncSession);
- var keys = syncSession.uploadKeys;
-
- var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
- .createInstance(Components.interfaces.nsIDOMParser);
- var doc = parser.parseFromString("<data/>", "text/xml");
- var docElem = doc.documentElement;
-
- // Add API version attribute
- docElem.setAttribute('version', Zotero.Sync.Server.apiVersion);
-
- // Updates
- for each(var syncObject in Zotero.Sync.syncObjects) {
- var Type = syncObject.singular; // 'Item'
- var Types = syncObject.plural; // 'Items'
- var type = Type.toLowerCase(); // 'item'
- var types = Types.toLowerCase(); // 'items'
- var objectsNode = false;
-
- if (type == 'setting') {
- continue;
- }
-
- Zotero.debug("Processing locally changed " + types);
-
- var libraryID, key;
- for (var libraryID in keys.updated[types]) {
- for (var key in keys.updated[types][libraryID]) {
- // Insert the <[types]> node
- if (!objectsNode) {
- objectsNode = docElem.appendChild(doc.createElement(types));
- }
-
- var l = parseInt(libraryID);
- l = l ? l : null;
- var obj = Zotero[Types].getByLibraryAndKey(l, key);
- if (!obj) {
- Zotero.debug("Updated " + type + " " + l + "/" + key + " has disappeared -- skipping");
- syncSession.removeFromUpdated({
- objectType: type,
- libraryID: l,
- key: key
- });
- continue;
- }
-
- if (type == 'item') {
- // itemToXML needs the sync session
- var elem = this.itemToXML(obj, doc, syncSession);
- }
- else {
- var elem = this[type + 'ToXML'](obj, doc);
- }
-
- objectsNode.appendChild(elem);
- }
- }
- }
-
- // Add unsynced settings
- var sql = "SELECT libraryID, setting, value FROM syncedSettings WHERE synced=0";
- var rows = Zotero.DB.query(sql);
- if (rows) {
- var settingsNode = doc.createElement('settings');
- for (var i=0; i<rows.length; i++) {
- var settingNode = doc.createElement('setting');
- settingNode.setAttribute('libraryID', rows[i].libraryID ? rows[i].libraryID : Zotero.libraryID);
- settingNode.setAttribute('name', rows[i].setting);
- settingNode.appendChild(doc.createTextNode(_xmlize(rows[i].value)));
- settingsNode.appendChild(settingNode);
- }
- docElem.appendChild(settingsNode);
- }
-
- if (Zotero.Prefs.get("sync.fulltext.enabled")) {
- // Add up to 500K characters of full-text content
- try {
- var rows = Zotero.Fulltext.getUnsyncedContent(500000);
- }
- catch (e) {
- Zotero.debug(e, 1);
- Components.utils.reportError(e);
- var rows = [];
- }
- if (rows.length) {
- let fulltextsNode = doc.createElement('fulltexts');
- syncSession.fulltextItems = [];
- for (let i=0; i<rows.length; i++) {
- syncSession.fulltextItems.push({
- libraryID: rows[i].libraryID,
- key: rows[i].key
- })
- let node = doc.createElement('fulltext');
- node.setAttribute('libraryID', rows[i].libraryID ? rows[i].libraryID : Zotero.libraryID);
- node.setAttribute('key', rows[i].key);
- node.setAttribute('indexedChars', rows[i].indexedChars);
- node.setAttribute('totalChars', rows[i].totalChars);
- node.setAttribute('indexedPages', rows[i].indexedPages);
- node.setAttribute('totalPages', rows[i].totalPages);
- node.appendChild(doc.createTextNode(_xmlize(rows[i].text)));
- fulltextsNode.appendChild(node);
- }
- docElem.appendChild(fulltextsNode);
- }
- }
-
- // Deletions
- var deletedNode = doc.createElement('deleted');
- var inserted = false;
-
- var defaultLibraryID = Zotero.libraryID;
-
- for each(var syncObject in Zotero.Sync.syncObjects) {
- var Type = syncObject.singular; // 'Item'
- var Types = syncObject.plural; // 'Items'
- var type = Type.toLowerCase(); // 'item'
- var types = Types.toLowerCase(); // 'items'
- var deletedObjectsNode = false;
-
- Zotero.debug('Processing locally deleted ' + types);
-
- var elementCreated = false;
- var libraryID, key;
- for (var libraryID in keys.deleted[types]) {
- for (var key in keys.deleted[types][libraryID]) {
- // Insert the <deleted> node
- if (!inserted) {
- docElem.appendChild(deletedNode);
- inserted = true;
- }
- // Insert the <deleted><[types]></deleted> node
- if (!deletedObjectsNode) {
- deletedObjectsNode = deletedNode.appendChild(doc.createElement(types));
- }
-
- var n = doc.createElement(type);
- n.setAttribute('libraryID', parseInt(libraryID) ? parseInt(libraryID) : defaultLibraryID);
- n.setAttribute('key', key);
- deletedObjectsNode.appendChild(n);
- }
- }
- }
-
-
- var s = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
- .createInstance(Components.interfaces.nsIDOMSerializer);
- var xmlstr = s.serializeToString(doc);
-
- // No updated data
- if (docElem.childNodes.length == 0) {
- return '';
- }
-
- return xmlstr;
- }
-
-
- // Remove any child items from collection, which might exist if an attachment in a collection was
- // remotely changed from a top-level item to a child item
- function _removeChildItemsFromCollection(collection, childItems) {
- if (!childItems.length) {
- return false;
- }
- var itemIDs = collection.getChildItems(true);
- // TODO: fix to always return array
- if (!itemIDs) {
- return false;
- }
- var newItemIDs = Zotero.Utilities.arrayDiff(itemIDs, childItems);
- if (itemIDs.length == newItemIDs.length) {
- return false;
- }
- collection.childItems = newItemIDs;
- return true;
- }
-
-
- function _mergeCollection(localObj, remoteObj, syncSession) {
- var diff = localObj.diff(remoteObj, false, true);
- if (!diff) {
- return false;
- }
-
- Zotero.debug("COLLECTION HAS CHANGED");
- Zotero.debug(diff);
-
- // Local is newer
- if (diff[0].primary.dateModified > diff[1].primary.dateModified) {
- Zotero.debug("Local is newer");
- var remoteIsTarget = false;
- var targetObj = localObj;
- }
- // Remote is newer
- else {
- Zotero.debug("Remote is newer");
- var remoteIsTarget = true;
- var targetObj = remoteObj;
- }
-
- if (diff[0].fields.name) {
- var msg = _generateAutoChangeLogMessage(
- 'collection', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
- );
- Zotero.log(msg, 'warning');
-
- if (!syncSession.suppressWarnings) {
- var msg = _generateAutoChangeAlertMessage(
- 'collections', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
- );
- alert(msg);
- syncSession.suppressWarnings = true;
- }
- }
-
- // Check for child collections in the other object
- // that aren't in the target one
- if (diff[1].childCollections.length) {
- // TODO: log
- // TODO: add
- throw ("Collection hierarchy conflict resolution is unimplemented");
- }
-
- // Add items to local object, which is what's saved
- if (diff[1].childItems.length) {
- var childItems = localObj.getChildItems(true);
- if (childItems) {
- localObj.childItems = childItems.concat(diff[1].childItems);
- }
- else {
- localObj.childItems = diff[1].childItems;
- }
-
- var msg = _generateCollectionItemMergeLogMessage(
- targetObj.name,
- diff[0].childItems.concat(diff[1].childItems)
- );
- Zotero.log('warning');
-
- if (!syncSession.suppressWarnings) {
-
- alert(msg);
- }
- }
-
- return true;
- }
-
-
- function _mergeTag(localObj, remoteObj, syncSession) {
- var diff = localObj.diff(remoteObj, false, true);
- if (!diff) {
- return false;
- }
-
- Zotero.debug("TAG HAS CHANGED");
- Zotero.debug(diff);
-
- // Local is newer
- if (diff[0].primary.dateModified >
- diff[1].primary.dateModified) {
- var remoteIsTarget = false;
- var targetObj = localObj;
- var targetDiff = diff[0];
- var otherDiff = diff[1];
- }
- // Remote is newer
- else {
- var remoteIsTarget = true;
- var targetObj = remoteObj;
- var targetDiff = diff[1];
- var otherDiff = diff[0];
- }
-
- if (targetDiff.fields.name) {
- var msg = _generateAutoChangeLogMessage(
- 'tag', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
- );
- Zotero.log(msg, 'warning');
-
- if (!syncSession.suppressWarnings) {
- var msg = _generateAutoChangeAlertMessage(
- 'tags', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
- );
- alert(msg);
- syncSession.suppressWarnings = true;
- }
- }
-
- // Add linked items in the other object to the target one
- if (otherDiff.linkedItems.length) {
- // need to handle changed items
-
- var linkedItems = targetObj.getLinkedItems(true);
- targetObj.linkedItems = linkedItems.concat(otherDiff.linkedItems);
-
- var msg = _generateTagItemMergeLogMessage(
- targetObj.name,
- otherDiff.linkedItems,
- remoteIsTarget
- );
- Zotero.log(msg, 'warning');
-
- if (!syncSession.suppressWarnings) {
- var msg = _generateTagItemMergeAlertMessage();
- alert(msg);
- syncSession.suppressWarnings = true;
- }
- }
-
- targetObj.save();
- return true;
- }
-
-
- /**
- * @param {String} itemTypes
- * @param {String} localName
- * @param {String} remoteName
- * @param {Boolean} [remoteMoreRecent=false]
- */
- function _generateAutoChangeAlertMessage(itemTypes, localName, remoteName, remoteMoreRecent) {
- if (localName === null) {
- var localDelete = true;
- }
- else if (remoteName === null) {
- var remoteDelete = true;
- }
-
- var msg = Zotero.getString('sync.conflict.autoChange.alert', itemTypes) + " ";
- if (localDelete) {
- msg += Zotero.getString('sync.conflict.remoteVersionsKept');
- }
- else if (remoteDelete) {
- msg += Zotero.getString('sync.conflict.localVersionsKept');
- }
- else {
- msg += Zotero.getString('sync.conflict.recentVersionsKept');
- }
- msg += "\n\n" + Zotero.getString('sync.conflict.viewErrorConsole',
- (Zotero.isStandalone ? "" : "Firefox")).replace(/\s+/, " ");
- return msg;
- }
-
-
- /**
- * @param {String} itemType
- * @param {String} localName
- * @param {String} remoteName
- * @param {Boolean} [remoteMoreRecent=false]
- */
- function _generateAutoChangeLogMessage(itemType, localName, remoteName, remoteMoreRecent) {
- if (localName === null) {
- localName = Zotero.getString('sync.conflict.deleted');
- var localDelete = true;
- }
- else if (remoteName === null) {
- remoteName = Zotero.getString('sync.conflict.deleted');
- var remoteDelete = true;
+ else if (remoteName === null) {
+ remoteName = Zotero.getString('sync.conflict.deleted');
+ var remoteDelete = true;
}
var msg = Zotero.getString('sync.conflict.autoChange.log', itemType) + "\n\n";
@@ -2465,71 +625,6 @@ Zotero.Sync.Server.Data = new function() {
}
- /**
- * Process the results of conflict resolution
- */
- function _processMergeData(syncSession, type, data, toSave, toDelete, relatedItems) {
- var Types = Zotero.Sync.syncObjects[type].plural;
- var types = Zotero.Sync.syncObjects[type].plural.toLowerCase();
-
- for each(var obj in data) {
- // TODO: do we need to make sure item isn't already being saved?
-
- // Handle items deleted during merge
- if (obj.ref == 'deleted') {
- // Deleted item was remote
- if (obj.left != 'deleted') {
- toDelete.push(obj.id);
-
- var libraryKeyHash = Zotero[Types].getLibraryKeyHash(obj.ref);
- if (relatedItems[libraryKeyHash]) {
- delete relatedItems[libraryKeyHash];
- }
-
- syncSession.addToDeleted(obj.left);
- }
- continue;
- }
-
- toSave.push(obj.ref);
-
- // Item had been deleted locally, so remove from
- // deleted array
- if (obj.left == 'deleted') {
- syncSession.removeFromDeleted(obj.ref);
- }
-
- // TODO: only upload if the local item was chosen
- // or remote item was changed
- syncSession.addToUpdated(obj.ref);
- }
- }
-
-
- this.removeMissingRelatedItems = function (itemNode) {
- var relatedNode = Zotero.Utilities.xpath(itemNode, "related");
- if (!relatedNode.length) {
- return [];
- }
- relatedNode = relatedNode[0];
- var libraryID = parseInt(itemNode.getAttribute('libraryID'));
- var exist = [];
- var missing = [];
- var relKeys = relatedNode.textContent;
- relKeys = relKeys ? relKeys.split(' ') : [];
- for each(var relKey in relKeys) {
- if (Zotero.Items.getByLibraryAndKey(libraryID, relKey)) {
- exist.push(relKey);
- }
- else {
- missing.push(relKey);
- }
- }
- relatedNode.textContent = exist.join(' ');
- return missing;
- }
-
-
function _xmlize(str) {
return str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A');
}
diff --git a/chrome/content/zotero/xpcom/sync/syncEventListeners.js b/chrome/content/zotero/xpcom/sync/syncEventListeners.js
@@ -97,9 +97,18 @@ Zotero.Sync.EventListeners.ChangeListener = new function () {
Zotero.Sync.EventListeners.AutoSyncListener = {
+ _editTimeout: 15,
+ _observerID: null,
+
init: function () {
- // Initialize save observer
- Zotero.Notifier.registerObserver(this);
+ // If auto-sync is enabled, initialize the save observer
+ if (Zotero.Prefs.get('sync.autoSync')) {
+ this.register();
+ }
+ },
+
+ register: function () {
+ _observerID = Zotero.Notifier.registerObserver(this, false, 'autosync');
},
notify: function (event, type, ids, extraData) {
@@ -108,8 +117,41 @@ Zotero.Sync.EventListeners.AutoSyncListener = {
return;
}
- if (Zotero.Prefs.get('sync.autoSync') && Zotero.Sync.Server.enabled) {
- Zotero.Sync.Runner.setSyncTimeout(false, false, true);
+ if (Zotero.Sync.Runner.syncInProgress) {
+ return;
+ }
+
+ // Only trigger sync for certain types
+ //
+ // TODO: settings, full text
+ if (Zotero.DataObjectUtilities.getTypes().indexOf(type) == -1) {
+ return;
+ }
+
+ // Determine affected libraries so only those can be synced
+ let libraryIDs = new Set();
+ if (Zotero.DataObjectUtilities.getTypes().indexOf(type) != -1) {
+ let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
+ ids.forEach(id => {
+ let lk = objectsClass.getLibraryAndKeyFromID(id);
+ if (lk) {
+ libraryIDs.add(lk.libraryID);
+ }
+ });
+ }
+
+ Zotero.Sync.Runner.setSyncTimeout(
+ this._editTimeout,
+ false,
+ {
+ libraries: libraryIDs.values()
+ }
+ );
+ },
+
+ unregister: function () {
+ if (_observerID) {
+ Zotero.Notifier.unregisterObserver(_observerID);
}
}
}
@@ -136,7 +178,7 @@ Zotero.Sync.EventListeners.IdleListener = {
},
register: function () {
- Zotero.debug("Initializing sync idle observer");
+ Zotero.debug("Registering auto-sync idle observer");
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
.getService(Components.interfaces.nsIIdleService);
idleService.addIdleObserver(this, this._idleTimeout);
@@ -148,9 +190,7 @@ Zotero.Sync.EventListeners.IdleListener = {
return;
}
- if (!Zotero.Sync.Server.enabled
- || Zotero.Sync.Server.syncInProgress
- || Zotero.Sync.Storage.syncInProgress) {
+ if (!Zotero.Sync.Runner.enabled || Zotero.Sync.Runner.syncInProgress) {
return;
}
@@ -167,10 +207,11 @@ Zotero.Sync.EventListeners.IdleListener = {
Zotero.debug("Beginning idle sync");
+ Zotero.Sync.Runner.setSyncTimeout(this._idleTimeout, true);
+
Zotero.Sync.Runner.sync({
background: true
});
- Zotero.Sync.Runner.setSyncTimeout(this._idleTimeout, true, true);
},
_backObserver: {
@@ -180,9 +221,7 @@ Zotero.Sync.EventListeners.IdleListener = {
}
Zotero.Sync.Runner.clearSyncTimeout();
- if (!Zotero.Sync.Server.enabled
- || Zotero.Sync.Server.syncInProgress
- || Zotero.Sync.Storage.syncInProgress) {
+ if (!Zotero.Sync.Runner.enabled || Zotero.Sync.Runner.syncInProgress) {
return;
}
Zotero.debug("Beginning return-from-idle sync");
@@ -193,7 +232,7 @@ Zotero.Sync.EventListeners.IdleListener = {
},
unregister: function () {
- Zotero.debug("Stopping sync idle observer");
+ Zotero.debug("Unregistering auto-sync idle observer");
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
.getService(Components.interfaces.nsIIdleService);
idleService.removeIdleObserver(this, this._idleTimeout);
diff --git a/chrome/content/zotero/xpcom/sync/syncRunner.js b/chrome/content/zotero/xpcom/sync/syncRunner.js
@@ -33,7 +33,8 @@ if (!Zotero.Sync) {
Zotero.Sync.Runner_Module = function (options = {}) {
const stopOnError = false;
- Zotero.defineProperty(this, 'background', { get: () => _background });
+ Zotero.defineProperty(this, 'enabled', { get: () => _apiKey || Zotero.Sync.Data.Local.getAPIKey() });
+ Zotero.defineProperty(this, 'syncInProgress', { get: () => _syncInProgress });
Zotero.defineProperty(this, 'lastSyncStatus', { get: () => _lastSyncStatus });
this.baseURL = options.baseURL || ZOTERO_CONFIG.API_URL;
@@ -55,10 +56,11 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
}.bind(this);
+ var _enabled = false;
var _autoSyncTimer;
- var _background;
var _firstInSession = true;
var _syncInProgress = false;
+ var _manualSyncRequired = false; // TODO: make public?
var _syncEngines = [];
var _storageEngines = [];
@@ -130,7 +132,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
_firstInSession = false;
}
- _background = !!options.background;
_syncInProgress = true;
this.updateIcons('animate');
@@ -163,12 +164,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.addError(e);
}
}.bind(this),
- background: _background,
+ background: !!options.background,
firstInSession: _firstInSession
};
let librariesToSync = yield this.checkLibraries(
- client, options, keyInfo, options.libraries
+ client,
+ options,
+ keyInfo,
+ options.libraries ? Array.from(options.libraries) : []
);
// Sync data and files, and then repeat if necessary
let attempt = 1;
@@ -290,15 +294,13 @@ Zotero.Sync.Runner_Module = function (options = {}) {
];
*/
- var syncAllLibraries = !libraries.length;
+ var syncAllLibraries = !libraries || !libraries.length;
// TODO: Ability to remove or disable editing of user library?
if (syncAllLibraries) {
if (access.user && access.user.library) {
- libraries.push(
- Zotero.Libraries.userLibraryID, Zotero.Libraries.publicationsLibraryID
- );
+ libraries = [Zotero.Libraries.userLibraryID, Zotero.Libraries.publicationsLibraryID];
}
}
else {
@@ -698,15 +700,17 @@ Zotero.Sync.Runner_Module = function (options = {}) {
/**
- * @param {Integer} [timeout=15] Timeout in seconds
- * @param {Boolean} [recurring=false]
- * @param {Boolean} [background] Triggered sync is a background sync
+ * @param {Integer} timeout - Timeout in seconds
+ * @param {Boolean} [recurring=false]
+ * @param {Object} [options] - Sync options
*/
- this.setSyncTimeout = function (timeout, recurring, background) {
- // check if server/auto-sync are enabled?
+ this.setSyncTimeout = function (timeout, recurring, options = {}) {
+ if (!Zotero.Prefs.get('sync.autoSync') || !this.enabled) {
+ return;
+ }
if (!timeout) {
- var timeout = 15;
+ throw new Error("Timeout not provided");
}
if (_autoSyncTimer) {
@@ -718,10 +722,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
createInstance(Components.interfaces.nsITimer);
}
+ var mergedOpts = {
+ background: true
+ };
+ Object.assign(mergedOpts, options);
+
// Implements nsITimerCallback
var callback = {
notify: function (timer) {
- if (!Zotero.Sync.Server.enabled) {
+ if (!_getAPIKey()) {
return;
}
@@ -730,25 +739,18 @@ Zotero.Sync.Runner_Module = function (options = {}) {
return;
}
- if (Zotero.Sync.Storage.syncInProgress) {
- Zotero.debug('Storage sync already in progress -- skipping auto-sync', 4);
- return;
- }
-
- if (Zotero.Sync.Server.syncInProgress) {
+ if (_syncInProgress) {
Zotero.debug('Sync already in progress -- skipping auto-sync', 4);
return;
}
- if (Zotero.Sync.Server.manualSyncRequired) {
+ if (_manualSyncRequired) {
Zotero.debug('Manual sync required -- skipping auto-sync', 4);
return;
}
- this.sync({
- background: background
- });
- }
+ this.sync(mergedOpts);
+ }.bind(this)
}
if (recurring) {
@@ -758,12 +760,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
);
}
else {
- if (Zotero.Sync.Storage.syncInProgress) {
- Zotero.debug('Storage sync in progress -- not setting auto-sync timeout', 4);
- return;
- }
-
- if (Zotero.Sync.Server.syncInProgress) {
+ if (_syncInProgress) {
Zotero.debug('Sync in progress -- not setting auto-sync timeout', 4);
return;
}
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -2431,20 +2431,14 @@ Zotero.Prefs = new function(){
Zotero.VersionHeader.unregister();
}
}],
- [ "zoteroDotOrgVersionHeader", function(val) {
- if (val) {
- Zotero.VersionHeader.register();
- }
- else {
- Zotero.VersionHeader.unregister();
- }
- }],
[ "sync.autoSync", function(val) {
if (val) {
- Zotero.Sync.Runner.IdleListener.register();
+ Zotero.Sync.EventListeners.AutoSyncListener.register();
+ Zotero.Sync.EventListeners.IdleListener.register();
}
else {
- Zotero.Sync.Runner.IdleListener.unregister();
+ Zotero.Sync.EventListeners.AutoSyncListener.unregister();
+ Zotero.Sync.EventListeners.IdleListener.unregister();
}
}],
[ "search.quicksearch-mode", function(val) {
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -406,30 +406,26 @@ var ZoteroPane = new function()
if (Zotero.Prefs.get('sync.autoSync')) {
yield Zotero.proxyAuthComplete.delay(1000);
- if (!Zotero.Sync.Server.enabled) {
+ if (!Zotero.Sync.Runner.enabled) {
Zotero.debug('Sync not enabled -- skipping auto-sync', 4);
- return;
}
-
- if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
+ else if (Zotero.Sync.Runner.syncInProgress) {
Zotero.debug('Sync already running -- skipping auto-sync', 4);
- return;
}
-
- if (Zotero.Sync.Server.manualSyncRequired) {
+ else if (Zotero.Sync.Server.manualSyncRequired) {
Zotero.debug('Manual sync required -- skipping auto-sync', 4);
- return;
}
-
- Zotero.Sync.Runner.sync({
- background: true
- });
+ else {
+ Zotero.Sync.Runner.sync({
+ background: true
+ });
+ }
}
- // Set sync icon to spinning or not
+ // Set sync icon to spinning if there's an existing sync
//
- // We don't bother setting an error state at open
- if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
+ // We don't bother setting an existing error state at open
+ if (Zotero.Sync.Runner.syncInProgress) {
Zotero.Sync.Runner.updateIcons('animate');
}
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
@@ -888,11 +888,6 @@ sync.status.uploadAccepted = Upload accepted \u2014 waiting for sync server
sync.status.syncingFiles = Syncing files
sync.status.syncingFullText = Syncing full-text content
-sync.fulltext.upgradePrompt.title = New: Full-Text Content Syncing
-sync.fulltext.upgradePrompt.text = Zotero can now sync the full-text content of files in your Zotero libraries with zotero.org and other linked devices, allowing you to easily search for your files wherever you are. The full-text content of your files will not be shared publicly.
-sync.fulltext.upgradePrompt.changeLater = You can change this setting later from the Sync pane of the Zotero preferences.
-sync.fulltext.upgradePrompt.enable = Use Full-Text Syncing
-
sync.storage.mbRemaining = %SMB remaining
sync.storage.kbRemaining = %SKB remaining
sync.storage.filesRemaining = %1$S/%2$S files