From 01ea59491a3c07c558f454227e433df7b753d9b1 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 11 Oct 2011 16:18:17 +0000 Subject: [PATCH] - Fix error handling in syncing when using pumpGenerator() - Take an optional error handler as the third parameter to pumpGenerator() --- chrome/content/zotero/xpcom/sync.js | 411 +++++++++++++------------- chrome/content/zotero/xpcom/zotero.js | 13 +- 2 files changed, 218 insertions(+), 206 deletions(-) diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js index aed5821741..6546f4b498 100644 --- a/chrome/content/zotero/xpcom/sync.js +++ b/chrome/content/zotero/xpcom/sync.js @@ -1460,209 +1460,7 @@ Zotero.Sync.Server = new function () { ); } - try { - var gen = Zotero.Sync.Server.Data.processUpdatedXML( - xml.updated, lastLocalSyncDate, syncSession, libraryID, function (xmlstr) { - try { - Zotero.UnresponsiveScriptIndicator.enable(); - - if (progressMeter) { - Zotero.hideZoteroPaneOverlay(); - } - Zotero.suppressUIUpdates = false; - _updatesInProgress = false; - - //Zotero.debug(xmlstr); - //throw('break'); - - 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 (!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'); - - //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 { - req.sendAsBinary(data); - } - 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); - } - } - catch (e) { - _error(e); - } - } - ); - - Zotero.pumpGenerator(gen); - } - catch (e) { + var errorHandler = function (e) { Zotero.DB.rollbackTransaction(); Zotero.UnresponsiveScriptIndicator.enable(); @@ -1673,7 +1471,212 @@ Zotero.Sync.Server = new function () { Zotero.suppressUIUpdates = false; _updatesInProgress = false; - throw (e); + _error(e); + } + + try { + var gen = Zotero.Sync.Server.Data.processUpdatedXML( + xml.updated, + lastLocalSyncDate, + syncSession, + libraryID, + function (xmlstr) { + Zotero.UnresponsiveScriptIndicator.enable(); + + if (progressMeter) { + Zotero.hideZoteroPaneOverlay(); + } + Zotero.suppressUIUpdates = false; + _updatesInProgress = false; + + //Zotero.debug(xmlstr); + //throw('break'); + + 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 (!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'); + + //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 { + req.sendAsBinary(data); + } + 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); + } + } + ); + + Zotero.pumpGenerator(gen, false, errorHandler); + } + catch (e) { + errorHandler(e); } } catch (e) { diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index e9ae5a47eb..5caf8d7c3a 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1498,8 +1498,11 @@ const ZOTERO_CONFIG = { /** * Pumps a generator until it yields false. See itemTreeView.js for an example. + * + * If errorHandler is specified, exceptions in the generator will be caught + * and passed to the callback */ - this.pumpGenerator = function(generator, ms) { + this.pumpGenerator = function(generator, ms, errorHandler) { _waiting++; var timer = Components.classes["@mozilla.org/timer;1"]. @@ -1528,7 +1531,13 @@ const ZOTERO_CONFIG = { _waitTimers = []; _waitTimerCallbacks = []; - if(err) throw err; + if(err) { + if(errorHandler) { + errorHandler(err); + } else { + throw err; + } + } }} timer.initWithCallback(timerCallback, ms ? ms : 0, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); // add timer to global scope so that it doesn't get garbage collected before it completes