commit 1da18e4ca7919098be3811690e1c8d70b388567c
parent 7729dcafc0bee94603f73ee8fee65ee75c7095e9
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 7 Jul 2017 05:18:23 -0400
Add "Stop Sync" button
Stops all syncing (not just file syncing like in 4.0) as soon as
possible.
Diffstat:
9 files changed, 134 insertions(+), 42 deletions(-)
diff --git a/chrome/content/zotero-platform/mac/overlay.css b/chrome/content/zotero-platform/mac/overlay.css
@@ -112,6 +112,7 @@
max-width: 28px;
}
+ #zotero-tb-sync-stop .toolbarbutton-icon,
#zotero-tb-sync-error .toolbarbutton-icon {
width: 16px;
}
diff --git a/chrome/content/zotero/xpcom/storage/storageEngine.js b/chrome/content/zotero/xpcom/storage/storageEngine.js
@@ -282,6 +282,7 @@ Zotero.Sync.Storage.Engine.prototype.start = Zotero.Promise.coroutine(function*
Zotero.Sync.Storage.Engine.prototype.stop = function () {
+ Zotero.debug("Stopping file syncing for " + this.library.name);
for (let type in this.queues) {
this.queues[type].stop();
}
diff --git a/chrome/content/zotero/xpcom/sync/syncEngine.js b/chrome/content/zotero/xpcom/sync/syncEngine.js
@@ -49,7 +49,6 @@ Zotero.Sync.Data.Engine = function (options) {
this.libraryID = options.libraryID;
this.library = Zotero.Libraries.get(options.libraryID);
this.libraryTypeID = this.library.libraryTypeID;
- this.requests = [];
this.uploadBatchSize = 25;
this.uploadDeletionBatchSize = 50;
@@ -93,6 +92,8 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
this.libraryTypeID = info.userID;
}
+ this._statusCheck();
+
// Check if we've synced this library with the current architecture yet
var libraryVersion = this.library.libraryVersion;
if (!libraryVersion || libraryVersion == -1) {
@@ -101,6 +102,8 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
libraryVersion = this.library.libraryVersion;
}
+ this._statusCheck();
+
// Perform a full sync if necessary, passing the getVersions() results if available.
//
// The full-sync flag (libraryID == -1) is set at the end of a successful upgrade, so this
@@ -120,6 +123,8 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
sync:
while (true) {
+ this._statusCheck();
+
let downloadResult, uploadResult;
try {
@@ -199,17 +204,11 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
/**
- * Stop all active requests
- *
- * @return {Promise<PromiseInspection[]>} Promise from Zotero.Promise.settle()
+ * Stop the sync process
*/
Zotero.Sync.Data.Engine.prototype.stop = function () {
- var funcs;
- var request;
- while (request = this.requests.shift()) {
- funcs.push(() => request.stop());
- }
- return Zotero.Promise.settle(funcs);
+ Zotero.debug("Stopping sync for " + this.library.name);
+ this._stopping = true;
}
@@ -254,7 +253,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
// Get other object types
//
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
- this._failedCheck();
+ this._statusCheck();
// For items, fetch top-level items first
//
@@ -496,7 +495,7 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
keys.forEach(key => objectData[key] = null);
while (true) {
- this._failedCheck();
+ this._statusCheck();
// Get data we've downloaded in a previous loop but failed to process
var json = [];
@@ -537,7 +536,7 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
await Zotero.Promise.map(
json,
async function (batch) {
- this._failedCheck();
+ this._statusCheck();
Zotero.debug(`Processing batch of downloaded ${objectTypePlural} in ${this.library.name}`);
@@ -559,6 +558,10 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
this._getOptions({
onObjectProcessed: () => {
num++;
+ // Check for stop every 5 items
+ if (num % 5 == 0) {
+ this._statusCheck();
+ }
},
// Increase the notifier batch size as we go, so that new items start coming in
// one by one but then switch to larger chunks
@@ -616,7 +619,7 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
await this._restoreRestoredCollectionItems(restored);
}
- this._failedCheck();
+ this._statusCheck();
// If all requests were successful, such that we had a chance to see all keys, remove keys we
// didn't see from the sync queue so they don't keep being retried forever
@@ -665,6 +668,8 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
// Show conflict resolution window
if (conflicts.length) {
+ this._statusCheck();
+
let results = await Zotero.Sync.Data.Local.processConflicts(
objectType, this.libraryID, conflicts, this._getOptions()
);
@@ -827,6 +832,8 @@ Zotero.Sync.Data.Engine.prototype._downloadDeletions = Zotero.Promise.coroutine(
}
if (conflicts.length) {
+ this._statusCheck();
+
// Sort conflicts by Date Modified
conflicts.sort(function (a, b) {
var d1 = a.left.dateModified;
@@ -936,6 +943,8 @@ Zotero.Sync.Data.Engine.prototype._startUpload = Zotero.Promise.coroutine(functi
// Get unsynced local objects for each object type
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
+ this._statusCheck();
+
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
@@ -1015,6 +1024,8 @@ Zotero.Sync.Data.Engine.prototype._startUpload = Zotero.Promise.coroutine(functi
try {
Zotero.debug(JSON.stringify(objectIDs));
for (let objectType in objectIDs) {
+ this._statusCheck();
+
libraryVersion = yield this._uploadObjects(
objectType, objectIDs[objectType], libraryVersion
);
@@ -1022,6 +1033,8 @@ Zotero.Sync.Data.Engine.prototype._startUpload = Zotero.Promise.coroutine(functi
Zotero.debug(JSON.stringify(objectDeletions));
for (let objectType in objectDeletions) {
+ this._statusCheck();
+
libraryVersion = yield this._uploadDeletions(
objectType, objectDeletions[objectType], libraryVersion
);
@@ -1562,7 +1575,7 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
// Get object types
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
- this._failedCheck();
+ this._statusCheck();
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
let ObjectType = Zotero.Utilities.capitalize(objectType);
@@ -1774,6 +1787,19 @@ Zotero.Sync.Data.Engine.prototype._checkObjectUploadError = Zotero.Promise.corou
});
+Zotero.Sync.Data.Engine.prototype._statusCheck = function () {
+ this._stopCheck();
+ this._failedCheck();
+}
+
+
+Zotero.Sync.Data.Engine.prototype._stopCheck = function () {
+ if (!this._stopping) return;
+ Zotero.debug("Sync stopped for " + this.library.name);
+ throw new Zotero.Sync.UserCancelledException;
+}
+
+
Zotero.Sync.Data.Engine.prototype._failedCheck = function () {
if (this.stopOnError && this.failed) {
Zotero.logError("Stopping on error");
diff --git a/chrome/content/zotero/xpcom/sync/syncFullTextEngine.js b/chrome/content/zotero/xpcom/sync/syncFullTextEngine.js
@@ -44,7 +44,7 @@ Zotero.Sync.Data.FullTextEngine = function (options) {
this.setStatus = options.setStatus || function () {};
this.onError = options.onError || function (e) {};
this.stopOnError = options.stopOnError;
- this.requestPromises = [];
+ this._stopping = false;
this.failed = false;
}
@@ -61,6 +61,8 @@ Zotero.Sync.Data.FullTextEngine.prototype.start = Zotero.Promise.coroutine(funct
Zotero.debug("Library version hasn't changed -- skipping full-text download");
}
+ this._stopCheck();
+
yield this._upload();
})
@@ -95,22 +97,26 @@ Zotero.Sync.Data.FullTextEngine.prototype._download = Zotero.Promise.coroutine(f
}
this.requestPromises = [];
- for (let key of keys) {
- // https://bugzilla.mozilla.org/show_bug.cgi?id=449811
- let tmpKey = key;
- this.requestPromises.push(
+
+ yield Zotero.Promise.map(
+ keys,
+ (key) => {
+ this._stopCheck();
this.apiClient.getFullTextForItem(
this.library.libraryType, this.library.libraryTypeID, key
)
- .then(function (results) {
+ .then((results) => {
+ this._stopCheck();
if (!results) return;
return Zotero.Fulltext.setItemContent(
- this.libraryID, tmpKey, results.data, results.version
+ this.libraryID, key, results.data, results.version
)
- }.bind(this))
- );
- }
- yield Zotero.Promise.all(this.requestPromises);
+ })
+ },
+ // Prepare twice the number of concurrent requests
+ { concurrency: 8 }
+ );
+
yield Zotero.FullText.setLibraryVersion(this.libraryID, results.libraryVersion);
});
@@ -125,6 +131,8 @@ Zotero.Sync.Data.FullTextEngine.prototype._upload = Zotero.Promise.coroutine(fun
let lastItemID = 0;
while (true) {
+ this._stopCheck();
+
let objs = yield Zotero.FullText.getUnsyncedContent(this.libraryID, {
maxSize: this.MAX_BATCH_SIZE,
maxItems: this.MAX_BATCH_ITEMS,
@@ -190,6 +198,13 @@ Zotero.Sync.Data.FullTextEngine.prototype._upload = Zotero.Promise.coroutine(fun
Zotero.Sync.Data.FullTextEngine.prototype.stop = Zotero.Promise.coroutine(function* () {
- // TODO: Cancel requests
- throw new Error("Unimplemented");
+ // TODO: Cancel requests?
+ this._stopping = true;
})
+
+
+Zotero.Sync.Data.FullTextEngine.prototype._stopCheck = function () {
+ if (!this._stopping) return;
+ Zotero.debug("Full-text sync stopped for " + this.library.name);
+ throw new Zotero.Sync.UserCancelledException;
+}
diff --git a/chrome/content/zotero/xpcom/sync/syncRunner.js b/chrome/content/zotero/xpcom/sync/syncRunner.js
@@ -68,10 +68,10 @@ Zotero.Sync.Runner_Module = function (options = {}) {
var _autoSyncTimer;
var _firstInSession = true;
var _syncInProgress = false;
+ var _stopping = false;
var _manualSyncRequired = false; // TODO: make public?
- var _syncEngines = [];
- var _storageEngines = [];
+ var _currentEngine = null;
var _storageControllers = {};
var _lastSyncStatus;
@@ -117,6 +117,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
return false;
}
_syncInProgress = true;
+ _stopping = false;
yield Zotero.Notifier.trigger('start', 'sync', []);
@@ -139,9 +140,10 @@ Zotero.Sync.Runner_Module = function (options = {}) {
let client = this.getAPIClient({ apiKey });
let keyInfo = yield this.checkAccess(client, options);
+ _stopCheck();
+
let emptyLibraryContinue = yield this.checkEmptyLibrary(keyInfo);
if (!emptyLibraryContinue) {
- yield this.end(options);
Zotero.debug("Syncing cancelled because user library is empty");
return false;
}
@@ -150,7 +152,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
.getService(Components.interfaces.nsIWindowMediator);
let lastWin = wm.getMostRecentWindow("navigator:browser");
if (!(yield Zotero.Sync.Data.Local.checkUser(lastWin, keyInfo.userID, keyInfo.username))) {
- yield this.end(options);
Zotero.debug("User cancelled sync on username mismatch");
return false;
}
@@ -179,6 +180,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
options.libraries ? Array.from(options.libraries) : []
);
+ _stopCheck();
+
// If items not yet loaded for libraries we need, load them now
for (let libraryID of librariesToSync) {
let library = Zotero.Libraries.get(libraryID);
@@ -187,10 +190,14 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
}
+ _stopCheck();
+
// Sync data and files, and then repeat if necessary
let attempt = 1;
let successfulLibraries = new Set(librariesToSync);
while (librariesToSync.length) {
+ _stopCheck();
+
if (attempt > 3) {
// TODO: Back off and/or nicer error
throw new Error("Too many sync attempts -- stopping");
@@ -201,6 +208,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
successfulLibraries.delete(libraryID);
});
+ _stopCheck();
+
// Run file sync on all libraries that passed the last data sync
librariesToSync = yield _doFileSync(nextLibraries, engineOptions);
if (librariesToSync.length) {
@@ -208,6 +217,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
continue;
}
+ _stopCheck();
+
// Run full-text sync on all libraries that haven't failed a data sync
librariesToSync = yield _doFullTextSync([...successfulLibraries], engineOptions);
if (librariesToSync.length) {
@@ -550,13 +561,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
var _doDataSync = Zotero.Promise.coroutine(function* (libraries, options, skipUpdateLastSyncTime) {
var successfulLibraries = [];
for (let libraryID of libraries) {
+ _stopCheck();
try {
let opts = {};
Object.assign(opts, options);
opts.libraryID = libraryID;
- let engine = new Zotero.Sync.Data.Engine(opts);
- yield engine.start();
+ _currentEngine = new Zotero.Sync.Data.Engine(opts);
+ yield _currentEngine.start();
+ _currentEngine = null;
successfulLibraries.push(libraryID);
}
catch (e) {
@@ -597,6 +610,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
var resyncLibraries = []
for (let libraryID of libraries) {
+ _stopCheck();
try {
let opts = {};
Object.assign(opts, options);
@@ -611,8 +625,9 @@ Zotero.Sync.Runner_Module = function (options = {}) {
throw new Error("Too many file sync attempts for library " + libraryID);
}
tries--;
- let engine = new Zotero.Sync.Storage.Engine(opts);
- let results = yield engine.start();
+ _currentEngine = new Zotero.Sync.Storage.Engine(opts);
+ let results = yield _currentEngine.start();
+ _currentEngine = null;
if (results.syncRequired) {
resyncLibraries.push(libraryID);
}
@@ -624,6 +639,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
}
}
catch (e) {
+ if (e instanceof Zotero.Sync.UserCancelledException) {
+ if (e.advanceToNextLibrary) {
+ Zotero.debug("Storage sync cancelled for library " + libraryID + " -- "
+ + "advancing to next library");
+ continue;
+ }
+ throw e;
+ }
+
Zotero.debug("File sync failed for library " + libraryID);
Zotero.logError(e);
this.checkError(e);
@@ -652,15 +676,21 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.setSyncStatus(Zotero.getString('sync.status.syncingFullText'));
var resyncLibraries = [];
for (let libraryID of libraries) {
+ _stopCheck();
try {
let opts = {};
Object.assign(opts, options);
opts.libraryID = libraryID;
- let engine = new Zotero.Sync.Data.FullTextEngine(opts);
- yield engine.start();
+ _currentEngine = new Zotero.Sync.Data.FullTextEngine(opts);
+ yield _currentEngine.start();
+ _currentEngine = null;
}
catch (e) {
+ if (e instanceof Zotero.Sync.UserCancelledException) {
+ throw e;
+ }
+
if (e instanceof Zotero.HTTP.UnexpectedStatusException && e.status == 412) {
resyncLibraries.push(libraryID);
continue;
@@ -762,8 +792,11 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.stop = function () {
- _syncEngines.forEach(engine => engine.stop());
- _storageEngines.forEach(engine => engine.stop());
+ this.setSyncStatus(Zotero.getString('sync.stopping'));
+ _stopping = true;
+ if (_currentEngine) {
+ _currentEngine.stop();
+ }
}
@@ -1143,14 +1176,17 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Update sync icon
var syncIcon = doc.getElementById('zotero-tb-sync');
+ var stopIcon = doc.getElementById('zotero-tb-sync-stop');
if (state == 'animate') {
syncIcon.setAttribute('status', state);
// Disable button while spinning
syncIcon.disabled = true;
+ stopIcon.hidden = false;
}
else {
syncIcon.removeAttribute('status');
syncIcon.disabled = false;
+ stopIcon.hidden = true;
}
}
@@ -1386,4 +1422,11 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Set as .apiKey on Runner in tests or set in login manager
return _apiKey || Zotero.Sync.Data.Local.getAPIKey()
})
+
+
+ function _stopCheck() {
+ if (_stopping) {
+ throw new Zotero.Sync.UserCancelledException;
+ }
+ }
}
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
@@ -210,6 +210,10 @@
</toolbarbutton>
</hbox>
<hbox align="center" pack="end">
+ <toolbarbutton id="zotero-tb-sync-stop"
+ tooltiptext="&zotero.sync.stop;"
+ oncommand="this.hidden = true; Zotero.Sync.Runner.stop()"
+ hidden="true"/>
<hbox id="zotero-tb-sync-progress-box" hidden="true" align="center">
<!-- TODO: localize -->
<toolbarbutton id="zotero-tb-sync-storage-cancel"
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
@@ -237,6 +237,7 @@
<!ENTITY zotero.integration.references.label "References in Bibliography">
+<!ENTITY zotero.sync.stop "Stop Sync">
<!ENTITY zotero.sync.error "Sync Error">
<!ENTITY zotero.sync.storage.progress "Progress:">
<!ENTITY zotero.sync.storage.downloads "Downloads:">
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
@@ -865,6 +865,7 @@ styles.abbreviations.missingInfo = The abbreviations file "%1$S" does not specif
sync.sync = Sync
sync.syncWith = Sync with %S
+sync.stopping = Stopping…
sync.cancel = Cancel Sync
sync.openSyncPreferences = Open Sync Preferences
sync.resetGroupAndSync = Reset Group and Sync
diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css
@@ -555,7 +555,7 @@
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow.png');
}
-#zotero-tb-sync-storage-cancel
+#zotero-tb-sync-stop
{
list-style-image: url(chrome://zotero/skin/control_stop_blue.png);
margin-right: 0;
@@ -745,7 +745,7 @@
.zotero-menuitem-create-report { list-style-image: url('chrome://zotero/skin/treeitem-report@2x.png'); }
#zotero-tb-advanced-search { list-style-image: url('chrome://zotero/skin/toolbar-advanced-search@2x.png'); }
#zotero-tb-locate { list-style-image: url('chrome://zotero/skin/toolbar-go-arrow@2x.png'); }
- #zotero-tb-sync-storage-cancel { list-style-image: url(chrome://zotero/skin/control_stop_blue@2x.png); margin-right: 0; }
+ #zotero-tb-sync-stop { list-style-image: url(chrome://zotero/skin/control_stop_blue@2x.png); margin-right: 0; }
#zotero-tb-sync-error { list-style-image: url(chrome://zotero/skin/error@2x.png); }
#zotero-tb-sync-error[state=warning] { list-style-image: url(chrome://zotero/skin/warning@2x.png); }
#zotero-pane-stack[fullscreenmode="true"] #zotero-tb-fullscreen { list-style-image: url('chrome://zotero/skin/toolbar-fullscreen-top@2x.png'); }