diff --git a/chrome/content/zotero/xpcom/sync/syncEngine.js b/chrome/content/zotero/xpcom/sync/syncEngine.js index d3831bed33..e9895dc00a 100644 --- a/chrome/content/zotero/xpcom/sync/syncEngine.js +++ b/chrome/content/zotero/xpcom/sync/syncEngine.js @@ -835,141 +835,116 @@ Zotero.Sync.Data.Engine.prototype._uploadObjects = Zotero.Promise.coroutine(func let results; let numSuccessful = 0; - try { - ({ libraryVersion, results } = yield this.apiClient.uploadObjects( - this.library.libraryType, - this.libraryTypeID, - "POST", - libraryVersion, - objectType, - batch - )); - - Zotero.debug("==="); - Zotero.debug(results); - - // Mark successful and unchanged objects as synced with new version, - // and save uploaded JSON to cache - let ids = []; - let toSave = []; - let toCache = []; - for (let state of ['successful', 'unchanged']) { - for (let index in results[state]) { - let current = results[state][index]; - // 'successful' includes objects, not keys - let key = state == 'successful' ? current.key : current; - - if (key != batch[index].key) { - throw new Error("Key mismatch (" + key + " != " + batch[index].key + ")"); - } - - let obj = yield objectsClass.getByLibraryAndKeyAsync( - this.libraryID, key, { noCache: true } - ) - ids.push(obj.id); - - if (state == 'successful') { - // Update local object with saved data if necessary - yield obj.loadAllData(); - obj.fromJSON(current.data); - toSave.push(obj); - toCache.push(current); - } - else { - // This won't reflect the actual version of the item on the server, but - // it will guarantee that the item won't be redownloaded unnecessarily - // in the case of a full sync, because the version will be higher than - // whatever version is on the server. - batch[index].version = libraryVersion - toCache.push(batch[index]); - } - - numSuccessful++; - // Remove from batch to mark as successful - delete batch[index]; - } - } - yield Zotero.Sync.Data.Local.saveCacheObjects( - objectType, this.libraryID, toCache - ); - yield Zotero.DB.executeTransaction(function* () { - for (let i = 0; i < toSave.length; i++) { - yield toSave[i].save(); - } - this.library.libraryVersion = libraryVersion; - yield this.library.save(); - objectsClass.updateVersion(ids, libraryVersion); - objectsClass.updateSynced(ids, true); - }.bind(this)); - - // Handle failed objects - for (let index in results.failed) { - let { code, message, data } = results.failed[index]; - let e = new Error(message); - e.name = "ZoteroObjectUploadError"; - e.code = code; - if (data) { - e.data = data; - } - Zotero.logError("Error for " + objectType + " " + batch[index].key + " in " - + this.library.name + ":\n\n" + e); + ({ libraryVersion, results } = yield this.apiClient.uploadObjects( + this.library.libraryType, + this.libraryTypeID, + "POST", + libraryVersion, + objectType, + batch + )); + + Zotero.debug("==="); + Zotero.debug(results); + + // Mark successful and unchanged objects as synced with new version, + // and save uploaded JSON to cache + let ids = []; + let toSave = []; + let toCache = []; + for (let state of ['successful', 'unchanged']) { + for (let index in results[state]) { + let current = results[state][index]; + // 'successful' includes objects, not keys + let key = state == 'successful' ? current.key : current; - // This shouldn't happen, because the upload request includes a library - // version and should prevent an outdated upload before the object version is - // checked. If it does, we need to do a full sync. - if (e.code == 412) { - return this.UPLOAD_RESULT_OBJECT_CONFLICT; + if (key != batch[index].key) { + throw new Error("Key mismatch (" + key + " != " + batch[index].key + ")"); } - if (this.onError) { - this.onError(e); + let obj = yield objectsClass.getByLibraryAndKeyAsync( + this.libraryID, key, { noCache: true } + ) + ids.push(obj.id); + + if (state == 'successful') { + // Update local object with saved data if necessary + yield obj.loadAllData(); + obj.fromJSON(current.data); + toSave.push(obj); + toCache.push(current); } - if (this.stopOnError) { - throw new Error(e); + else { + // This won't reflect the actual version of the item on the server, but + // it will guarantee that the item won't be redownloaded unnecessarily + // in the case of a full sync, because the version will be higher than + // whatever version is on the server. + batch[index].version = libraryVersion + toCache.push(batch[index]); } - batch[index].tries++; - // Mark 400 errors as permanently failed - if (e.code >= 400 && e.code < 500) { - batch[index].failed = true; - } - // 500 errors should stay in queue and be retried + + numSuccessful++; + // Remove from batch to mark as successful + delete batch[index]; } - - // Add failed objects back to end of queue - var numFailed = 0; - for (let o of batch) { - if (o !== undefined) { - queue.push(o); - // TODO: Clear JSON? - numFailed++; - } - } - Zotero.debug("Failed: " + numFailed, 2); } - catch (e) { - if (e instanceof Zotero.HTTP.UnexpectedStatusException) { - if (e.status == 412) { - throw e; - } - - // On 5xx, delay and retry - if (e.status >= 500 && e.status <= 600) { - if (!failureDelayGenerator) { - // Keep trying for up to an hour - failureDelayGenerator = Zotero.Utilities.Internal.delayGenerator( - Zotero.Sync.Data.failureDelayIntervals, 60 * 60 * 1000 - ); - } - let keepGoing = yield failureDelayGenerator.next(); - if (!keepGoing) { - Zotero.logError("Failed too many times"); - throw e; - } - continue; - } + yield Zotero.Sync.Data.Local.saveCacheObjects( + objectType, this.libraryID, toCache + ); + yield Zotero.DB.executeTransaction(function* () { + for (let i = 0; i < toSave.length; i++) { + yield toSave[i].save(); } - throw e; + this.library.libraryVersion = libraryVersion; + yield this.library.save(); + objectsClass.updateVersion(ids, libraryVersion); + objectsClass.updateSynced(ids, true); + }.bind(this)); + + // Handle failed objects + for (let index in results.failed) { + let { code, message, data } = results.failed[index]; + let e = new Error(message); + e.name = "ZoteroObjectUploadError"; + e.code = code; + if (data) { + e.data = data; + } + Zotero.logError("Error for " + objectType + " " + batch[index].key + " in " + + this.library.name + ":\n\n" + e); + + // This shouldn't happen, because the upload request includes a library + // version and should prevent an outdated upload before the object version is + // checked. If it does, we need to do a full sync. + if (e.code == 412) { + return this.UPLOAD_RESULT_OBJECT_CONFLICT; + } + + if (this.onError) { + this.onError(e); + } + if (this.stopOnError) { + throw new Error(e); + } + batch[index].tries++; + // Mark 400 errors as permanently failed + if (e.code >= 400 && e.code < 500) { + batch[index].failed = true; + } + // 500 errors should stay in queue and be retried } + + // Add failed objects back to end of queue + var numFailed = 0; + for (let o of batch) { + if (o !== undefined) { + queue.push(o); + // TODO: Clear JSON? + numFailed++; + } + } + Zotero.debug("Failed: " + numFailed, 2); + // If we didn't make any progress, bail if (!numSuccessful) { throw new Error("Made no progress during upload -- stopping");