Attachment progress notifications. These are already hooked up to the UI in the connector, but still need to be hooked up to the UI in Firefox.

Addresses #3
This commit is contained in:
Simon Kornblith 2012-06-02 16:58:14 -04:00
parent f2d03014b0
commit 221d1da340
5 changed files with 218 additions and 34 deletions

View File

@ -551,9 +551,9 @@ var Zotero_Browser = new function() {
function _constructLookupFunction(tab, success) {
return function(e) {
tab.page.translate.setTranslator(tab.page.translators[0]);
tab.page.translate.clearHandlers("done");
tab.page.translate.clearHandlers("itemsDone");
tab.page.translate.clearHandlers("itemDone");
tab.page.translate.setHandler("done", function(obj, status) {
tab.page.translate.setHandler("itemsDone", function(obj, status) {
if(status) {
success(e, obj);
Zotero_Browser.progress.close();
@ -730,10 +730,10 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID, trans
// use first translator available
this.page.translate.setTranslator(translator ? translator : this.page.translators[0]);
this.page.translate.clearHandlers("done");
this.page.translate.clearHandlers("itemsDone");
this.page.translate.clearHandlers("itemDone");
this.page.translate.setHandler("done", function(obj, item) { Zotero_Browser.finishScraping(obj, item) });
this.page.translate.setHandler("itemsDone", 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.translate(libraryID);

View File

@ -220,6 +220,7 @@ Zotero.Attachments = new function(){
var urlRe = /^https?:\/\/[^\s]*$/;
var matches = urlRe.exec(url);
if (!matches) {
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();
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();
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();
callback(false, e);
throw (e);
}

View File

@ -79,10 +79,23 @@ Zotero.Translate.ItemSaver.prototype = {
payload.cookie = this._cookie;
}
Zotero.Connector.callMethod("saveItems", payload, function(success, status) {
if(success !== false) {
Zotero.Connector.callMethod("saveItems", payload, function(data, status) {
if(data !== false) {
Zotero.debug("Translate: Save via Standalone succeeded");
var haveAttachments = false;
if(data.items) {
for(var i=0; i<data.items.length; i++) {
var attachments = items[i].attachments = data.items[i].attachments;
for(var j=0; j<attachments.length; j++) {
if(attachments[j].id) {
attachmentCallback(attachments[j], 0);
haveAttachments = true;
}
}
}
}
callback(true, items);
if(haveAttachments) me._pollForProgress(items, attachmentCallback);
} else if(Zotero.isFx) {
callback(false, new Error("Save via Standalone failed with "+status));
} else {
@ -91,6 +104,60 @@ Zotero.Translate.ItemSaver.prototype = {
});
},
/**
* Polls for updates to attachment progress
* @param items Items in Zotero.Item.toArray() format
* @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.
* attachmentCallback() will be called with all attachments that will be saved
*/
"_pollForProgress":function(items, attachmentCallback) {
var attachments = [];
var progressIDs = [];
var previousStatus = [];
for(var i=0; i<items.length; i++) {
var itemAttachments = items[i].attachments;
for(var j=0; j<itemAttachments.length; j++) {
if(itemAttachments[j].id) {
attachments.push(itemAttachments[j]);
progressIDs.push(itemAttachments[j].id);
previousStatus.push(0);
}
}
}
var nPolls = 0;
var poll = function() {
Zotero.Connector.callMethod("attachmentProgress", progressIDs, function(currentStatus, status) {
if(currentStatus) {
for(var i=0; i<attachments.length; i++) {
if(currentStatus[i] === 100 || currentStatus[i] === false) {
attachmentCallback(attachments[i], currentStatus[i]);
attachments.splice(i, 1);
progressIDs.splice(i, 1);
previousStatus.splice(i, 1);
currentStatus.splice(i, 1);
i--;
} else if(currentStatus[i] !== previousStatus[i]) {
attachmentCallback(attachments[i], currentStatus[i]);
previousStatus[i] = currentStatus[i];
}
}
if(nPolls++ < 60 && attachments.length) {
setTimeout(poll, 1000);
}
} else {
for(var i=0; i<attachments.length; i++) {
attachmentCallback(attachments[i], false, "Lost connection to Zotero Standalone");
}
}
});
};
poll();
},
/**
* Saves items to server
* @param items Items in Zotero.Item.toArray() format
@ -101,6 +168,7 @@ Zotero.Translate.ItemSaver.prototype = {
* @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.
* attachmentCallback() will be called with all attachments that will be saved
*/
"_saveToServer":function(items, callback, attachmentCallback) {
var newItems = [], typedArraysSupported = false;

View File

@ -27,6 +27,31 @@ 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;
/**
* Called on attachment progress
*/
this.onProgress = function(attachment, progress, error) {
var progressID = attachmentsInProgress.get(attachment);
if(!progressID) {
progressID = attachment.id = i++;
attachmentsInProgress.set(attachment, progressID);
}
attachmentProgress[progressID] = 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 +194,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 +271,13 @@ 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.Browser.deleteHiddenBrowser(me._browser);
if(jsonItems.length || me.selectedItems === false) {
me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
@ -269,7 +298,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 +328,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);
}
}
@ -434,6 +464,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
*

View File

@ -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,12 @@ 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);
} else if(!newAttachment) {
item.attachments.splice(i--, 1);
}
}
}
}
@ -209,7 +224,7 @@ Zotero.Translate.ItemSaver.prototype = {
if(!attachment.url && !attachment.path) {
Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2);
return;
return false;
}
if(!attachment.path) {
@ -221,34 +236,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 +327,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 +340,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 && "__wrappedDOMObject" in attachment.document) {
attachment.document = attachment.document.__wrappedDOMObject;
@ -327,10 +348,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 +369,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 +407,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) {