commit 1585ece566094600faa38844ba6efaac5507da9f
parent 7598370af447c4fe91bbe9db82cb12adc900c599
Author: Simon Kornblith <simon@simonster.com>
Date: Mon, 9 Apr 2012 11:27:01 -0400
Merge branches '3.0' and 'master'
Diffstat:
8 files changed, 749 insertions(+), 74 deletions(-)
diff --git a/chrome/content/zotero/recognizePDF.js b/chrome/content/zotero/recognizePDF.js
@@ -363,7 +363,7 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
var me = this;
if(this._DOI) {
// use CrossRef to look for DOI
- var translate = new Zotero.Translate("search");
+ var translate = new Zotero.Translate.Search();
translate.setTranslator("11645bd1-0420-45c1-badb-53fb41eeb753");
var item = {"itemType":"journalArticle", "DOI":this._DOI};
translate.setSearch(item);
@@ -411,7 +411,7 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
this._hiddenBrowser.docShell.allowImages = false;
}
- var translate = new Zotero.Translate("web");
+ var translate = new Zotero.Translate.Web();
var savedItem = false;
translate.setTranslator("57a00950-f0d1-4b41-b6ba-44ff0fc30289");
translate.setHandler("itemDone", function(translate, item) {
@@ -425,6 +425,13 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
translate.setHandler("done", function(translate, success) {
if(!success || !savedItem) me._queryGoogle();
});
+ translate.setHandler("translators", function(translate, detected) {
+ if(detected.length) {
+ translate.translate(me._libraryID, false);
+ } else {
+ me._queryGoogle();
+ }
+ });
this._hiddenBrowser.addEventListener("pageshow", function() { me._scrape(translate) }, true);
@@ -459,10 +466,11 @@ Zotero_RecognizePDF.Recognizer.prototype._scrape = function(/**Zotero.Translate*
this._callback(false, "recognizePDF.limit");
return;
}
-
+
this._hiddenBrowser.removeEventListener("pageshow", this._scrape.caller, true);
translate.setDocument(this._hiddenBrowser.contentDocument);
- translate.translate(this._libraryID, false);
+
+ translate.getTranslators(false, true);
}
/**
diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js
@@ -78,11 +78,10 @@ Zotero.Connector_Types = new function() {
this.getImageSrc = function(idOrName) {
var itemType = Zotero.Connector_Types["itemTypes"][idOrName];
- if(!itemType) return false;
- var icon = itemType[6]/* icon */;
+ var icon = itemType ? itemType[6]/* icon */ : "treeitem-"+idOrName+".png";
if(Zotero.isBookmarklet) {
- return ZOTERO_CONFIG.BOOKMARKLET_URL+"icons/"+icon;
+ return ZOTERO_CONFIG.BOOKMARKLET_URL+"images/"+icon;
} else if(Zotero.isFx) {
return "chrome://zotero/skin/"+icon;
} else if(Zotero.isChrome) {
diff --git a/chrome/content/zotero/xpcom/connector/connector.js b/chrome/content/zotero/xpcom/connector/connector.js
@@ -27,8 +27,7 @@ Zotero.Connector = new function() {
const CONNECTOR_URI = "http://127.0.0.1:23119/";
const CONNECTOR_API_VERSION = 2;
- var _ieStandaloneIframeTarget;
- var _ieConnectorCallbacks;
+ var _ieStandaloneIframeTarget, _ieConnectorCallbacks;
this.isOnline = null;
/**
@@ -67,16 +66,26 @@ Zotero.Connector = new function() {
Zotero.debug("Connector: Standalone found; trying IE hack");
_ieConnectorCallbacks = [];
- Zotero.Messaging.addMessageListener("standaloneLoaded", function(data, event) {
+ var listener = function(event) {
if(event.origin !== "http://127.0.0.1:23119") return;
+ event.stopPropagation();
- Zotero.debug("Connector: Standalone loaded");
- _ieStandaloneIframeTarget = iframe.contentWindow;
- callback(true);
- });
- Zotero.Messaging.addMessageListener("connectorResponse", function(data, event) {
- if(event.origin !== "http://127.0.0.1:23119") return;
+ // If this is the first time the target was loaded, then this is a loaded
+ // event
+ if(!_ieStandaloneIframeTarget) {
+ Zotero.debug("Connector: Standalone loaded");
+ _ieStandaloneIframeTarget = iframe.contentWindow;
+ callback(true);
+ return;
+ }
+ // Otherwise, this is a response event
+ try {
+ var data = JSON.parse(event.data);
+ } catch(e) {
+ Zotero.debug("Invalid JSON received: "+event.data);
+ return;
+ }
var xhrSurrogate = {
"status":data[1],
"responseText":data[2],
@@ -84,7 +93,13 @@ Zotero.Connector = new function() {
};
_ieConnectorCallbacks[data[0]](xhrSurrogate);
delete _ieConnectorCallbacks[data[0]];
- });
+ };
+
+ if(window.addEventListener) {
+ window.addEventListener("message", listener, false);
+ } else {
+ window.attachEvent("onmessage", function() { listener(event); });
+ }
var iframe = document.createElement("iframe");
iframe.src = "http://127.0.0.1:23119/connector/ieHack";
@@ -169,10 +184,10 @@ Zotero.Connector = new function() {
};
if(Zotero.isIE) { // IE requires XDR for CORS
- if(_ieStandaloneIframeTarget !== undefined) {
+ if(_ieStandaloneIframeTarget) {
var requestID = Zotero.Utilities.randomString();
_ieConnectorCallbacks[requestID] = newCallback;
- _ieStandaloneIframeTarget.postMessage("ZOTERO_MSG "+JSON.stringify([null, "connectorRequest",
+ _ieStandaloneIframeTarget.postMessage(JSON.stringify([null, "connectorRequest",
[requestID, method, JSON.stringify(data)]]), "http://127.0.0.1:23119/connector/ieHack");
} else {
Zotero.debug("Connector: No iframe target; not sending to Standalone");
diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -32,7 +32,26 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, d
this._uri = document.location.toString();
this._cookie = document.cookie;
}
+
+ // Add listener for callbacks
+ if(!Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded) {
+ Zotero.Messaging.addMessageListener("attachmentCallback", function(data) {
+ var id = data[0],
+ status = data[1];
+ var callback = Zotero.Translate.ItemSaver._attachmentCallbacks[id];
+ if(callback) {
+ if(status === false || status === 100) {
+ delete Zotero.Translate.ItemSaver._attachmentCallbacks[id];
+ }
+ data[1] = 50+data[1]/2;
+ callback(data[1], data[2]);
+ }
+ });
+ Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = true;
+ }
}
+Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = false;
+Zotero.Translate.ItemSaver._attachmentCallbacks = {};
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
@@ -41,8 +60,16 @@ Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
Zotero.Translate.ItemSaver.prototype = {
/**
* 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) {
+ "saveItems":function(items, callback, attachmentCallback) {
var me = this;
// first try to save items via connector
var payload = {"items":items};
@@ -58,30 +85,454 @@ Zotero.Translate.ItemSaver.prototype = {
} else if(Zotero.isFx) {
callback(false, new Error("Save via Standalone failed with "+status));
} else {
- me._saveToServer(items, callback);
+ me._saveToServer(items, callback, attachmentCallback);
}
});
},
/**
* Saves items to 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.
*/
- "_saveToServer":function(items, callback) {
- var newItems = [];
+ "_saveToServer":function(items, callback, attachmentCallback) {
+ var newItems = [], typedArraysSupported = false;
+ try {
+ typedArraysSupported = new Uint8Array(1);
+ } catch(e) {}
for(var i=0, n=items.length; i<n; i++) {
- newItems.push(Zotero.Utilities.itemToServerJSON(items[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();
+ }
+ }
+ } else {
+ item.attachments = [];
+ }
}
- var url = 'users/%%USERID%%/items';
- var payload = JSON.stringify({"items":newItems}, null, "\t")
-
- Zotero.OAuth.doAuthenticatedPost(url, payload, function(status) {
- if(!status) {
+ var me = this;
+ Zotero.OAuth.createItem({"items":newItems}, null, function(statusCode, response) {
+ if(statusCode !== 201) {
callback(false, new Error("Save to server failed"));
} else {
Zotero.debug("Translate: Save to server complete");
- callback(true, newItems);
+
+ if(typedArraysSupported) {
+ try {
+ var newKeys = me._getItemKeysFromServerResponse(response);
+ } catch(e) {
+ callback(false, e);
+ return;
+ }
+
+ for(var i=0; i<items.length; i++) {
+ var item = items[i], key = newKeys[i];
+ if(item.attachments && item.attachments.length) {
+ me._saveAttachmentsToServer(key, me._getFileBaseNameFromItem(item),
+ item.attachments, attachmentCallback);
+ }
+ }
+ }
+
+ callback(true, items);
}
- }, true);
- }
+ });
+ },
+
+ /**
+ * Saves an attachment to server
+ * @param {String} itemKey The key of the parent item
+ * @param {String} baseName A string to use as the base name for attachments
+ * @param {Object[]} attachments An array of attachment objects
+ * @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.
+ */
+ "_saveAttachmentsToServer":function(itemKey, baseName, attachments, attachmentCallback) {
+ var me = this,
+ uploadAttachments = [],
+ retrieveHeadersForAttachments = attachments.length;
+
+ /**
+ * Creates attachments on the z.org server. This is executed after we have received
+ * headers for all attachments to be downloaded, but before they are uploaded to
+ * z.org.
+ * @inner
+ */
+ var createAttachments = function() {
+ var attachmentPayload = [];
+ for(var i=0; i<uploadAttachments.length; i++) {
+ var attachment = uploadAttachments[i];
+ attachmentPayload.push({
+ "itemType":"attachment",
+ "linkMode":attachment.linkMode,
+ "title":(attachment.title ? attachment.title.toString() : "Untitled Attachment"),
+ "accessDate":"CURRENT_TIMESTAMP",
+ "url":attachment.url,
+ "note":(attachment.note ? attachment.note.toString() : ""),
+ "tags":(attachment.tags && attachment.tags instanceof Array ? attachment.tags : [])
+ });
+ }
+
+ Zotero.OAuth.createItem({"items":attachmentPayload}, itemKey, function(statusCode, response) {
+ var err;
+ if(statusCode === 201) {
+ try {
+ var newKeys = me._getItemKeysFromServerResponse(response);
+ } catch(e) {
+ err = new Error("Unexpected response received from server");
+ }
+ } else {
+ err = new Error("Unexpected status "+statusCode+" received from server");
+ }
+
+ for(var i=0; i<uploadAttachments.length; i++) {
+ var attachment = uploadAttachments[i];
+ if(err) {
+ attachmentProgress(attachment, false, err);
+ } else {
+ attachment.key = newKeys[i];
+
+ Zotero.debug("Finished creating item");
+ if(attachment.linkMode === "linked_url") {
+ attachmentCallback(attachment, 100);
+ } else if("data" in attachment) {
+ me._uploadAttachmentToServer(attachment, attachmentCallback);
+ }
+ }
+ }
+
+ if(err) throw err;
+ });
+ };
+
+ for(var i=0; i<attachments.length; i++) {
+ // Also begin to download attachments
+ (function(attachment) {
+ var headersValidated = null;
+
+ // Ensure these are undefined before continuing, since we'll use them to determine
+ // whether an attachment has been created on the Zotero server and downloaded from
+ // the host
+ delete attachment.key;
+ delete attachment.data;
+
+ /**
+ * 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
+ * begins uploading them.
+ * @inner
+ */
+ var checkHeaders = function() {
+ if(headersValidated !== null) return headersValidated;
+
+ retrieveHeadersForAttachments--;
+ headersValidated = false;
+
+ var err = null,
+ status = xhr.status;
+
+ // Validate status
+ if(status === 0) {
+ // Probably failed due to SOP
+ attachmentCallback(attachment, 50);
+ attachment.linkMode = "linked_url";
+ } else if(status !== 200) {
+ err = new Error("Server returned unexpected status code "+status);
+ } else {
+ // Validate content type
+ var contentType = "application/octet-stream",
+ charset = null,
+ contentTypeHeader = xhr.getResponseHeader("Content-Type");
+ if(contentTypeHeader) {
+ // See RFC 2616 sec 3.7
+ var m = /^[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+\/[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+/.exec(contentTypeHeader);
+ if(m) contentType = m[0].toLowerCase();
+ m = /;\s*charset\s*=\s*("[^"]+"|[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+)/.exec(contentTypeHeader);
+ if(m) {
+ charset = m[1];
+ if(charset[0] === '"') charset = charset.substring(1, charset.length-1);
+ }
+
+ if(attachment.mimeType
+ && attachment.mimeType.toLowerCase() !== contentType.toLowerCase()) {
+ err = new Error("Attachment MIME type "+contentType+
+ " does not match specified type "+attachment.mimeType);
+ }
+ }
+
+ attachment.mimeType = contentType;
+ attachment.linkMode = "imported_url";
+ switch(contentType.toLowerCase()) {
+ case "application/pdf":
+ attachment.filename = baseName+".pdf";
+ break;
+ case "text/html":
+ case "application/xhtml+xml":
+ attachment.filename = baseName+".html";
+ break;
+ default:
+ attachment.filename = baseName;
+ }
+ if(charset) attachment.charset = charset;
+ headersValidated = true;
+ }
+
+ // If we didn't validate the headers, cancel the request
+ if(headersValidated === false && "abort" in xhr) xhr.abort();
+
+ // Add attachments to attachment payload if there was no error
+ if(!err) {
+ uploadAttachments.push(attachment);
+ }
+
+ // If we have retrieved the headers for all attachments, create items on z.org
+ // server
+ if(retrieveHeadersForAttachments === 0) createAttachments();
+
+ // If there was an error, throw it now
+ if(err) {
+ attachmentCallback(attachment, false, err);
+ throw err;
+ }
+ };
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", attachment.url, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onloadend = function() {
+ if(!checkHeaders()) return;
+
+ attachmentCallback(attachment, 50);
+ attachment.data = xhr.response;
+ // If item already created, head to upload
+ if("key" in attachment) {
+ me._uploadAttachmentToServer(attachment, attachmentCallback);
+ }
+ };
+ xhr.onprogress = function(event) {
+ if(this.readyState < 2 || !checkHeaders()) return;
+
+ if(event.total && attachmentCallback) {
+ attachmentCallback(attachment, event.loaded/event.total*50);
+ }
+ };
+ xhr.send();
+
+ if(attachmentCallback) {
+ attachmentCallback(attachment, 0);
+ }
+ })(attachments[i]);
+ }
+ },
+
+ /**
+ * Uploads an attachment to the Zotero server
+ * @param {Object} attachment Attachment object, including
+ * @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.
+ */
+ "_uploadAttachmentToServer":function(attachment, attachmentCallback) {
+ var binaryHash = this._md5(new Uint8Array(attachment.data), 0, attachment.data.byteLength),
+ hash = "";
+ for(var i=0; i<binaryHash.length; i++) {
+ if(binaryHash[i] < 16) hash += "0";
+ hash += binaryHash[i].toString(16);
+ }
+ attachment.md5 = hash;
+
+ Zotero.Translate.ItemSaver._attachmentCallbacks[attachment.id] = function(status, error) {
+ attachmentCallback(attachment, status, error);
+ };
+ Zotero.OAuth.uploadAttachment(attachment);
+ },
+
+ /**
+ * Gets item keys from a server response
+ * @param {String} response ATOM response
+ */
+ "_getItemKeysFromServerResponse":function(response) {
+ try {
+ response = (new DOMParser()).parseFromString(response, "text/xml");
+ } catch(e) {
+ throw new Error("Save to server returned invalid output");
+ }
+ var keyNodes = response.getElementsByTagNameNS("http://zotero.org/ns/api", "key");
+ var newKeys = [];
+ for(var i=0, n=keyNodes.length; i<n; i++) {
+ newKeys.push("textContent" in keyNodes[i] ? keyNodes[i].textContent
+ : keyNodes[i].innerText);
+ }
+ return newKeys;
+ },
+
+ /**
+ * Gets the base name for an attachment from an item object. This mimics the default behavior
+ * of Zotero.Attachments.getFileBaseNameFromItem
+ * @param {Object} item
+ */
+ "_getFileBaseNameFromItem":function(item) {
+ var parts = [];
+ if(item.creators && item.creators.length) {
+ if(item.creators.length === 1) {
+ parts.push(item.creators[0].lastName);
+ } else if(item.creators.length === 2) {
+ parts.push(item.creators[0].lastName+" and "+item.creators[1].lastName);
+ } else {
+ parts.push(item.creators[0].lastName+" et al.");
+ }
+ }
+
+ if(item.date) {
+ var date = Zotero.Date.strToDate(item.date);
+ if(date.year) parts.push(date.year);
+ }
+
+ if(item.title) {
+ parts.push(item.title.substr(0, 50));
+ }
+
+ if(parts.length) return parts.join(" - ");
+ return "Attachment";
+ },
+
+ /*
+ pdf.js MD5 implementation
+ Copyright (c) 2011 Mozilla Foundation
+
+ Contributors: Andreas Gal <gal@mozilla.com>
+ Chris G Jones <cjones@mozilla.com>
+ Shaon Barman <shaon.barman@gmail.com>
+ Vivien Nicolas <21@vingtetun.org>
+ Justin D'Arcangelo <justindarc@gmail.com>
+ Yury Delendik
+ Kalervo Kujala
+ Adil Allawi <@ironymark>
+ Jakob Miland <saebekassebil@gmail.com>
+ Artur Adib <aadib@mozilla.com>
+ Brendan Dahl <bdahl@mozilla.com>
+ David Quintana <gigaherz@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ */
+ "_md5":(function calculateMD5Closure() {
+ // Don't throw if typed arrays are not supported
+ try {
+ var r = new Uint8Array([
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
+
+ var k = new Int32Array([
+ -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
+ -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
+ 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
+ 643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
+ 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
+ 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
+ -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
+ -722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
+ -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
+ -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
+ -145523070, -1120210379, 718787259, -343485551]);
+ } catch(e) {};
+
+ function hash(data, offset, length) {
+ var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
+ // pre-processing
+ var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
+ var padded = new Uint8Array(paddedLength);
+ var i, j, n;
+ if (offset || length != data.byteLength) {
+ padded.set(new Uint8Array(data.buffer, offset, length));
+ } else {
+ padded.set(data);
+ }
+ i = length;
+ padded[i++] = 0x80;
+ n = paddedLength - 8;
+ while (i < n)
+ padded[i++] = 0;
+ padded[i++] = (length << 3) & 0xFF;
+ padded[i++] = (length >> 5) & 0xFF;
+ padded[i++] = (length >> 13) & 0xFF;
+ padded[i++] = (length >> 21) & 0xFF;
+ padded[i++] = (length >>> 29) & 0xFF;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ // chunking
+ // TODO ArrayBuffer ?
+ var w = new Int32Array(16);
+ for (i = 0; i < paddedLength;) {
+ for (j = 0; j < 16; ++j, i += 4) {
+ w[j] = (padded[i] | (padded[i + 1] << 8) |
+ (padded[i + 2] << 16) | (padded[i + 3] << 24));
+ }
+ var a = h0, b = h1, c = h2, d = h3, f, g;
+ for (j = 0; j < 64; ++j) {
+ if (j < 16) {
+ f = (b & c) | ((~b) & d);
+ g = j;
+ } else if (j < 32) {
+ f = (d & b) | ((~d) & c);
+ g = (5 * j + 1) & 15;
+ } else if (j < 48) {
+ f = b ^ c ^ d;
+ g = (3 * j + 5) & 15;
+ } else {
+ f = c ^ (b | (~d));
+ g = (7 * j) & 15;
+ }
+ var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
+ d = c;
+ c = b;
+ b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
+ a = tmp;
+ }
+ h0 = (h0 + a) | 0;
+ h1 = (h1 + b) | 0;
+ h2 = (h2 + c) | 0;
+ h3 = (h3 + d) | 0;
+ }
+ return new Uint8Array([
+ h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
+ h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
+ h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
+ h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
+ ]);
+ }
+ return hash;
+ })()
};
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
@@ -90,6 +90,7 @@ Zotero.Translate.Sandbox = {
const allowedObjects = ["complete", "attachments", "seeAlso", "creators", "tags", "notes"];
+ delete item.complete;
for(var i in item) {
var val = item[i];
var type = typeof val;
@@ -99,7 +100,7 @@ Zotero.Translate.Sandbox = {
} else if(type === "string") {
// trim strings
item[i] = val.trim();
- } else if((type === "object" || type === "xml") && allowedObjects.indexOf(i) === -1) {
+ } else if((type === "object" || type === "xml" || type === "function") && allowedObjects.indexOf(i) === -1) {
// convert things that shouldn't be objecst to objects
translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to string");
item[i] = val.toString();
@@ -144,6 +145,8 @@ Zotero.Translate.Sandbox = {
translate.complete(false, data);
throw data;
}
+ }, function(arg1, arg2, arg3) {
+ translate._attachmentProgress(arg1, arg2, arg3);
});
translate._runHandler("itemSaving", item);
@@ -889,15 +892,41 @@ Zotero.Translate.Base.prototype = {
*
* @param {Boolean} [getAllTranslators] Whether all applicable translators should be returned,
* rather than just the first available.
+ * @param {Boolean} [checkSetTranslator] If true, the appropriate detect function is run on the
+ * set document/text/etc. using the translator set by setTranslator.
+ * getAllTranslators parameter is meaningless in this context.
* @return {Zotero.Translator[]} An array of {@link Zotero.Translator} objects
*/
- "getTranslators":function(getAllTranslators) {
+ "getTranslators":function(getAllTranslators, checkSetTranslator) {
// do not allow simultaneous instances of getTranslators
if(this._currentState === "detect") throw new Error("getTranslators: detection is already running");
this._currentState = "detect";
this._getAllTranslators = getAllTranslators;
- this._getTranslatorsGetPotentialTranslators();
-
+
+ if(checkSetTranslator) {
+ // setTranslator must be called beforehand if checkSetTranslator is set
+ if( !this.translator || !this.translator[0] ) {
+ throw new Error("getTranslators: translator must be set via setTranslator before calling" +
+ " getTranslators with the checkSetTranslator flag");
+ }
+ var translators = new Array();
+ var t;
+ for(var i=0, n=this.translator.length; i<n; i++) {
+ if(typeof(this.translator[i]) == 'string') {
+ t = Zotero.Translators.get(this.translator[i]);
+ if(!t) Zotero.debug("getTranslators: could not retrieve translator '" + this.translator[i] + "'");
+ } else {
+ t = this.translator[i];
+ }
+ /**TODO: check that the translator is of appropriate type?*/
+ if(t) translators.push(t);
+ }
+ if(!translators.length) throw new Error("getTranslators: no valid translators were set.");
+ this._getTranslatorsTranslatorsReceived(translators);
+ } else {
+ this._getTranslatorsGetPotentialTranslators();
+ }
+
// if detection returns immediately, return found translators
if(!this._currentState) return this._foundTranslators;
},
@@ -985,6 +1014,7 @@ Zotero.Translate.Base.prototype = {
this._libraryID = libraryID;
this._saveAttachments = saveAttachments === undefined || saveAttachments;
+ this._attachmentsSaving = [];
var me = this;
if(typeof this.translator[0] === "object") {
@@ -1036,30 +1066,6 @@ Zotero.Translate.Base.prototype = {
},
/**
- * 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.
- */
- "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]);
- }
-
- this.saveQueue = [];
- } else {
- Zotero.logError(data);
- }
-
- this._runHandler("done", returnValue);
- },
-
- /**
* Return the progress of the import operation, or null if progress cannot be determined
*/
"getProgress":function() { return null },
@@ -1078,9 +1084,14 @@ Zotero.Translate.Base.prototype = {
// Make sure this isn't called twice
if(this._currentState === null) {
- var e = new Error();
- Zotero.debug("Translate: WARNING: Zotero.done() called after translation completion. This should never happen. Please examine the stack below.");
- Zotero.debug(e.stack);
+ if(!returnValue) {
+ Zotero.debug("Translate: WARNING: Zotero.done() called after translator completion with error");
+ Zotero.debug(error);
+ } else {
+ var e = new Error();
+ Zotero.debug("Translate: WARNING: Zotero.done() called after translation completion. This should never happen. Please examine the stack below.");
+ Zotero.debug(e.stack);
+ }
return;
}
var oldState = this._currentState;
@@ -1102,8 +1113,19 @@ Zotero.Translate.Base.prototype = {
var lastProperToProxyFunction = this._properToProxyFunctions ? this._properToProxyFunctions.shift() : null;
if(returnValue) {
- var dupeTranslator = {"itemType":returnValue, "properToProxy":lastProperToProxyFunction};
+ var dupeTranslator = {"properToProxy":lastProperToProxyFunction};
+
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
+ if(Zotero.isBookmarklet && returnValue === "server") {
+ // In the bookmarklet, the return value from detectWeb can be "server" to
+ // indicate the translator should be run on the Zotero server
+ dupeTranslator.runMode = Zotero.Translator.RUN_MODE_ZOTERO_SERVER;
+ } else {
+ // Usually the return value from detectWeb will be either an item type or
+ // the string "multiple"
+ dupeTranslator.itemType = returnValue;
+ }
+
this._foundTranslators.push(dupeTranslator);
} else if(error) {
this._debug("Detect using "+lastTranslator.label+" failed: \n"+errorString, 2);
@@ -1127,7 +1149,8 @@ Zotero.Translate.Base.prototype = {
if(this.saveQueue.length) {
var me = this;
this._itemSaver.saveItems(this.saveQueue.slice(),
- function(returnValue, data) { me.itemsSaved(returnValue, data) });
+ function(returnValue, data) { me._itemsSaved(returnValue, data); },
+ function(arg1, arg2, arg3) { me._attachmentProgress(arg1, arg2, arg3); });
return;
} else {
this._debug("Translation successful");
@@ -1145,13 +1168,78 @@ Zotero.Translate.Base.prototype = {
}
// call handlers
- this._runHandler("done", returnValue);
+ this._runHandler("itemsDone", returnValue);
+ if(returnValue) {
+ this._checkIfDone();
+ } else {
+ this._runHandler("done", returnValue);
+ }
}
return errorString;
},
/**
+ * 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.
+ */
+ "_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]);
+ }
+
+ 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();
+ },
+
+ /**
+ * Checks if saving done, and if so, fires done event
+ */
+ "_checkIfDone":function() {
+ if(!this._attachmentsSaving.length) {
+ this._runHandler("done", true);
+ }
+ },
+
+ /**
* Begins running detect code for a translator, first loading it
*/
"_detect":function() {
@@ -1462,12 +1550,11 @@ Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments,
* Overload _translateTranslatorLoaded to send an RPC call if necessary
*/
Zotero.Translate.Web.prototype._translateTranslatorLoaded = function() {
- if(this.translator[0].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER
- || this._parentTranslator) {
- // begin process to run translator in browser
+ var runMode = this.translator[0].runMode;
+ if(runMode === Zotero.Translator.RUN_MODE_IN_BROWSER || this._parentTranslator) {
Zotero.Translate.Base.prototype._translateTranslatorLoaded.apply(this);
- } else {
- // otherwise, ferry translator load to RPC
+ } else if(runMode === Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE ||
+ (runMode === Zotero.Translator.RUN_MODE_ZOTERO_SERVER && Zotero.Connector.isOnline)) {
var me = this;
Zotero.Connector.callMethod("savePage", {
"uri":this.location.toString(),
@@ -1476,11 +1563,17 @@ Zotero.Translate.Web.prototype._translateTranslatorLoaded = function() {
"cookie":this.document.cookie,
"html":this.document.documentElement.innerHTML
}, function(obj) { me._translateRPCComplete(obj) });
+ } else if(runMode === Zotero.Translator.RUN_MODE_ZOTERO_SERVER) {
+ var me = this;
+ Zotero.OAuth.createItem({"url":this.document.location.href.toString()}, null,
+ function(statusCode, response) {
+ me._translateServerComplete(statusCode, response);
+ });
}
}
/**
- * Called when an RPC call for remote translation completes
+ * Called when an call to Zotero Standalone for translation completes
*/
Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode) {
if(!obj) this.complete(false, failureCode);
@@ -1488,7 +1581,7 @@ Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode
if(obj.selectItems) {
// if we have to select items, call the selectItems handler and do it
var me = this;
- var items = this._runHandler("select", obj.selectItems,
+ this._runHandler("select", obj.selectItems,
function(selectedItems) {
Zotero.Connector.callMethod("selectItems",
{"instanceID":obj.instanceID, "selectedItems":selectedItems},
@@ -1504,6 +1597,66 @@ Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode
this.complete(true);
}
}
+
+/**
+ * Called when an call to the Zotero Translator Server for translation completes
+ */
+Zotero.Translate.Web.prototype._translateServerComplete = function(statusCode, response) {
+ if(statusCode === 300) {
+ // Multiple Choices
+ try {
+ response = JSON.parse(response);
+ } catch(e) {
+ Zotero.logError(e);
+ this.complete(false, "Invalid JSON response received from server");
+ return;
+ }
+ var me = this;
+ this._runHandler("select", response,
+ function(selectedItems) {
+ Zotero.OAuth.createItem({
+ "url":me.document.location.href.toString(),
+ "items":selectedItems
+ }, null,
+ function(statusCode, response) {
+ me._translateServerComplete(statusCode, response);
+ });
+ }
+ );
+ } else if(statusCode === 201) {
+ // Created
+ try {
+ response = (new DOMParser()).parseFromString(response, "application/xml");
+ } catch(e) {
+ Zotero.logError(e);
+ this.complete(false, "Invalid XML response received from server");
+ return;
+ }
+
+ // Extract items from ATOM/JSON response
+ var items = [];
+ var contents = response.getElementsByTagNameNS("http://www.w3.org/2005/Atom", "content");
+ for(var i=0, n=contents.length; i<n; i++) {
+ var content = contents[i];
+ if(content.getAttributeNS("http://zotero.org/ns/api", "type") != "json") continue;
+
+ try {
+ item = JSON.parse("textContent" in content ?
+ content.textContent : content.innerText);
+ } catch(e) {
+ Zotero.logError(e);
+ this.complete(false, "Invalid JSON response received from server");
+ return;
+ }
+ this._runHandler("itemDone", null, item);
+ items.push(item);
+ }
+ this.newItems = items;
+ this.complete(true);
+ } else {
+ this.complete(false, response);
+ }
+}
/**
* Overload complete to report translation failure
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
@@ -1476,7 +1476,6 @@ Zotero.Utilities = {
}
},
-
/**
* Get the real target URL from an intermediate URL
*/
@@ -1501,5 +1500,55 @@ Zotero.Utilities = {
}
return url;
+ },
+
+ /**
+ * Adds a string to a given array at a given offset, converted to UTF-8
+ * @param {String} string The string to convert to UTF-8
+ * @param {Array|Uint8Array} array The array to which to add the string
+ * @param {Integer} [offset] Offset at which to add the string
+ */
+ "stringToUTF8Array":function(string, array, offset) {
+ if(!offset) offset = 0;
+ var n = string.length;
+ for(var i=0; i<n; i++) {
+ var val = string.charCodeAt(i);
+ if(val >= 128) {
+ if(val >= 2048) {
+ array[offset] = ((val >>> 6) | 192);
+ array[offset+1] = (val & 63) | 128;
+ offset += 2;
+ } else {
+ array[offset] = (val >>> 12) | 224;
+ array[offset+1] = ((val >>> 6) & 63) | 128;
+ array[offset+2] = (val & 63) | 128;
+ offset += 3;
+ }
+ } else {
+ array[offset++] = val;
+ }
+ }
+ },
+
+ /**
+ * Gets the byte length of the UTF-8 representation of a given string
+ * @param {String} string
+ * @return {Integer}
+ */
+ "getStringByteLength":function(string) {
+ var length = 0, n = string.length;
+ for(var i=0; i<n; i++) {
+ var val = string.charCodeAt(i);
+ if(val >= 128) {
+ if(val >= 2048) {
+ length += 3;
+ } else {
+ length += 2;
+ }
+ } else {
+ length += 1;
+ }
+ }
+ return length;
}
}
diff --git a/chrome/skin/default/zotero/progress_arcs.png b/chrome/skin/default/zotero/progress_arcs.png
Binary files differ.
diff --git a/chrome/skin/default/zotero/treeitem-attachment-pdf.png b/chrome/skin/default/zotero/treeitem-attachment-pdf.png
Binary files differ.