commit d26bb248490b473d536ddddadc8176da010ce8f3
parent f1f4044018c40235c67fe90134ccbcfdd99461f8
Author: Simon Kornblith <simon@simonster.com>
Date: Sun, 10 Jun 2012 21:56:15 -0400
Merge branch 'attachment-progress'
Diffstat:
11 files changed, 582 insertions(+), 265 deletions(-)
diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js
@@ -50,8 +50,6 @@ var Zotero_Browser = new function() {
this.tabClose = tabClose;
this.resize = resize;
this.updateStatus = updateStatus;
- this.finishScraping = finishScraping;
- this.itemDone = itemDone;
this.tabbrowser = null;
this.appcontent = null;
@@ -447,51 +445,6 @@ var Zotero_Browser = new function() {
}
}
- /*
- * Callback to be executed when scraping is complete
- */
- function finishScraping(obj, returnValue) {
- if(!returnValue) {
- Zotero_Browser.progress.show();
- Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
- // Include link to Known Translator Issues page
- var url = "http://www.zotero.org/documentation/known_translator_issues";
- var linkText = '<a href="' + url + '" tooltiptext="' + url + '">'
- + Zotero.getString('ingester.scrapeErrorDescription.linkText') + '</a>';
- Zotero_Browser.progress.addDescription(Zotero.getString("ingester.scrapeErrorDescription", linkText));
- Zotero_Browser.progress.startCloseTimer(8000);
- } else {
- Zotero_Browser.progress.startCloseTimer();
- }
- Zotero_Browser.isScraping = false;
- }
-
-
- /*
- * Callback to be executed when an item has been finished
- */
- function itemDone(obj, dbItem, item, collection) {
- var title = item.title;
- var icon = Zotero.ItemTypes.getImageSrc(item.itemType);
- Zotero_Browser.progress.show();
- Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scraping"));
- Zotero_Browser.progress.addLines([title], [icon]);
-
- // add item to collection, if one was specified
- if(collection) {
- collection.addItem(dbItem.id);
- }
-
- if(Zotero_Browser.isScraping) {
- // initialize close timer between item saves in case translator doesn't call done
- Zotero_Browser.progress.startCloseTimer(10000); // is this long enough?
- } else {
- // if we aren't supposed to be scraping now, the translator is broken; assume we're
- // done
- Zotero_Browser.progress.startCloseTimer();
- }
- }
-
/**
* Called when status bar icon is right-clicked
*/
@@ -725,6 +678,28 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID, trans
var collection = false;
}
+ if(Zotero.isConnector) {
+ Zotero.Connector.callMethod("getSelectedCollection", {}, function(response, status) {
+ if(status !== 200) return;
+ Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapingTo"),
+ "chrome://zotero/skin/treesource-"+(response.id ? "collection" : "library")+".png",
+ response.name+"\u2026");
+ });
+ } else {
+ var name;
+ if(collection) {
+ name = collection.name;
+ } else if(libraryID) {
+ name = Zotero.Libraries.getName(libraryID);
+ } else {
+ name = Zotero.getString("pane.collections.library");
+ }
+
+ Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapingTo"),
+ "chrome://zotero/skin/treesource-"+(collection ? "collection" : "library")+".png",
+ name+"\u2026");
+ }
+
var me = this;
// use first translator available
@@ -733,8 +708,54 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID, trans
this.page.translate.clearHandlers("done");
this.page.translate.clearHandlers("itemDone");
- this.page.translate.setHandler("done", function(obj, item) { Zotero_Browser.finishScraping(obj, item) });
- this.page.translate.setHandler("itemDone", function(obj, dbItem, item) { Zotero_Browser.itemDone(obj, dbItem, item, collection) });
+ this.page.translate.setHandler("done", function(obj, returnValue) {
+ if(!returnValue) {
+ Zotero_Browser.progress.show();
+ Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapeError"));
+ // Include link to Known Translator Issues page
+ var url = "http://www.zotero.org/documentation/known_translator_issues";
+ var linkText = '<a href="' + url + '" tooltiptext="' + url + '">'
+ + Zotero.getString('ingester.scrapeErrorDescription.linkText') + '</a>';
+ Zotero_Browser.progress.addDescription(Zotero.getString("ingester.scrapeErrorDescription", linkText));
+ Zotero_Browser.progress.startCloseTimer(8000);
+ } else {
+ Zotero_Browser.progress.startCloseTimer();
+ }
+ Zotero_Browser.isScraping = false;
+ });
+
+ var attachmentsMap = new WeakMap();
+
+ this.page.translate.setHandler("itemDone", function(obj, dbItem, item) {
+ Zotero_Browser.progress.show();
+ var itemProgress = new Zotero_Browser.progress.ItemProgress(Zotero.ItemTypes.getImageSrc(item.itemType),
+ item.title);
+ itemProgress.setProgress(100);
+ for(var i=0; i<item.attachments.length; i++) {
+ var attachment = item.attachments[i];
+ attachmentsMap.set(attachment,
+ new Zotero_Browser.progress.ItemProgress(
+ Zotero.Utilities.determineAttachmentIcon(attachment),
+ attachment.title, itemProgress));
+ }
+
+ // add item to collection, if one was specified
+ if(collection) {
+ collection.addItem(dbItem.id);
+ }
+ });
+
+ this.page.translate.setHandler("attachmentProgress", function(obj, attachment, progress, error) {
+ var itemProgress = attachmentsMap.get(attachment);
+ if(progress === false) {
+ itemProgress.setError();
+ } else {
+ itemProgress.setProgress(progress);
+ if(progress === 100) {
+ itemProgress.setIcon(Zotero.Utilities.determineAttachmentIcon(attachment));
+ }
+ }
+ });
this.page.translate.translate(libraryID);
}
diff --git a/chrome/content/zotero/progressWindow.xul b/chrome/content/zotero/progressWindow.xul
@@ -8,8 +8,8 @@
windowtype="alert:alert">
<hbox id="zotero-progress-box">
- <vbox id="zotero-progress-text-box">
- <label id="zotero-progress-text-headline"/>
+ <vbox id="zotero-progress-text-box" flex="1">
+ <hbox id="zotero-progress-text-headline" pack="start"/>
</vbox>
</hbox>
</window>
diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js
@@ -220,6 +220,7 @@ Zotero.Attachments = new function(){
var urlRe = /^https?:\/\/[^\s]*$/;
var matches = urlRe.exec(url);
if (!matches) {
+ if(callback) callback(false);
throw ("Invalid URL '" + url + "' in Zotero.Attachments.importFromURL()");
}
@@ -297,9 +298,11 @@ Zotero.Attachments = new function(){
if (mimeType == 'application/pdf' &&
Zotero.MIME.sniffForMIMEType(str) != 'application/pdf') {
- Zotero.debug("Downloaded PDF did not have MIME type "
- + "'application/pdf' in Attachments.importFromURL()", 2);
+ var errString = "Downloaded PDF did not have MIME type "
+ + "'application/pdf' in Attachments.importFromURL()";
+ Zotero.debug(errString, 2);
attachmentItem.erase();
+ if(callback) callback(false, new Error(errString));
return;
}
@@ -311,6 +314,8 @@ Zotero.Attachments = new function(){
Zotero.Notifier.trigger('add', 'item', itemID);
Zotero.Notifier.trigger('modify', 'item', sourceItemID);
+
+ if(callback) callback(attachmentItem);
// We don't have any way of knowing that the file
// is flushed to disk, so we just wait a second
@@ -325,6 +330,7 @@ Zotero.Attachments = new function(){
catch (e) {
// Clean up
attachmentItem.erase();
+ if(callback) callback(false, e);
throw (e);
}
@@ -346,8 +352,6 @@ Zotero.Attachments = new function(){
nsIURL.spec = url;
wbp.saveURI(nsIURL, null, null, null, null, file);
- if(callback) callback(attachmentItem);
-
return attachmentItem;
}
catch (e){
@@ -553,7 +557,7 @@ Zotero.Attachments = new function(){
Zotero.Fulltext.indexDocument(document, itemID);
Zotero.Notifier.trigger('refresh', 'item', itemID);
if (callback) {
- callback();
+ callback(attachmentItem);
}
};
}
@@ -612,6 +616,7 @@ Zotero.Attachments = new function(){
// Clean up
var item = Zotero.Items.get(itemID);
item.erase();
+ if(callback) callback(false, e);
throw (e);
}
diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -115,7 +115,7 @@ Zotero.Translate.ItemSaver.prototype = {
if(typedArraysSupported) {
// Get rid of attachments that we won't be able to save properly and add ids
for(var j=0; j<item.attachments.length; j++) {
- if(!item.attachments[j].url || item.attachments[j].mimeType === "text/html") {
+ if(item.attachments[j].url && item.attachments[j].mimeType !== "text/html") {
item.attachments.splice(j--, 1);
} else {
item.attachments[j].id = Zotero.Utilities.randomString();
diff --git a/chrome/content/zotero/xpcom/progressWindow.js b/chrome/content/zotero/xpcom/progressWindow.js
@@ -107,30 +107,22 @@ Zotero.ProgressWindowSet = new function() {
* Pass the active window into the constructor
*/
Zotero.ProgressWindow = function(_window){
- this.show = show;
- this.changeHeadline = changeHeadline;
- this.addLines = addLines;
- this.addDescription = addDescription;
- this.startCloseTimer = startCloseTimer;
- this.close = close;
+ var self = this,
+ _window = null,
+ _progressWindow = null,
+ _windowLoaded = false,
+ _windowLoading = false,
+ _timeoutID = false,
+ _closing = false,
+ _mouseWasOver = false,
+ _deferredUntilWindowLoad = [],
+ _deferredUntilWindowLoadThis = [],
+ _deferredUntilWindowLoadArgs = [];
- var _window = null;
-
- var _progressWindow = null;
- var _windowLoaded = false;
- var _windowLoading = false;
- var _timeoutID = false;
- var _mouseWasOver = false
-
- // keep track of all of these things in case they're called before we're
- // done loading the progress window
- var _loadHeadline = '';
- var _loadLines = [];
- var _loadIcons = [];
- var _loadDescription = null;
-
-
- function show() {
+ /**
+ * Shows the progress window
+ */
+ this.show = function show() {
if(_windowLoading || _windowLoaded) { // already loading or loaded
return false;
}
@@ -164,89 +156,90 @@ Zotero.ProgressWindow = function(_window){
return true;
}
- function changeHeadline(headline) {
- if(_windowLoaded) {
- _progressWindow.document.getElementById("zotero-progress-text-headline").value = headline;
- } else {
- _loadHeadline = headline;
+ /**
+ * Changes the "headline" shown at the top of the progress window
+ */
+ this.changeHeadline = _deferUntilWindowLoad(function changeHeadline(text, icon, postText) {
+ var doc = _progressWindow.document,
+ headline = doc.getElementById("zotero-progress-text-headline");
+ while(headline.hasChildNodes()) headline.removeChild(headline.firstChild);
+
+ var preNode = doc.createElement("label");
+ preNode.setAttribute("value", text);
+ preNode.setAttribute("crop", "end");
+ headline.appendChild(preNode);
+
+ if(icon) {
+ var img = doc.createElement("image");
+ img.width = 16;
+ img.height = 16;
+ img.setAttribute("src", icon);
+ headline.appendChild(img);
}
- }
-
- function addLines(labels, icons) {
- if(_windowLoaded) {
- for (var i in labels) {
- var newText = _progressWindow.document.createElement("description");
- newText.appendChild(
- _progressWindow.document.createTextNode(labels[i])
- );
- newText.setAttribute("class", "zotero-progress-item-label");
- newText.setAttribute("crop", "end");
-
- var newImageHolder = _progressWindow.document.createElement("vbox");
- var newImage = _progressWindow.document.createElement("image");
- newImage.setAttribute("class", "zotero-progress-item-icon");
- newImage.setAttribute("src", icons[i]);
- newImage.setAttribute("flex", 0);
- newImage.setAttribute("orient", "horizontal");
- newImage.setAttribute("pack", "start");
- newImageHolder.appendChild(newImage);
-
- var newHB = _progressWindow.document.createElement("hbox");
- newHB.setAttribute("class", "zotero-progress-item-hbox");
-
- newHB.appendChild(newImageHolder);
- newHB.appendChild(newText);
-
- _progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
- }
-
- _move();
- } else {
- _loadLines = _loadLines.concat(labels);
- _loadIcons = _loadIcons.concat(icons);
+
+ if(postText) {
+ var postNode = doc.createElement("label");
+ postNode.style.marginLeft = 0;
+ postNode.setAttribute("value", " "+postText);
+ postNode.setAttribute("crop", "end");
+ postNode.setAttribute("flex", "1");
+ headline.appendChild(postNode);
}
- }
+ });
+ /**
+ * Adds a line to the progress window with the specified icon
+ */
+ this.addLines = _deferUntilWindowLoad(function addLines(labels, icons) {
+ for (var i in labels) {
+ new this.ItemProgress(icons[i], labels[i]);
+ }
+
+ _move();
+ });
- /*
+ /**
* Add a description to the progress window
*
* <a> elements are turned into XUL links
*/
- function addDescription(text) {
- if(_windowLoaded) {
- var newHB = _progressWindow.document.createElement("hbox");
- newHB.setAttribute("class", "zotero-progress-item-hbox");
- var newDescription = _progressWindow.document.createElement("description");
-
- var parts = Zotero.Utilities.parseMarkup(text);
- for each(var part in parts) {
- if (part.type == 'text') {
- var elem = _progressWindow.document.createTextNode(part.text);
- }
- else if (part.type == 'link') {
- var elem = _progressWindow.document.createElement('label');
- elem.setAttribute('value', part.text);
- elem.setAttribute('class', 'zotero-text-link');
- for (var i in part.attributes) {
- elem.setAttribute(i, part.attributes[i]);
- }
+ this.addDescription = _deferUntilWindowLoad(function addDescription(text) {
+ var newHB = _progressWindow.document.createElement("hbox");
+ newHB.setAttribute("class", "zotero-progress-item-hbox");
+ var newDescription = _progressWindow.document.createElement("description");
+
+ var parts = Zotero.Utilities.parseMarkup(text);
+ for each(var part in parts) {
+ if (part.type == 'text') {
+ var elem = _progressWindow.document.createTextNode(part.text);
+ }
+ else if (part.type == 'link') {
+ var elem = _progressWindow.document.createElement('label');
+ elem.setAttribute('value', part.text);
+ elem.setAttribute('class', 'zotero-text-link');
+ for (var i in part.attributes) {
+ elem.setAttribute(i, part.attributes[i]);
}
-
- newDescription.appendChild(elem);
}
- newHB.appendChild(newDescription);
- _progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
-
- _move();
- } else {
- _loadDescription = text;
+ newDescription.appendChild(elem);
}
- }
-
+
+ newHB.appendChild(newDescription);
+ _progressWindow.document.getElementById("zotero-progress-text-box").appendChild(newHB);
+
+ _move();
+ });
- function startCloseTimer(ms, requireMouseOver) {
+ /**
+ * Sets a timer to close the progress window. If a previous close timer was set,
+ * clears it.
+ * @param {Integer} ms The number of milliseconds to wait before closing the progress
+ * window.
+ * @param {Boolean} [requireMouseOver] If true, wait until the mouse has touched the
+ * window before closing.
+ */
+ this.startCloseTimer = function startCloseTimer(ms, requireMouseOver) {
if (_windowLoaded || _windowLoading) {
if (requireMouseOver && !_mouseWasOver) {
return;
@@ -261,10 +254,14 @@ Zotero.ProgressWindow = function(_window){
}
_timeoutID = _progressWindow.setTimeout(_timeout, ms);
+ _closing = true;
}
}
- function close() {
+ /**
+ * Immediately closes the progress window if it is open.
+ */
+ this.close = function close() {
_disableTimeout();
_windowLoaded = false;
_windowLoading = false;
@@ -275,23 +272,104 @@ Zotero.ProgressWindow = function(_window){
} catch(ex) {}
}
+ /**
+ * Creates a new object representing a line in the progressWindow. This is the OO
+ * version of addLines() above.
+ */
+ this.ItemProgress = _deferUntilWindowLoad(function(iconSrc, title, parentItemProgress) {
+ this._itemText = _progressWindow.document.createElement("description");
+ this._itemText.appendChild(_progressWindow.document.createTextNode(title));
+ this._itemText.setAttribute("class", "zotero-progress-item-label");
+ this._itemText.setAttribute("crop", "end");
+
+ this._image = _progressWindow.document.createElement("hbox");
+ this._image.setAttribute("class", "zotero-progress-item-icon");
+ this._image.setAttribute("flex", 0);
+ this._image.style.width = "16px";
+ this._image.style.backgroundRepeat = "no-repeat";
+ this.setIcon(iconSrc);
+
+ this._hbox = _progressWindow.document.createElement("hbox");
+ this._hbox.setAttribute("class", "zotero-progress-item-hbox");
+ if(parentItemProgress) {
+ this._hbox.style.marginLeft = "16px";
+ this._hbox.zoteroIsChildItem;
+ } else {
+ this._hbox.setAttribute("parent", "true");
+ }
+ this._hbox.style.opacity = "0.5";
+
+ this._hbox.appendChild(this._image);
+ this._hbox.appendChild(this._itemText);
+
+ var container = _progressWindow.document.getElementById("zotero-progress-text-box");
+ if(parentItemProgress) {
+ var nextItem = parentItemProgress._hbox.nextSibling;
+ while(nextItem && nextItem.zoteroIsChildItem) {
+ nextItem = nextItem.nextSibling;
+ }
+ container.insertBefore(this._hbox, nextItem);
+ } else {
+ container.appendChild(this._hbox);
+ }
+
+ _move();
+ });
+
+ /**
+ * Sets the current save progress for this item.
+ * @param {Integer} percent A percentage from 0 to 100.
+ */
+ this.ItemProgress.prototype.setProgress = _deferUntilWindowLoad(function(percent) {
+ if(percent != 0 && percent != 100) {
+ // Indication of partial progress, so we will use the circular indicator
+ this._image.style.backgroundImage = "url('chrome://zotero/skin/progress_arcs.png')";
+ this._image.style.backgroundPosition = "-"+(Math.round(percent/100*nArcs)*16)+"px 0";
+ this._hbox.style.opacity = percent/200+.5;
+ this._hbox.style.filter = "alpha(opacity = "+(percent/2+50)+")";
+ } else if(percent == 100) {
+ this._image.style.backgroundImage = "url('"+this._iconSrc+"')";
+ this._image.style.backgroundPosition = "";
+ this._hbox.style.opacity = "1";
+ this._hbox.style.filter = "";
+ }
+ });
+
+ /**
+ * Sets the icon for this item.
+ * @param {Integer} percent A percentage from 0 to 100.
+ */
+ this.ItemProgress.prototype.setIcon = _deferUntilWindowLoad(function(iconSrc) {
+ this._image.style.backgroundImage = "url('"+iconSrc+"')";
+ this._image.style.backgroundPosition = "";
+ this._iconSrc = iconSrc;
+ });
+
+ /**
+ * Indicates that an error occurred saving this item.
+ */
+ this.ItemProgress.prototype.setError = _deferUntilWindowLoad(function() {
+ this._image.style.backgroundImage = "url('chrome://zotero/skin/cross.png')";
+ this._image.style.backgroundPosition = "";
+ this._itemText.style.color = "red";
+ this._hbox.style.opacity = "1";
+ this._hbox.style.filter = "";
+ });
+
function _onWindowLoaded() {
_windowLoading = false;
_windowLoaded = true;
_move();
+
// do things we delayed because the window was loading
- changeHeadline(_loadHeadline);
- addLines(_loadLines, _loadIcons);
- if (_loadDescription) {
- addDescription(_loadDescription);
+ for(var i=0; i<_deferredUntilWindowLoad.length; i++) {
+ _deferredUntilWindowLoad[i].apply(_deferredUntilWindowLoadThis[i],
+ _deferredUntilWindowLoadArgs[i]);
}
-
- // reset parameters
- _loadHeadline = '';
- _loadLines = [];
- _loadIcons = [];
- _loadDescription = null;
+ _deferredUntilWindowLoad = [];
+ _deferredUntilWindowLoadThis = [];
+ _deferredUntilWindowLoadArgs = [];
}
function _move() {
@@ -303,8 +381,8 @@ Zotero.ProgressWindow = function(_window){
}
function _timeout() {
- close(); // could check to see if we're really supposed to close yet
- // (in case multiple scrapers are operating at once)
+ self.close(); // could check to see if we're really supposed to close yet
+ // (in case multiple scrapers are operating at once)
_timeoutID = false;
}
@@ -319,7 +397,6 @@ Zotero.ProgressWindow = function(_window){
_timeoutID = false;
}
-
/*
* Disable the close timer when the mouse is over the window
*/
@@ -328,8 +405,7 @@ Zotero.ProgressWindow = function(_window){
_disableTimeout();
}
-
- /*
+ /**
* Start the close timer when the mouse leaves the window
*
* Note that this onmouseout doesn't work correctly on popups in Fx2,
@@ -345,11 +421,27 @@ Zotero.ProgressWindow = function(_window){
&& (e.screenY >= top) && e.screenY <= (top + this.outerHeight)) {
return;
}
- startCloseTimer();
+ if(_closing) self.startCloseTimer();
}
-
function _onMouseUp(e) {
- close();
+ self.close();
+ }
+
+ /**
+ * Wraps a function to ensure it isn't called until the window is loaded
+ */
+ function _deferUntilWindowLoad(fn) {
+ return function() {
+ if(_window.closed) return;
+
+ if(_windowLoaded) {
+ fn.apply(this, Array.prototype.slice.call(arguments));
+ } else {
+ _deferredUntilWindowLoad.push(fn);
+ _deferredUntilWindowLoadThis.push(this);
+ _deferredUntilWindowLoadArgs.push(Array.prototype.slice.call(arguments));
+ }
+ }
}
}
diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js
@@ -27,6 +27,35 @@ const CONNECTOR_API_VERSION = 2;
Zotero.Server.Connector = function() {};
Zotero.Server.Connector._waitingForSelection = {};
Zotero.Server.Connector.Data = {};
+Zotero.Server.Connector.AttachmentProgressManager = new function() {
+ var attachmentsInProgress = new WeakMap(),
+ attachmentProgress = {},
+ i = 1;
+
+ /**
+ * Adds attachments to attachment progress manager
+ */
+ this.add = function(attachments) {
+ for(var i=0; i<attachments.length; i++) {
+ var attachment = attachments[i];
+ attachmentsInProgress.set(attachment, (attachment.id = i++));
+ }
+ }
+
+ /**
+ * Called on attachment progress
+ */
+ this.onProgress = function(attachment, progress, error) {
+ attachmentProgress[attachmentsInProgress.get(attachment)] = progress;
+ };
+
+ /**
+ * Gets progress for a given progressID
+ */
+ this.getProgressForID = function(progressID) {
+ return progressID in attachmentProgress ? attachmentProgress[progressID] : 0;
+ };
+};
/**
* Lists all available translators, including code for translators that should be run on every page
@@ -169,7 +198,7 @@ Zotero.Server.Connector.Detect.prototype = {
* cookie - document.cookie or equivalent
*
* Returns:
- * If a single item, sends response code 201 with no body.
+ * If a single item, sends response code 201 with item in body.
* If multiple items, sends response code 300 with the following content:
* items - list of items in the format typically passed to the selectItems handler
* instanceID - an ID that must be maintained for the subsequent Zotero.Connector.Select call
@@ -246,9 +275,14 @@ Zotero.Server.Connector.SavePage.prototype = {
if(collection) {
collection.addItem(item.id);
}
+
jsonItems.push(jsonItem);
});
- translate.setHandler("done", function(obj, item) {
+ translate.setHandler("attachmentProgress", function(obj, attachment, progress, error) {
+ Zotero.Server.Connector.AttachmentProgressManager.onProgress(attachment, progress, error);
+ });
+ translate.setHandler("itemsDone", function(obj, item) {
+ Zotero.Server.Connector.AttachmentProgressManager.add(item.attachments);
Zotero.Browser.deleteHiddenBrowser(me._browser);
if(jsonItems.length || me.selectedItems === false) {
me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
@@ -269,7 +303,7 @@ Zotero.Server.Connector.SavePage.prototype = {
* Accepts:
* items - an array of JSON format items
* Returns:
- * 201 response code with empty body
+ * 201 response code with item in body.
*/
Zotero.Server.Connector.SaveItem = function() {};
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
@@ -299,22 +333,23 @@ Zotero.Server.Connector.SaveItem.prototype = {
// save items
var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1, undefined, cookieSandbox);
- itemSaver.saveItems(data.items, function(returnValue, data) {
+ itemSaver.saveItems(data.items, function(returnValue, newItems) {
if(returnValue) {
try {
- for each(var item in data) {
+ for each(var item in newItems) {
if(collection) collection.addItem(item.id);
}
- sendResponseCallback(201);
+
+ sendResponseCallback(201, "application/json", JSON.stringify({"items":data.items}));
} catch(e) {
Zotero.logError(e);
sendResponseCallback(500);
}
} else {
sendResponseCallback(500);
- throw data;
+ throw newItems;
}
- });
+ }, Zotero.Server.Connector.AttachmentProgressManager.onProgress);
}
}
@@ -435,6 +470,31 @@ Zotero.Server.Connector.SelectItems.prototype = {
}
/**
+ * Gets progress for an attachment that is currently being saved
+ *
+ * Accepts:
+ * Array of attachment IDs returned by savePage, saveItems, or saveSnapshot
+ * Returns:
+ * 200 response code with current progress in body. Progress is either a number
+ * between 0 and 100 or "false" to indicate that saving failed.
+ */
+Zotero.Server.Connector.Progress = function() {};
+Zotero.Server.Endpoints["/connector/attachmentProgress"] = Zotero.Server.Connector.Progress;
+Zotero.Server.Connector.Progress.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * @param {String} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ sendResponseCallback(200, "application/json",
+ JSON.stringify([Zotero.Server.Connector.AttachmentProgressManager.getProgressForID(id) for each(id in data)]));
+ }
+};
+
+/**
* Get code for a translator
*
* Accepts:
@@ -460,6 +520,64 @@ Zotero.Server.Connector.GetTranslatorCode.prototype = {
}
/**
+ * Get selected collection
+ *
+ * Accepts:
+ * Nothing
+ * Returns:
+ * libraryID
+ * libraryName
+ * collectionID
+ * collectionName
+ */
+Zotero.Server.Connector.GetSelectedCollection = function() {};
+Zotero.Server.Endpoints["/connector/getSelectedCollection"] = Zotero.Server.Connector.GetSelectedCollection;
+Zotero.Server.Connector.GetSelectedCollection.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Returns a 200 response to say the server is alive
+ * @param {String} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(postData, sendResponseCallback) {
+ var zp = Zotero.getActiveZoteroPane(),
+ libraryID = null,
+ collection = null,
+ editable = true;
+
+ try {
+ libraryID = zp.getSelectedLibraryID();
+ collection = zp.getSelectedCollection();
+ editable = zp.collectionsView.editable;
+ } catch(e) {}
+
+ var response = {
+ "editable":editable,
+ "libraryID":libraryID
+ };
+
+ if(libraryID) {
+ response.libraryName = Zotero.Libraries.getName(libraryID);
+ } else {
+ response.libraryName = Zotero.getString("pane.collections.library");
+ }
+
+ if(collection && collection.id) {
+ response.id = collection.id;
+ response.name = collection.name;
+ } else {
+ response.id = null;
+ response.name = response.libraryName;
+ }
+
+ sendResponseCallback(200, "application/json", JSON.stringify(response));
+ }
+}
+
+
+/**
* Test connection
*
* Accepts:
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
@@ -130,28 +130,16 @@ Zotero.Translate.Sandbox = {
}
}
}
+
+ // Fire itemSaving event
+ translate._runHandler("itemSaving", item);
if(translate instanceof Zotero.Translate.Web) {
// For web translators, we queue saves
translate.saveQueue.push(item);
- translate._runHandler("itemSaving", item);
} else {
- var newItem;
- translate._itemSaver.saveItems([item], function(returnValue, data) {
- if(returnValue) {
- newItem = data[0];
- translate.newItems.push(newItem);
- } else {
- translate.complete(false, data);
- throw data;
- }
- }, function(arg1, arg2, arg3) {
- translate._attachmentProgress(arg1, arg2, arg3);
- });
-
- translate._runHandler("itemSaving", item);
- // pass both the saved item and the original JS array item
- translate._runHandler("itemDone", newItem, item);
+ // Save items
+ translate._saveItems([item]);
}
},
@@ -1014,7 +1002,8 @@ Zotero.Translate.Base.prototype = {
this._libraryID = libraryID;
this._saveAttachments = saveAttachments === undefined || saveAttachments;
- this._attachmentsSaving = [];
+ this._savingAttachments = [];
+ this._savingItems = 0;
var me = this;
if(typeof this.translator[0] === "object") {
@@ -1143,14 +1132,11 @@ Zotero.Translate.Base.prototype = {
if(returnValue) {
if(this.saveQueue.length) {
- var me = this;
- this._itemSaver.saveItems(this.saveQueue.slice(),
- function(returnValue, data) { me._itemsSaved(returnValue, data); },
- function(arg1, arg2, arg3) { me._attachmentProgress(arg1, arg2, arg3); });
+ this._saveItems(this.saveQueue);
+ this.saveQueue = [];
return;
- } else {
- this._debug("Translation successful");
}
+ this._debug("Translation successful");
} else {
if(error) {
// report error to console
@@ -1176,61 +1162,79 @@ Zotero.Translate.Base.prototype = {
},
/**
- * Callback executed when items have been saved (which may happen asynchronously, if in
- * connector)
- *
- * @param {Boolean} returnValue Whether saving was successful
- * @param {Zotero.Item[]|Error} data If returnValue is true, this will be an array of
- * Zotero.Item objects. If returnValue is false, this will
- * be a string error message.
+ * Saves items to the database, taking care to defer attachmentProgress notifications
+ * until after save
*/
- "_itemsSaved":function(returnValue, data) {
- if(returnValue) {
- // trigger deferred itemDone events
- var nItems = data.length;
- for(var i=0; i<nItems; i++) {
- this._runHandler("itemDone", data[i], this.saveQueue[i]);
+ "_saveItems":function(items) {
+ var me = this,
+ itemDoneEventsDispatched = false,
+ deferredProgress = [],
+ attachmentsWithProgress = [];
+
+ this._savingItems++;
+ this._itemSaver.saveItems(items.slice(), function(returnValue, newItems) {
+ if(returnValue) {
+ // Remove attachments not being saved from item.attachments
+ for(var i=0; i<items.length; i++) {
+ var item = items[i];
+ for(var j=0; j<item.attachments.length; j++) {
+ if(attachmentsWithProgress.indexOf(item.attachments[j]) === -1) {
+ item.attachments.splice(j--, 1);
+ }
+ }
+ }
+
+ // Trigger itemDone events
+ for(var i=0, nItems = items.length; i<nItems; i++) {
+ me._runHandler("itemDone", newItems[i], items[i]);
+ }
+
+ // Specify that itemDone event was dispatched, so that we don't defer
+ // attachmentProgress notifications anymore
+ itemDoneEventsDispatched = true;
+
+ // Run deferred attachmentProgress notifications
+ for(var i=0; i<deferredProgress.length; i++) {
+ me._runHandler("attachmentProgress", deferredProgress[i][0],
+ deferredProgress[i][1], deferredProgress[i][2]);
+ }
+
+ me.newItems = me.newItems.concat(newItems);
+ me._savingItems--;
+ me._checkIfDone();
+ } else {
+ Zotero.logError(newItems);
+ me.complete(returnValue, newItems);
+ }
+ },
+ function(attachment, progress, error) {
+ var attachmentIndex = me._savingAttachments.indexOf(attachment);
+ if(progress === false || progress === 100) {
+ if(attachmentIndex !== -1) {
+ me._savingAttachments.splice(attachmentIndex, 1);
+ }
+ } else if(attachmentIndex === -1) {
+ me._savingAttachments.push(attachment);
}
- this.saveQueue = [];
- } else {
- Zotero.logError(data);
- }
-
- if(returnValue) {
- this._checkIfDone();
- } else {
- this._runHandler("done", returnValue);
- }
- },
-
- /**
- * Callback for attachment progress, passed as third argument to Zotero.ItemSaver#saveItems
- *
- * @param {Object} attachment Attachment object to be saved. Should remain the same between
- * repeated calls to callback.
- * @param {Boolean|Number} progress Percent complete, or false if an error occurred.
- * @param {Error} [error] Error, if an error occurred during saving.
- */
- "_attachmentProgress":function(attachment, progress, error) {
- Zotero.debug("Attachment progress (progress = "+progress+")");
- Zotero.debug(attachment);
- var attachmentIndex = this._attachmentsSaving.indexOf(attachment);
- if((progress === false || progress === 100) && attachmentIndex !== -1) {
- this._attachmentsSaving.splice(attachmentIndex, 1);
- } else if(attachmentIndex === -1) {
- this._attachmentsSaving.push(attachment);
- }
-
- this._runHandler("attachmentProgress", attachment, progress, error);
- this._checkIfDone();
+ if(itemDoneEventsDispatched) {
+ // itemDone event has already fired, so we can fire attachmentProgress
+ // notifications
+ me._runHandler("attachmentProgress", attachment, progress, error);
+ me._checkIfDone();
+ } else {
+ // Defer until after we fire the itemDone event
+ deferredProgress.push([attachment, progress, error]);
+ attachmentsWithProgress.push(attachment);
+ }
+ });
},
/**
* Checks if saving done, and if so, fires done event
*/
"_checkIfDone":function() {
- if(!this._attachmentsSaving.length) {
+ if(!this._savingItems && !this._savingAttachments.length && !this._currentState) {
this._runHandler("done", true);
}
},
diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js
@@ -87,7 +87,18 @@ Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
Zotero.Translate.ItemSaver.prototype = {
- "saveItems":function(items, callback) {
+ /**
+ * Saves items to Standalone or the server
+ * @param items Items in Zotero.Item.toArray() format
+ * @param {Function} callback A callback to be executed when saving is complete. If saving
+ * succeeded, this callback will be passed true as the first argument and a list of items
+ * saved as the second. If saving failed, the callback will be passed false as the first
+ * argument and an error object as the second
+ * @param {Function} [attachmentCallback] A callback that receives information about attachment
+ * save progress. The callback will be called as attachmentCallback(attachment, false, error)
+ * on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
+ */
+ "saveItems":function(items, callback, attachmentCallback) {
// if no open transaction, open a transaction and add a timer call to close it
var openedTransaction = false;
if(!Zotero.DB.transactionInProgress()) {
@@ -110,8 +121,8 @@ Zotero.Translate.ItemSaver.prototype = {
newItem = Zotero.Items.get(myID);
} else {
if(type == "attachment") { // handle attachments differently
- newItem = this._saveAttachment(item);
- if(!newItem) return;
+ newItem = this._saveAttachment(item, null, attachmentCallback);
+ if(!newItem) continue;
var myID = newItem.id;
} else {
var typeID = Zotero.ItemTypes.getID(type);
@@ -137,8 +148,10 @@ Zotero.Translate.ItemSaver.prototype = {
// handle attachments
if(item.attachments) {
for(var i=0; i<item.attachments.length; i++) {
- var newAttachment = this._saveAttachment(item.attachments[i], myID);
- if(newAttachment) this._saveTags(item.attachments[i], newAttachment);
+ var newAttachment = this._saveAttachment(item.attachments[i], myID, attachmentCallback);
+ if(typeof newAttachment === "object") {
+ this._saveTags(item.attachments[i], newAttachment);
+ }
}
}
}
@@ -203,13 +216,13 @@ Zotero.Translate.ItemSaver.prototype = {
return topLevelCollection;
},
- "_saveAttachmentFile":function(attachment, parentID) {
+ "_saveAttachmentFile":function(attachment, parentID, attachmentCallback) {
const urlRe = /(([A-Za-z]+):\/\/[^\s]*)/i;
Zotero.debug("Translate: Adding attachment", 4);
if(!attachment.url && !attachment.path) {
Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2);
- return;
+ return false;
}
if(!attachment.path) {
@@ -221,34 +234,40 @@ Zotero.Translate.ItemSaver.prototype = {
attachment.url = false;
} else if(protocol != "http" && protocol != "https") {
Zotero.debug("Translate: Unrecognized protocol "+protocol, 2);
- return;
+ return false;
}
}
if(!attachment.path) {
// create from URL
+ attachment.linkMode = "linked_file";
try {
var myID = Zotero.Attachments.linkFromURL(attachment.url, parentID,
(attachment.mimeType ? attachment.mimeType : undefined),
(attachment.title ? attachment.title : undefined));
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
- return;
+ attachmentCallback(attachment, false, e);
+ return false;
}
Zotero.debug("Translate: Created attachment; id is "+myID, 4);
+ attachmentCallback(attachment, 100);
var newItem = Zotero.Items.get(myID);
} else {
var file = this._parsePath(attachment.path);
if(!file || !file.exists()) return;
if (attachment.url) {
+ attachment.linkMode = "imported_url";
var myID = Zotero.Attachments.importSnapshotFromFile(file,
attachment.url, attachment.title, attachment.mimeType, attachment.charset,
parentID);
}
else {
+ attachment.linkMode = "imported_file";
var myID = Zotero.Attachments.importFromFile(file, parentID);
}
+ attachmentCallback(attachment, 100);
}
var newItem = Zotero.Items.get(myID);
@@ -306,7 +325,7 @@ Zotero.Translate.ItemSaver.prototype = {
return file;
},
- "_saveAttachmentDownload":function(attachment, parentID) {
+ "_saveAttachmentDownload":function(attachment, parentID, attachmentCallback) {
Zotero.debug("Translate: Adding attachment", 4);
// determine whether to save attachments at all
@@ -319,7 +338,7 @@ Zotero.Translate.ItemSaver.prototype = {
var shouldAttach = ((attachment.document
|| (attachment.mimeType && attachment.mimeType == "text/html")) && automaticSnapshots)
|| downloadAssociatedFiles;
- if(!shouldAttach) return;
+ if(!shouldAttach) return false;
if(attachment.document) {
attachment.document = Zotero.Translate.DOMWrapper.unwrap(attachment.document);
@@ -327,10 +346,18 @@ Zotero.Translate.ItemSaver.prototype = {
if(attachment.snapshot === false || !this._saveFiles) {
// if snapshot is explicitly set to false, attach as link
+ attachment.linkMode = "linked_url";
if(attachment.document) {
- Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
- (attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
- (attachment.title ? attachment.title : attachment.document.title));
+ try {
+ Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
+ (attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
+ (attachment.title ? attachment.title : attachment.document.title));
+ attachmentCallback(attachment, 100);
+ } catch(e) {
+ Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
+ attachmentCallback(attachment, false, e);
+ }
+ return true;
} else {
if(!attachment.mimeType || !attachment.title) {
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
@@ -340,19 +367,33 @@ Zotero.Translate.ItemSaver.prototype = {
Zotero.Attachments.linkFromURL(attachment.url, parentID,
(attachment.mimeType ? attachment.mimeType : undefined),
(attachment.title ? attachment.title : undefined));
+ attachmentCallback(attachment, 100);
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
+ attachmentCallback(attachment, false, e);
}
+ return true;
}
} else {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
if(automaticSnapshots) {
try {
- Zotero.Attachments.importFromDocument(attachment.document, parentID, attachment.title);
+ attachment.linkMode = "imported_url";
+ Zotero.Attachments.importFromDocument(attachment.document,
+ parentID, attachment.title, function(status, err) {
+ if(status) {
+ attachmentCallback(attachment, 100);
+ } else {
+ attachmentCallback(attachment, false, err);
+ }
+ }, this._libraryID);
+ attachmentCallback(attachment, 0);
} catch(e) {
Zotero.debug("Translate: Error attaching document", 2);
+ attachmentCallback(attachment, false, e);
}
+ return true;
}
// Save attachment if snapshot pref enabled or not HTML
// (in which case downloadAssociatedFiles applies)
@@ -364,14 +405,27 @@ Zotero.Translate.ItemSaver.prototype = {
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
try {
Zotero.debug('Importing attachment from URL');
+ attachment.linkMode = "imported_url";
Zotero.Attachments.importFromURL(attachment.url, parentID, title,
- fileBaseName, null, mimeType, this._libraryID, null, this._cookieSandbox);
+ fileBaseName, null, mimeType, this._libraryID, function(status, err) {
+ // TODO: actually indicate progress during download
+ if(status) {
+ attachmentCallback(attachment, 100);
+ } else {
+ attachmentCallback(attachment, false, err);
+ }
+ }, this._cookieSandbox);
+ attachmentCallback(attachment, 0);
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
+ attachmentCallback(attachment, false, e);
}
+ return true;
}
}
}
+
+ return false;
},
"_saveFields":function(item, newItem) {
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
@@ -1575,5 +1575,16 @@ Zotero.Utilities = {
}
}
return length;
+ },
+
+ /**
+ * Gets the icon for a JSON-style attachment
+ */
+ "determineAttachmentIcon":function(attachment) {
+ if(attachment.linkMode === "linked_url") {
+ return Zotero.ItemTypes.getImageSrc("attachment-web-link");
+ }
+ return Zotero.ItemTypes.getImageSrc(attachment.mimeType === "application/pdf"
+ ? "attachment-pdf" : "attachment-snapshot");
}
}
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
@@ -412,6 +412,7 @@ save.error.cannotAddFilesToCollection = You cannot add files to the currently se
ingester.saveToZotero = Save to Zotero
ingester.saveToZoteroUsing = Save to Zotero using "%S"
ingester.scraping = Saving Item…
+ingester.scrapingTo = Saving to
ingester.scrapeComplete = Item Saved
ingester.scrapeError = Could Not Save Item
ingester.scrapeErrorDescription = An error occurred while saving this item. Check %S for more information.
diff --git a/chrome/skin/default/zotero/zotero.css b/chrome/skin/default/zotero/zotero.css
@@ -228,12 +228,18 @@ label.zotero-text-link {
margin: 0;
min-height: 50px;
width: 250px;
- padding-bottom: 3px;
+ padding: 3px 0 3px 0;
}
#zotero-progress-text-headline
{
font-weight: bold;
+ margin-bottom: 2px;
+}
+
+.zotero-progress-icon-headline {
+ width: 16px;
+ height: 16px;
}
.zotero-progress-item-icon
@@ -245,8 +251,13 @@ label.zotero-text-link {
.zotero-progress-item-hbox
{
padding-left: 5px;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.zotero-progress-item-hbox[parent]
+{
margin-top: 3px;
- margin-bottom: 3px;
}
.zotero-progress-item-label