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; max-width: 28px;
} }
#zotero-tb-sync-stop .toolbarbutton-icon,
#zotero-tb-sync-error .toolbarbutton-icon { #zotero-tb-sync-error .toolbarbutton-icon {
width: 16px; 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.Sync.Storage.Engine.prototype.stop = function () {
Zotero.debug("Stopping file syncing for " + this.library.name);
for (let type in this.queues) { for (let type in this.queues) {
this.queues[type].stop(); this.queues[type].stop();
} }

View file

@ -49,7 +49,6 @@ Zotero.Sync.Data.Engine = function (options) {
this.libraryID = options.libraryID; this.libraryID = options.libraryID;
this.library = Zotero.Libraries.get(options.libraryID); this.library = Zotero.Libraries.get(options.libraryID);
this.libraryTypeID = this.library.libraryTypeID; this.libraryTypeID = this.library.libraryTypeID;
this.requests = [];
this.uploadBatchSize = 25; this.uploadBatchSize = 25;
this.uploadDeletionBatchSize = 50; this.uploadDeletionBatchSize = 50;
@ -93,6 +92,8 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
this.libraryTypeID = info.userID; this.libraryTypeID = info.userID;
} }
this._statusCheck();
// Check if we've synced this library with the current architecture yet // Check if we've synced this library with the current architecture yet
var libraryVersion = this.library.libraryVersion; var libraryVersion = this.library.libraryVersion;
if (!libraryVersion || libraryVersion == -1) { if (!libraryVersion || libraryVersion == -1) {
@ -101,6 +102,8 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
libraryVersion = this.library.libraryVersion; libraryVersion = this.library.libraryVersion;
} }
this._statusCheck();
// Perform a full sync if necessary, passing the getVersions() results if available. // 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 // 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: sync:
while (true) { while (true) {
this._statusCheck();
let downloadResult, uploadResult; let downloadResult, uploadResult;
try { try {
@ -199,17 +204,11 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
/** /**
* Stop all active requests * Stop the sync process
*
* @return {Promise<PromiseInspection[]>} Promise from Zotero.Promise.settle()
*/ */
Zotero.Sync.Data.Engine.prototype.stop = function () { Zotero.Sync.Data.Engine.prototype.stop = function () {
var funcs; Zotero.debug("Stopping sync for " + this.library.name);
var request; this._stopping = true;
while (request = this.requests.shift()) {
funcs.push(() => request.stop());
}
return Zotero.Promise.settle(funcs);
} }
@ -254,7 +253,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
// Get other object types // Get other object types
// //
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) { for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
this._failedCheck(); this._statusCheck();
// For items, fetch top-level items first // 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); keys.forEach(key => objectData[key] = null);
while (true) { while (true) {
this._failedCheck(); this._statusCheck();
// Get data we've downloaded in a previous loop but failed to process // Get data we've downloaded in a previous loop but failed to process
var json = []; var json = [];
@ -537,7 +536,7 @@ Zotero.Sync.Data.Engine.prototype._downloadObjects = async function (objectType,
await Zotero.Promise.map( await Zotero.Promise.map(
json, json,
async function (batch) { async function (batch) {
this._failedCheck(); this._statusCheck();
Zotero.debug(`Processing batch of downloaded ${objectTypePlural} in ${this.library.name}`); 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({ this._getOptions({
onObjectProcessed: () => { onObjectProcessed: () => {
num++; 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 // Increase the notifier batch size as we go, so that new items start coming in
// one by one but then switch to larger chunks // 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); 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 // 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 // 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 // Show conflict resolution window
if (conflicts.length) { if (conflicts.length) {
this._statusCheck();
let results = await Zotero.Sync.Data.Local.processConflicts( let results = await Zotero.Sync.Data.Local.processConflicts(
objectType, this.libraryID, conflicts, this._getOptions() objectType, this.libraryID, conflicts, this._getOptions()
); );
@ -827,6 +832,8 @@ Zotero.Sync.Data.Engine.prototype._downloadDeletions = Zotero.Promise.coroutine(
} }
if (conflicts.length) { if (conflicts.length) {
this._statusCheck();
// Sort conflicts by Date Modified // Sort conflicts by Date Modified
conflicts.sort(function (a, b) { conflicts.sort(function (a, b) {
var d1 = a.left.dateModified; 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 // Get unsynced local objects for each object type
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) { for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
this._statusCheck();
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType); let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType); let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
@ -1015,6 +1024,8 @@ Zotero.Sync.Data.Engine.prototype._startUpload = Zotero.Promise.coroutine(functi
try { try {
Zotero.debug(JSON.stringify(objectIDs)); Zotero.debug(JSON.stringify(objectIDs));
for (let objectType in objectIDs) { for (let objectType in objectIDs) {
this._statusCheck();
libraryVersion = yield this._uploadObjects( libraryVersion = yield this._uploadObjects(
objectType, objectIDs[objectType], libraryVersion objectType, objectIDs[objectType], libraryVersion
); );
@ -1022,6 +1033,8 @@ Zotero.Sync.Data.Engine.prototype._startUpload = Zotero.Promise.coroutine(functi
Zotero.debug(JSON.stringify(objectDeletions)); Zotero.debug(JSON.stringify(objectDeletions));
for (let objectType in objectDeletions) { for (let objectType in objectDeletions) {
this._statusCheck();
libraryVersion = yield this._uploadDeletions( libraryVersion = yield this._uploadDeletions(
objectType, objectDeletions[objectType], libraryVersion objectType, objectDeletions[objectType], libraryVersion
); );
@ -1562,7 +1575,7 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
// Get object types // Get object types
for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) { for (let objectType of Zotero.DataObjectUtilities.getTypesForLibrary(this.libraryID)) {
this._failedCheck(); this._statusCheck();
let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType); let objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
let ObjectType = Zotero.Utilities.capitalize(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 () { Zotero.Sync.Data.Engine.prototype._failedCheck = function () {
if (this.stopOnError && this.failed) { if (this.stopOnError && this.failed) {
Zotero.logError("Stopping on error"); Zotero.logError("Stopping on error");

View file

@ -44,7 +44,7 @@ Zotero.Sync.Data.FullTextEngine = function (options) {
this.setStatus = options.setStatus || function () {}; this.setStatus = options.setStatus || function () {};
this.onError = options.onError || function (e) {}; this.onError = options.onError || function (e) {};
this.stopOnError = options.stopOnError; this.stopOnError = options.stopOnError;
this.requestPromises = []; this._stopping = false;
this.failed = 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"); Zotero.debug("Library version hasn't changed -- skipping full-text download");
} }
this._stopCheck();
yield this._upload(); yield this._upload();
}) })
@ -95,22 +97,26 @@ Zotero.Sync.Data.FullTextEngine.prototype._download = Zotero.Promise.coroutine(f
} }
this.requestPromises = []; this.requestPromises = [];
for (let key of keys) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=449811 yield Zotero.Promise.map(
let tmpKey = key; keys,
this.requestPromises.push( (key) => {
this._stopCheck();
this.apiClient.getFullTextForItem( this.apiClient.getFullTextForItem(
this.library.libraryType, this.library.libraryTypeID, key this.library.libraryType, this.library.libraryTypeID, key
) )
.then(function (results) { .then((results) => {
this._stopCheck();
if (!results) return; if (!results) return;
return Zotero.Fulltext.setItemContent( return Zotero.Fulltext.setItemContent(
this.libraryID, tmpKey, results.data, results.version this.libraryID, key, results.data, results.version
) )
}.bind(this)) })
); },
} // Prepare twice the number of concurrent requests
yield Zotero.Promise.all(this.requestPromises); { concurrency: 8 }
);
yield Zotero.FullText.setLibraryVersion(this.libraryID, results.libraryVersion); 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; let lastItemID = 0;
while (true) { while (true) {
this._stopCheck();
let objs = yield Zotero.FullText.getUnsyncedContent(this.libraryID, { let objs = yield Zotero.FullText.getUnsyncedContent(this.libraryID, {
maxSize: this.MAX_BATCH_SIZE, maxSize: this.MAX_BATCH_SIZE,
maxItems: this.MAX_BATCH_ITEMS, 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* () { Zotero.Sync.Data.FullTextEngine.prototype.stop = Zotero.Promise.coroutine(function* () {
// TODO: Cancel requests // TODO: Cancel requests?
throw new Error("Unimplemented"); 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 _autoSyncTimer;
var _firstInSession = true; var _firstInSession = true;
var _syncInProgress = false; var _syncInProgress = false;
var _stopping = false;
var _manualSyncRequired = false; // TODO: make public? var _manualSyncRequired = false; // TODO: make public?
var _syncEngines = []; var _currentEngine = null;
var _storageEngines = [];
var _storageControllers = {}; var _storageControllers = {};
var _lastSyncStatus; var _lastSyncStatus;
@ -117,6 +117,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
return false; return false;
} }
_syncInProgress = true; _syncInProgress = true;
_stopping = false;
yield Zotero.Notifier.trigger('start', 'sync', []); yield Zotero.Notifier.trigger('start', 'sync', []);
@ -139,9 +140,10 @@ Zotero.Sync.Runner_Module = function (options = {}) {
let client = this.getAPIClient({ apiKey }); let client = this.getAPIClient({ apiKey });
let keyInfo = yield this.checkAccess(client, options); let keyInfo = yield this.checkAccess(client, options);
_stopCheck();
let emptyLibraryContinue = yield this.checkEmptyLibrary(keyInfo); let emptyLibraryContinue = yield this.checkEmptyLibrary(keyInfo);
if (!emptyLibraryContinue) { if (!emptyLibraryContinue) {
yield this.end(options);
Zotero.debug("Syncing cancelled because user library is empty"); Zotero.debug("Syncing cancelled because user library is empty");
return false; return false;
} }
@ -150,7 +152,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
.getService(Components.interfaces.nsIWindowMediator); .getService(Components.interfaces.nsIWindowMediator);
let lastWin = wm.getMostRecentWindow("navigator:browser"); let lastWin = wm.getMostRecentWindow("navigator:browser");
if (!(yield Zotero.Sync.Data.Local.checkUser(lastWin, keyInfo.userID, keyInfo.username))) { if (!(yield Zotero.Sync.Data.Local.checkUser(lastWin, keyInfo.userID, keyInfo.username))) {
yield this.end(options);
Zotero.debug("User cancelled sync on username mismatch"); Zotero.debug("User cancelled sync on username mismatch");
return false; return false;
} }
@ -179,6 +180,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
options.libraries ? Array.from(options.libraries) : [] options.libraries ? Array.from(options.libraries) : []
); );
_stopCheck();
// If items not yet loaded for libraries we need, load them now // If items not yet loaded for libraries we need, load them now
for (let libraryID of librariesToSync) { for (let libraryID of librariesToSync) {
let library = Zotero.Libraries.get(libraryID); 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 // Sync data and files, and then repeat if necessary
let attempt = 1; let attempt = 1;
let successfulLibraries = new Set(librariesToSync); let successfulLibraries = new Set(librariesToSync);
while (librariesToSync.length) { while (librariesToSync.length) {
_stopCheck();
if (attempt > 3) { if (attempt > 3) {
// TODO: Back off and/or nicer error // TODO: Back off and/or nicer error
throw new Error("Too many sync attempts -- stopping"); throw new Error("Too many sync attempts -- stopping");
@ -201,6 +208,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
successfulLibraries.delete(libraryID); successfulLibraries.delete(libraryID);
}); });
_stopCheck();
// Run file sync on all libraries that passed the last data sync // Run file sync on all libraries that passed the last data sync
librariesToSync = yield _doFileSync(nextLibraries, engineOptions); librariesToSync = yield _doFileSync(nextLibraries, engineOptions);
if (librariesToSync.length) { if (librariesToSync.length) {
@ -208,6 +217,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
continue; continue;
} }
_stopCheck();
// Run full-text sync on all libraries that haven't failed a data sync // Run full-text sync on all libraries that haven't failed a data sync
librariesToSync = yield _doFullTextSync([...successfulLibraries], engineOptions); librariesToSync = yield _doFullTextSync([...successfulLibraries], engineOptions);
if (librariesToSync.length) { if (librariesToSync.length) {
@ -550,13 +561,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
var _doDataSync = Zotero.Promise.coroutine(function* (libraries, options, skipUpdateLastSyncTime) { var _doDataSync = Zotero.Promise.coroutine(function* (libraries, options, skipUpdateLastSyncTime) {
var successfulLibraries = []; var successfulLibraries = [];
for (let libraryID of libraries) { for (let libraryID of libraries) {
_stopCheck();
try { try {
let opts = {}; let opts = {};
Object.assign(opts, options); Object.assign(opts, options);
opts.libraryID = libraryID; opts.libraryID = libraryID;
let engine = new Zotero.Sync.Data.Engine(opts); _currentEngine = new Zotero.Sync.Data.Engine(opts);
yield engine.start(); yield _currentEngine.start();
_currentEngine = null;
successfulLibraries.push(libraryID); successfulLibraries.push(libraryID);
} }
catch (e) { catch (e) {
@ -597,6 +610,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.setSyncStatus(Zotero.getString('sync.status.syncingFiles')); this.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
var resyncLibraries = [] var resyncLibraries = []
for (let libraryID of libraries) { for (let libraryID of libraries) {
_stopCheck();
try { try {
let opts = {}; let opts = {};
Object.assign(opts, options); 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); throw new Error("Too many file sync attempts for library " + libraryID);
} }
tries--; tries--;
let engine = new Zotero.Sync.Storage.Engine(opts); _currentEngine = new Zotero.Sync.Storage.Engine(opts);
let results = yield engine.start(); let results = yield _currentEngine.start();
_currentEngine = null;
if (results.syncRequired) { if (results.syncRequired) {
resyncLibraries.push(libraryID); resyncLibraries.push(libraryID);
} }
@ -624,6 +639,15 @@ Zotero.Sync.Runner_Module = function (options = {}) {
} }
} }
catch (e) { 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.debug("File sync failed for library " + libraryID);
Zotero.logError(e); Zotero.logError(e);
this.checkError(e); this.checkError(e);
@ -652,15 +676,21 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.setSyncStatus(Zotero.getString('sync.status.syncingFullText')); this.setSyncStatus(Zotero.getString('sync.status.syncingFullText'));
var resyncLibraries = []; var resyncLibraries = [];
for (let libraryID of libraries) { for (let libraryID of libraries) {
_stopCheck();
try { try {
let opts = {}; let opts = {};
Object.assign(opts, options); Object.assign(opts, options);
opts.libraryID = libraryID; opts.libraryID = libraryID;
let engine = new Zotero.Sync.Data.FullTextEngine(opts); _currentEngine = new Zotero.Sync.Data.FullTextEngine(opts);
yield engine.start(); yield _currentEngine.start();
_currentEngine = null;
} }
catch (e) { catch (e) {
if (e instanceof Zotero.Sync.UserCancelledException) {
throw e;
}
if (e instanceof Zotero.HTTP.UnexpectedStatusException && e.status == 412) { if (e instanceof Zotero.HTTP.UnexpectedStatusException && e.status == 412) {
resyncLibraries.push(libraryID); resyncLibraries.push(libraryID);
continue; continue;
@ -762,8 +792,11 @@ Zotero.Sync.Runner_Module = function (options = {}) {
this.stop = function () { this.stop = function () {
_syncEngines.forEach(engine => engine.stop()); this.setSyncStatus(Zotero.getString('sync.stopping'));
_storageEngines.forEach(engine => engine.stop()); _stopping = true;
if (_currentEngine) {
_currentEngine.stop();
}
} }
@ -1143,14 +1176,17 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Update sync icon // Update sync icon
var syncIcon = doc.getElementById('zotero-tb-sync'); var syncIcon = doc.getElementById('zotero-tb-sync');
var stopIcon = doc.getElementById('zotero-tb-sync-stop');
if (state == 'animate') { if (state == 'animate') {
syncIcon.setAttribute('status', state); syncIcon.setAttribute('status', state);
// Disable button while spinning // Disable button while spinning
syncIcon.disabled = true; syncIcon.disabled = true;
stopIcon.hidden = false;
} }
else { else {
syncIcon.removeAttribute('status'); syncIcon.removeAttribute('status');
syncIcon.disabled = false; 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 // Set as .apiKey on Runner in tests or set in login manager
return _apiKey || Zotero.Sync.Data.Local.getAPIKey() return _apiKey || Zotero.Sync.Data.Local.getAPIKey()
}) })
function _stopCheck() {
if (_stopping) {
throw new Zotero.Sync.UserCancelledException;
}
}
} }

View file

@ -210,6 +210,10 @@
</toolbarbutton> </toolbarbutton>
</hbox> </hbox>
<hbox align="center" pack="end"> <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"> <hbox id="zotero-tb-sync-progress-box" hidden="true" align="center">
<!-- TODO: localize --> <!-- TODO: localize -->
<toolbarbutton id="zotero-tb-sync-storage-cancel" <toolbarbutton id="zotero-tb-sync-storage-cancel"

View file

@ -237,6 +237,7 @@
<!ENTITY zotero.integration.references.label "References in Bibliography"> <!ENTITY zotero.integration.references.label "References in Bibliography">
<!ENTITY zotero.sync.stop "Stop Sync">
<!ENTITY zotero.sync.error "Sync Error"> <!ENTITY zotero.sync.error "Sync Error">
<!ENTITY zotero.sync.storage.progress "Progress:"> <!ENTITY zotero.sync.storage.progress "Progress:">
<!ENTITY zotero.sync.storage.downloads "Downloads:"> <!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.sync = Sync
sync.syncWith = Sync with %S sync.syncWith = Sync with %S
sync.stopping = Stopping…
sync.cancel = Cancel Sync sync.cancel = Cancel Sync
sync.openSyncPreferences = Open Sync Preferences sync.openSyncPreferences = Open Sync Preferences
sync.resetGroupAndSync = Reset Group and Sync sync.resetGroupAndSync = Reset Group and Sync

View file

@ -555,7 +555,7 @@
list-style-image: url('chrome://zotero/skin/toolbar-go-arrow.png'); 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); list-style-image: url(chrome://zotero/skin/control_stop_blue.png);
margin-right: 0; margin-right: 0;
@ -745,7 +745,7 @@
.zotero-menuitem-create-report { list-style-image: url('chrome://zotero/skin/treeitem-report@2x.png'); } .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-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-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 { 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-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'); } #zotero-pane-stack[fullscreenmode="true"] #zotero-tb-fullscreen { list-style-image: url('chrome://zotero/skin/toolbar-fullscreen-top@2x.png'); }