Add "Stop Sync" button

Stops all syncing (not just file syncing like in 4.0) as soon as
possible.
This commit is contained in:
Dan Stillman 2017-07-07 05:18:23 -04:00
parent 7729dcafc0
commit 1da18e4ca7
9 changed files with 134 additions and 42 deletions

View file

@ -112,6 +112,7 @@
max-width: 28px;
}
#zotero-tb-sync-stop .toolbarbutton-icon,
#zotero-tb-sync-error .toolbarbutton-icon {
width: 16px;
}

View file

@ -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();
}

View file

@ -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");

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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"

View file

@ -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:">

View file

@ -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

View file

@ -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'); }