commit c6da52f24b6d6253944d2fe6df261514ec39293a
parent 23e2d1911c9664e5371bae70030508ba36916ab9
Author: Simon Kornblith <simon@simonster.com>
Date: Sat, 21 Jul 2012 20:17:14 -0400
Support saving snapshots from connector, part 1
Snapshots are currently saved using a <base> tag. Thus, images are skipped.
Diffstat:
1 file changed, 128 insertions(+), 23 deletions(-)
diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -33,8 +33,10 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, d
this._cookie = document.cookie;
}
- // Add listener for callbacks
- if(Zotero.Messaging && !Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded) {
+ // Add listener for callbacks, but only for Safari or the bookmarklet. In Chrome, we
+ // (have to) save attachments from the inject page.
+ if(Zotero.Messaging && !Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded
+ && (Zotero.isBookmarklet || Zotero.isSafari)) {
Zotero.Messaging.addMessageListener("attachmentCallback", function(data) {
var id = data[0],
status = data[1];
@@ -173,21 +175,15 @@ Zotero.Translate.ItemSaver.prototype = {
"_saveToServer":function(items, callback, attachmentCallback) {
var newItems = [], typedArraysSupported = false;
try {
- // Safari <5.2 supports typed arrays, but doesn't support sending them in
- // an XHR
- typedArraysSupported = new Uint8Array(1) && (!Zotero.isSafari || window.WebKitBlobBuilder);
+ typedArraysSupported = !!(new Uint8Array(1) && new Blob());
} catch(e) {}
+
for(var i=0, n=items.length; i<n; i++) {
var item = items[i];
newItems.push(Zotero.Utilities.itemToServerJSON(item));
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") {
- item.attachments.splice(j--, 1);
- } else {
- item.attachments[j].id = Zotero.Utilities.randomString();
- }
+ item.attachments[j].id = Zotero.Utilities.randomString();
}
} else {
item.attachments = [];
@@ -301,6 +297,25 @@ Zotero.Translate.ItemSaver.prototype = {
delete attachment.key;
delete attachment.data;
+ if(attachment.snapshot === false && attachment.mimeType) {
+ // If we aren't taking a snapshot and we have the MIME type, we don't need
+ // to retrieve anything
+ attachment.linkMode = "linked_url";
+ uploadAttachments.push(attachment);
+ if(attachmentCallback) attachmentCallback(attachment, 0);
+ if(--retrieveHeadersForAttachments === 0) createAttachments();
+ return;
+ }
+
+ var isSnapshot = false;
+ if(attachment.mimeType) {
+ switch(attachment.mimeType.toLowerCase()) {
+ case "text/html":
+ case "application/xhtml+xml":
+ isSnapshot = true;
+ }
+ }
+
/**
* Checks headers to ensure that they reflect our expectations. When headers have
* been checked for all attachments, creates new items on the z.org server and
@@ -317,9 +332,9 @@ Zotero.Translate.ItemSaver.prototype = {
status = xhr.status;
// Validate status
- if(status === 0) {
- // Probably failed due to SOP
- attachmentCallback(attachment, 50);
+ if(status === 0 || attachment.snapshot === false) {
+ // Failed due to SOP, or we are supposed to be getting a snapshot
+ attachmentCallback(attachment, 0);
attachment.linkMode = "linked_url";
} else if(status !== 200) {
err = new Error("Server returned unexpected status code "+status);
@@ -382,8 +397,8 @@ Zotero.Translate.ItemSaver.prototype = {
};
var xhr = new XMLHttpRequest();
- xhr.open("GET", attachment.url, true);
- xhr.responseType = "arraybuffer";
+ xhr.open((attachment.snapshot === false ? "HEAD" : "GET"), attachment.url, true);
+ xhr.responseType = (isSnapshot ? "document" : "arraybuffer");
xhr.onreadystatechange = function() {
if(xhr.readyState !== 4 || !checkHeaders()) return;
@@ -403,9 +418,7 @@ Zotero.Translate.ItemSaver.prototype = {
};
xhr.send();
- if(attachmentCallback) {
- attachmentCallback(attachment, 0);
- }
+ if(attachmentCallback) attachmentCallback(attachment, 0);
})(attachments[i]);
}
},
@@ -419,6 +432,91 @@ Zotero.Translate.ItemSaver.prototype = {
*/
"_uploadAttachmentToServer":function(attachment, attachmentCallback) {
Zotero.debug("Uploading attachment to server");
+ switch(attachment.mimeType.toLowerCase()) {
+ case "text/html":
+ case "application/xhtml+xml":
+ // It's possible that we didn't know if this was a snapshot until after the
+ // download began. If this is the case, we need to convert it to a document.
+ if(attachment.data instanceof ArrayBuffer) {
+ var me = this,
+ blob = new Blob([attachment.data], {"type":attachment.mimeType}),
+ reader = new FileReader();
+ reader.onloadend = function() {
+ if(reader.error) {
+ attachmentCallback(attachment, false, reader.error);
+ } else {
+ // Convert to an HTML document
+ var result = reader.result, doc;
+ try {
+ // First try using DOMParser
+ doc = (new DOMParser()).parseFromString(result, "text/html");
+ } catch(e) {}
+
+ // If DOMParser fails, use document.implementation.createHTMLDocument
+ if(!doc) {
+ doc = document.implementation.createHTMLDocument("");
+ var docEl = doc.documentElement;
+ docEl.innerHTML = result;
+ if(docEl.children.length === 1 && docEl.firstElementChild === "html") {
+ doc.replaceChild(docEl.firstElementChild, docEl);
+ }
+ }
+
+ attachment.data = doc;
+ me._uploadAttachmentToServer(attachment, attachmentCallback);
+ }
+ }
+ reader.readAsText(blob, attachment.charset || "iso-8859-1");
+ return;
+ }
+
+ // We are now assured that attachment.data is an HTMLDocument, so we can
+ // add a base tag
+
+ // Get the head tag
+ var doc = attachment.data,
+ head = doc.getElementsByTagName("head");
+ if(!head.length) {
+ head = doc.createElement("head");
+ var docEl = attachment.data.documentElement;
+ docEl.insertBefore(head, docEl.firstChildElement);
+ } else {
+ head = head[0];
+ }
+
+ // Add the base tag
+ var base = doc.createElement("base");
+ base.href = attachment.url;
+ head.appendChild(base);
+
+ // Remove content type tags
+ var metaTags = doc.getElementsByTagName("meta"), metaTag;
+ for(var i=0; i<metaTags.length; i++) {
+ metaTag = metaTags[i];
+ var attr = metaTag.getAttribute("http-equiv");
+ if(attr && attr.toLowerCase() === "content-type") {
+ metaTag.parentNode.removeChild(metaTag);
+ }
+ }
+
+ // Add UTF-8 content type
+ metaTag = doc.createElement("meta");
+ metaTag.setAttribute("http-equiv", "Content-Type");
+ metaTag.setAttribute("content", attachment.mimeType+"; charset=UTF-8");
+ head.insertBefore(metaTag, head.firstChild);
+
+ // Serialize document to UTF-8
+ var src = new XMLSerializer().serializeToString(doc),
+ srcLength = Zotero.Utilities.getStringByteLength(src),
+ srcArray = new Uint8Array(srcLength);
+ Zotero.Utilities.stringToUTF8Array(src, srcArray);
+
+ // Rewrite data
+ attachment.data = srcArray.buffer;
+ attachment.charset = "UTF-8";
+ break;
+ }
+
var binaryHash = this._md5(new Uint8Array(attachment.data), 0, attachment.data.byteLength),
hash = "";
for(var i=0; i<binaryHash.length; i++) {
@@ -427,10 +525,17 @@ Zotero.Translate.ItemSaver.prototype = {
}
attachment.md5 = hash;
- Zotero.Translate.ItemSaver._attachmentCallbacks[attachment.id] = function(status, error) {
- attachmentCallback(attachment, status, error);
- };
- Zotero.API.uploadAttachment(attachment);
+ if(Zotero.isChrome && !Zotero.isBookmarklet) {
+ // In Chrome, we don't use messaging for Zotero.API.uploadAttachment, since
+ // we can't pass ArrayBuffers to the background page
+ Zotero.API.uploadAttachment(attachment, attachmentCallback.bind(this, attachment));
+ } else {
+ // In Safari and the connectors, we can pass ArrayBuffers
+ Zotero.Translate.ItemSaver._attachmentCallbacks[attachment.id] = function(status, error) {
+ attachmentCallback(attachment, status, error);
+ };
+ Zotero.API.uploadAttachment(attachment);
+ }
},
/**