commit 8268d1b01c7363107cba5cc7eb72d0c3c9beae46
parent fd5d9496fde2386916e7aa65e366307e9841da80
Author: Simon Kornblith <simon@simonster.com>
Date: Tue, 14 Jun 2011 00:36:21 +0000
Zotero Everywhere megacommit
- Implement connector for Firefox (should switch in/out of connector mode automatically when Standalone is launched or closed, although this has only been tested extensively on OS X)
- Share core translation code between Zotero and connectors
Still to be done:
- Run translators in non-Fx connectors (this works in theory, but it's not currently enabled for any translators)
- Show translation results in non-Fx connectors
- Ability to translate to server when Zotero Standalone is not running
Diffstat:
37 files changed, 4246 insertions(+), 2304 deletions(-)
diff --git a/chrome.manifest b/chrome.manifest
@@ -63,6 +63,6 @@ contract @mozilla.org/autocomplete/search;1?name=zotero {06a2ed11-d0a4-4ff0-a56
component {9BC3D762-9038-486A-9D70-C997AF848A7C} components/zotero-protocol-handler.js
contract @mozilla.org/network/protocol;1?name=zotero {9BC3D762-9038-486A-9D70-C997AF848A7C}
-component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-integration-service.js
-contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration {531828f8-a16c-46be-b9aa-14845c3b010f}
-category command-line-handler m-zotero-integration @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration
+component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-command-line-handler.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero {531828f8-a16c-46be-b9aa-14845c3b010f}
+category command-line-handler m-zotero @mozilla.org/commandlinehandler/general-startup;1?type=zotero
diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js
@@ -112,6 +112,17 @@ var Zotero_Browser = new function() {
function(e) { Zotero_Browser.chromeLoad(e) }, false);
window.addEventListener("unload",
function(e) { Zotero_Browser.chromeUnload(e) }, false);
+
+ ZoteroPane_Local.addReloadListener(reload);
+ reload();
+ }
+
+ /**
+ * Called when Zotero is reloaded
+ */
+ function reload() {
+ // Handles the display of a div showing progress in scraping
+ Zotero_Browser.progress = new Zotero.ProgressWindow();
}
/**
@@ -144,7 +155,7 @@ var Zotero_Browser = new function() {
// get libraryID and collectionID
var libraryID, collectionID;
- if(ZoteroPane) {
+ if(ZoteroPane && !Zotero.isConnector) {
libraryID = ZoteroPane.getSelectedLibraryID();
collectionID = ZoteroPane.getSelectedCollection(true);
} else {
@@ -374,7 +385,7 @@ var Zotero_Browser = new function() {
// get data object
var tab = _getTabObject(browser);
- if(isHTML) {
+ if(isHTML && !Zotero.isConnector) {
var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec);
if(annotationID) {
if(Zotero.Annotate.isAnnotated(annotationID)) {
@@ -509,8 +520,8 @@ var Zotero_Browser = new function() {
* Callback to be executed when an item has been finished
*/
function itemDone(obj, item, collection) {
- var title = item.getField("title", false, true);
- var icon = item.getImageSrc();
+ 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]);
@@ -742,7 +753,7 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
this.page.hasBeenTranslated = true;
}
this.page.translate.clearHandlers("itemDone");
- this.page.translate.setHandler("itemDone", function(obj, item) { Zotero_Browser.itemDone(obj, item, collection) });
+ this.page.translate.setHandler("itemDone", function(obj, dbItem, item) { Zotero_Browser.itemDone(obj, item, collection) });
this.page.translate.translate(libraryID);
}
@@ -755,12 +766,10 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
Zotero_Browser.Tab.prototype.getCaptureIcon = function() {
if(this.page.translators && this.page.translators.length) {
var itemType = this.page.translators[0].itemType;
- if(itemType == "multiple") {
- // Use folder icon for multiple types, for now
- return "chrome://zotero/skin/treesource-collection.png";
- } else {
- return Zotero.ItemTypes.getImageSrc(itemType);
- }
+ Zotero.debug("want capture icon for "+itemType);
+ return (itemType === "multiple"
+ ? "chrome://zotero/skin/treesource-collection.png"
+ : Zotero.ItemTypes.getImageSrc(itemType));
}
return false;
@@ -784,7 +793,7 @@ Zotero_Browser.Tab.prototype.getCaptureTooltip = function() {
/*
* called when a user is supposed to select items
*/
-Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList) {
+Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList, callback) {
// this is kinda ugly, mozillazine made me do it! honest!
var io = { dataIn:itemList, dataOut:null }
var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
@@ -794,7 +803,7 @@ Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList) {
Zotero_Browser.progress.close();
}
- return io.dataOut;
+ callback(io.dataOut);
}
/*
@@ -812,7 +821,4 @@ Zotero_Browser.Tab.prototype._translatorsAvailable = function(translate, transla
Zotero_Browser.updateStatus();
}
-// Handles the display of a div showing progress in scraping
-Zotero_Browser.progress = new Zotero.ProgressWindow();
-
Zotero_Browser.init();
\ No newline at end of file
diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js
@@ -29,11 +29,16 @@
var ZoteroOverlay = new function()
{
const DEFAULT_ZPANE_HEIGHT = 300;
- var toolbarCollapseState, isFx36, showInPref;
+ var toolbarCollapseState, isFx36, showInPref;
+ var zoteroPane, zoteroSplitter;
+ var _stateBeforeReload = false;
this.isTab = false;
this.onLoad = function() {
+ zoteroPane = document.getElementById('zotero-pane-stack');
+ zoteroSplitter = document.getElementById('zotero-splitter');
+
ZoteroPane_Overlay = ZoteroPane;
ZoteroPane.init();
@@ -141,6 +146,19 @@ var ZoteroOverlay = new function()
if(Zotero.isFx4) {
XULBrowserWindow.inContentWhitelist.push("chrome://zotero/content/tab.xul");
}
+
+ // Close pane if connector is enabled
+ ZoteroPane_Local.addReloadListener(function() {
+ if(Zotero.isConnector) {
+ // save current state
+ _stateBeforeReload = !zoteroPane.hidden && !zoteroPane.collapsed;
+ // ensure pane is closed
+ if(!zoteroPane.collapsed) ZoteroOverlay.toggleDisplay(false);
+ } else {
+ // reopen pane if it was open before
+ ZoteroOverlay.toggleDisplay(_stateBeforeReload);
+ }
+ });
}
this.onUnload = function() {
@@ -158,10 +176,16 @@ var ZoteroOverlay = new function()
*/
this.toggleDisplay = function(makeVisible)
{
- if(this.isTab && (makeVisible || makeVisible === undefined)) {
- // If in separate tab mode, just open the tab
- this.loadZoteroTab();
- return;
+ if(makeVisible || makeVisible === undefined) {
+ if(Zotero.isConnector) {
+ // If in connector mode, bring Zotero Standalone to foreground
+ Zotero.activateStandalone();
+ return;
+ } else if(this.isTab) {
+ // If in separate tab mode, just open the tab
+ this.loadZoteroTab();
+ return;
+ }
}
if(!Zotero || !Zotero.initialized) {
@@ -169,12 +193,7 @@ var ZoteroOverlay = new function()
return;
}
- var zoteroPane = document.getElementById('zotero-pane-stack');
- var zoteroSplitter = document.getElementById('zotero-splitter');
- var isHidden = zoteroPane.getAttribute('hidden') == 'true';
- var isCollapsed = zoteroPane.getAttribute('collapsed') == 'true';
-
- if(makeVisible === undefined) makeVisible = isHidden || isCollapsed;
+ if(makeVisible === undefined) makeVisible = zoteroPane.hidden || zoteroPane.collapsed;
zoteroSplitter.setAttribute('hidden', !makeVisible);
zoteroPane.setAttribute('hidden', false);
diff --git a/chrome/content/zotero/recognizePDF.js b/chrome/content/zotero/recognizePDF.js
@@ -402,7 +402,7 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
Zotero.Browser.deleteHiddenBrowser(me._hiddenBrowser);
me._callback(item);
});
- translate.setHandler("select", function(translate, items) { return me._selectItems(translate, items) });
+ translate.setHandler("select", function(translate, items) { me._selectItems(translate, items, callback) });
translate.setHandler("done", function(translate, success) { if(!success) me._queryGoogle(); });
this._hiddenBrowser.addEventListener("pageshow", function() { me._scrape(translate) }, true);
@@ -449,10 +449,12 @@ Zotero_RecognizePDF.Recognizer.prototype._scrape = function(/**Zotero.Translate*
* @private
* @type Object
*/
-Zotero_RecognizePDF.Recognizer.prototype._selectItems = function(/**Zotero.Translate*/ translate, /**Object*/ items) {
+Zotero_RecognizePDF.Recognizer.prototype._selectItems = function(/**Zotero.Translate*/ translate,
+ /**Object*/ items, /**Function**/ callback) {
for(var i in items) {
var obj = {};
obj[i] = items;
- return obj;
+ callback(obj);
+ return;
}
}
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector.js b/chrome/content/zotero/xpcom/connector.js
@@ -1,780 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-Zotero.Connector = new function() {
- var _onlineObserverRegistered;
- var responseCodes = {
- 200:"OK",
- 201:"Created",
- 300:"Multiple Choices",
- 400:"Bad Request",
- 404:"Not Found",
- 500:"Internal Server Error",
- 501:"Method Not Implemented"
- };
-
- /**
- * initializes a very rudimentary web server
- */
- this.init = function() {
- if (Zotero.HTTP.browserIsOffline()) {
- Zotero.debug('Browser is offline -- not initializing connector HTTP server');
- _registerOnlineObserver();
- return;
- }
-
- // start listening on socket
- var serv = Components.classes["@mozilla.org/network/server-socket;1"]
- .createInstance(Components.interfaces.nsIServerSocket);
- try {
- // bind to a random port on loopback only
- serv.init(Zotero.Prefs.get('connector.port'), true, -1);
- serv.asyncListen(Zotero.Connector.SocketListener);
-
- Zotero.debug("Connector HTTP server listening on 127.0.0.1:"+serv.port);
- } catch(e) {
- Zotero.debug("Not initializing connector HTTP server");
- }
-
- _registerOnlineObserver()
- }
-
- /**
- * generates the response to an HTTP request
- */
- this.generateResponse = function (status, contentType, body) {
- var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
- response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
- response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
-
- if(body) {
- if(contentType) {
- response += "Content-Type: "+contentType+"\r\n";
- }
- response += "\r\n"+body;
- } else {
- response += "Content-Length: 0\r\n\r\n"
- }
-
- return response;
- }
-
- /**
- * Decodes application/x-www-form-urlencoded data
- *
- * @param {String} postData application/x-www-form-urlencoded data, as sent in a g request
- * @return {Object} data in object form
- */
- this.decodeURLEncodedData = function(postData) {
- var splitData = postData.split("&");
- var variables = {};
- for each(var variable in splitData) {
- var splitIndex = variable.indexOf("=");
- variables[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
- }
- return variables;
- }
-
- function _registerOnlineObserver() {
- if (_onlineObserverRegistered) {
- return;
- }
-
- // Observer to enable the integration when we go online
- var observer = {
- observe: function(subject, topic, data) {
- if (data == 'online') {
- Zotero.Connector.init();
- }
- }
- };
-
- var observerService =
- Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- observerService.addObserver(observer, "network:offline-status-changed", false);
-
- _onlineObserverRegistered = true;
- }
-}
-
-Zotero.Connector.SocketListener = new function() {
- this.onSocketAccepted = onSocketAccepted;
- this.onStopListening = onStopListening;
-
- /*
- * called when a socket is opened
- */
- function onSocketAccepted(socket, transport) {
- // get an input stream
- var iStream = transport.openInputStream(0, 0, 0);
- var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
-
- var dataListener = new Zotero.Connector.DataListener(iStream, oStream);
- var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
- .createInstance(Components.interfaces.nsIInputStreamPump);
- pump.init(iStream, -1, -1, 0, 0, false);
- pump.asyncRead(dataListener, null);
- }
-
- function onStopListening(serverSocket, status) {
- Zotero.debug("Connector HTTP server going offline");
- }
-}
-
-/*
- * handles the actual acquisition of data
- */
-Zotero.Connector.DataListener = function(iStream, oStream) {
- this.header = "";
- this.headerFinished = false;
-
- this.body = "";
- this.bodyLength = 0;
-
- this.iStream = iStream;
- this.oStream = oStream;
- this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Components.interfaces.nsIScriptableInputStream);
- this.sStream.init(iStream);
-
- this.foundReturn = false;
-}
-
-/*
- * called when a request begins (although the request should have begun before
- * the DataListener was generated)
- */
-Zotero.Connector.DataListener.prototype.onStartRequest = function(request, context) {}
-
-/*
- * called when a request stops
- */
-Zotero.Connector.DataListener.prototype.onStopRequest = function(request, context, status) {
- this.iStream.close();
- this.oStream.close();
-}
-
-/*
- * called when new data is available
- */
-Zotero.Connector.DataListener.prototype.onDataAvailable = function(request, context,
- inputStream, offset, count) {
- var readData = this.sStream.read(count);
-
- if(this.headerFinished) { // reading body
- this.body += readData;
- // check to see if data is done
- this._bodyData();
- } else { // reading header
- // see if there's a magic double return
- var lineBreakIndex = readData.indexOf("\r\n\r\n");
- if(lineBreakIndex != -1) {
- if(lineBreakIndex != 0) {
- this.header += readData.substr(0, lineBreakIndex+4);
- this.body = readData.substr(lineBreakIndex+4);
- }
-
- this._headerFinished();
- return;
- }
- var lineBreakIndex = readData.indexOf("\n\n");
- if(lineBreakIndex != -1) {
- if(lineBreakIndex != 0) {
- this.header += readData.substr(0, lineBreakIndex+2);
- this.body = readData.substr(lineBreakIndex+2);
- }
-
- this._headerFinished();
- return;
- }
- if(this.header && this.header[this.header.length-1] == "\n" &&
- (readData[0] == "\n" || readData[0] == "\r")) {
- if(readData.length > 1 && readData[1] == "\n") {
- this.header += readData.substr(0, 2);
- this.body = readData.substr(2);
- } else {
- this.header += readData[0];
- this.body = readData.substr(1);
- }
-
- this._headerFinished();
- return;
- }
- this.header += readData;
- }
-}
-
-/*
- * processes an HTTP header and decides what to do
- */
-Zotero.Connector.DataListener.prototype._headerFinished = function() {
- this.headerFinished = true;
-
- Zotero.debug(this.header);
-
- const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
-
- // get first line of request (all we care about for now)
- var method = methodRe.exec(this.header);
-
- if(!method) {
- this._requestFinished(Zotero.Connector.generateResponse(400));
- return;
- }
- if(!Zotero.Connector.Endpoints[method[2]]) {
- this._requestFinished(Zotero.Connector.generateResponse(404));
- return;
- }
- this.endpoint = Zotero.Connector.Endpoints[method[2]];
-
- if(method[1] == "HEAD" || method[1] == "OPTIONS") {
- this._requestFinished(Zotero.Connector.generateResponse(200));
- } else if(method[1] == "GET") {
- this._requestFinished(this._processEndpoint("GET", method[3]));
- } else if(method[1] == "POST") {
- const contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
-
- // parse content length
- var m = contentLengthRe.exec(this.header);
- if(!m) {
- this._requestFinished(Zotero.Connector.generateResponse(400));
- return;
- }
-
- this.bodyLength = parseInt(m[1]);
- this._bodyData();
- } else {
- this._requestFinished(Zotero.Connector.generateResponse(501));
- return;
- }
-}
-
-/*
- * checks to see if Content-Length bytes of body have been read and, if so, processes the body
- */
-Zotero.Connector.DataListener.prototype._bodyData = function() {
- if(this.body.length >= this.bodyLength) {
- // convert to UTF-8
- var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
- .createInstance(Components.interfaces.nsIStringInputStream);
- dataStream.setData(this.body, this.bodyLength);
-
- var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
- .createInstance(Components.interfaces.nsIConverterInputStream);
- utf8Stream.init(dataStream, "UTF-8", 4096, "?");
-
- this.body = "";
- var string = {};
- while(utf8Stream.readString(this.bodyLength, string)) {
- this.body += string.value;
- }
-
- // handle envelope
- this._processEndpoint("POST", this.body);
- }
-}
-
-/**
- * Generates a response based on calling the function associated with the endpoint
- */
-Zotero.Connector.DataListener.prototype._processEndpoint = function(method, postData) {
- try {
- var endpoint = new this.endpoint;
- var me = this;
- var sendResponseCallback = function(code, contentType, arg) {
- me._requestFinished(Zotero.Connector.generateResponse(code, contentType, arg));
- }
- endpoint.init(method, postData ? postData : undefined, sendResponseCallback);
- } catch(e) {
- Zotero.debug(e);
- this._requestFinished(Zotero.Connector.generateResponse(500));
- throw e;
- }
-}
-
-/*
- * returns HTTP data from a request
- */
-Zotero.Connector.DataListener.prototype._requestFinished = function(response) {
- // close input stream
- this.iStream.close();
-
- // open UTF-8 converter for output stream
- var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
- .createInstance(Components.interfaces.nsIConverterOutputStream);
-
- // write
- try {
- intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
-
- // write response
- Zotero.debug(response);
- intlStream.writeString(response);
- } finally {
- intlStream.close();
- }
-}
-
-/**
- * Manage cookies in a sandboxed fashion
- *
- * @param {browser} browser Hidden browser object
- * @param {String} uri URI of page to manage cookies for (cookies for domains that are not
- * subdomains of this URI are ignored)
- * @param {String} cookieData Cookies with which to initiate the sandbox
- */
-Zotero.Connector.CookieManager = function(browser, uri, cookieData) {
- this._webNav = browser.webNavigation;
- this._browser = browser;
- this._watchedBrowsers = [browser];
- this._observerService = Components.classes["@mozilla.org/observer-service;1"].
- getService(Components.interfaces.nsIObserverService);
-
- this._uri = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService)
- .newURI(uri, null, null);
-
- var splitCookies = cookieData.split(/; ?/);
- this._cookies = {};
- for each(var cookie in splitCookies) {
- var key = cookie.substr(0, cookie.indexOf("="));
- var value = cookie.substr(cookie.indexOf("=")+1);
- this._cookies[key] = value;
- }
-
- [this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
-}
-
-Zotero.Connector.CookieManager.prototype = {
- "_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
- "_watchedXHRs":[],
-
- /**
- * nsIObserver implementation for adding, clearing, and slurping cookies
- */
- "observe": function(channel, topic) {
- if(topic == "quit-application") {
- Zotero.debug("WARNING: A Zotero.Connector.CookieManager for "+this._uri.spec+" was still open on shutdown");
- } else {
- channel.QueryInterface(Components.interfaces.nsIHttpChannel);
- var isTracked = null;
- try {
- var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
- for each(var browser in this._watchedBrowsers) {
- isTracked = topDoc == browser.contentDocument;
- if(isTracked) break;
- }
- } catch(e) {}
- if(isTracked === null) {
- try {
- isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
- } catch(e) {}
- }
- if(isTracked === null) {
- try {
- isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
- } catch(e) {}
- }
-
- // isTracked is now either true, false, or null
- // true => we should manage cookies for this request
- // false => we should not manage cookies for this request
- // null => this request is of a type we couldn't match to this request. one such type
- // is a link prefetch (nsPrefetchNode) but there might be others as well. for
- // now, we are paranoid and reject these.
-
- if(isTracked === false) {
- Zotero.debug("Zotero.Connector.CookieManager: not touching channel for "+channel.URI.spec);
- return;
- } else if(isTracked) {
- Zotero.debug("Zotero.Connector.CookieManager: managing cookies for "+channel.URI.spec);
- } else {
- Zotero.debug("Zotero.Connector.CookieManager: being paranoid about channel for "+channel.URI.spec);
- }
-
- if(topic == "http-on-modify-request") {
- // clear cookies to be sent to other domains
- if(isTracked === null || channel.URI.host != this._uri.host) {
- channel.setRequestHeader("Cookie", "", false);
- channel.setRequestHeader("Cookie2", "", false);
- Zotero.debug("Zotero.Connector.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
- return;
- }
-
- // add cookies to be sent to this domain
- var cookies = [key+"="+this._cookies[key]
- for(key in this._cookies)].join("; ");
- channel.setRequestHeader("Cookie", cookies, false);
- Zotero.debug("Zotero.Connector.CookieManager: added cookies for request to "+channel.URI.spec);
- } else if(topic == "http-on-examine-response") {
- // clear cookies being received
- try {
- var cookieHeader = channel.getResponseHeader("Set-Cookie");
- } catch(e) {
- return;
- }
- channel.setResponseHeader("Set-Cookie", "", false);
- channel.setResponseHeader("Set-Cookie2", "", false);
-
- // don't process further if these cookies are for another set of domains
- if(isTracked === null || channel.URI.host != this._uri.host) {
- Zotero.debug("Zotero.Connector.CookieManager: rejected cookies from "+channel.URI.spec);
- return;
- }
-
- // put new cookies into our sandbox
- if(cookieHeader) {
- var cookies = cookieHeader.split(/; ?/);
- var newCookies = {};
- for each(var cookie in cookies) {
- var key = cookie.substr(0, cookie.indexOf("="));
- var value = cookie.substr(cookie.indexOf("=")+1);
- var lcCookie = key.toLowerCase();
-
- if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
- // ignore cookie parameters; we are only holding cookies for a few minutes
- // with a single domain, and the path attribute doesn't allow any additional
- // security anyway
- continue;
- } else if(lcCookie == "secure") {
- // don't accept secure cookies
- newCookies = {};
- break;
- } else {
- newCookies[key] = value;
- }
- }
- [this._cookies[key] = newCookies[key] for(key in newCookies)];
- }
-
- Zotero.debug("Zotero.Connector.CookieManager: slurped cookies from "+channel.URI.spec);
- }
- }
- },
-
- /**
- * Attach CookieManager to a specific XMLHttpRequest
- * @param {XMLHttpRequest} xhr
- */
- "attachToBrowser": function(browser) {
- this._watchedBrowsers.push(browser);
- },
-
- /**
- * Attach CookieManager to a specific XMLHttpRequest
- * @param {XMLHttpRequest} xhr
- */
- "attachToXHR": function(xhr) {
- this._watchedXHRs.push(xhr);
- },
-
- /**
- * Destroys this CookieManager (intended to be executed when the browser is destroyed)
- */
- "destroy": function() {
- [this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
- }
-}
-
-Zotero.Connector.Data = {};
-
-Zotero.Connector.Translate = function() {};
-Zotero.Connector.Translate._waitingForSelection = {};
-
-
-/**
- * Lists all available translators, including code for translators that should be run on every page
- */
-Zotero.Connector.Translate.List = function() {};
-
-Zotero.Connector.Translate.List.prototype = {
- /**
- * Gets available translator list
- * @param {String} method "GET" or "POST"
- * @param {String} data POST data or GET query string
- * @param {Function} sendResponseCallback function to send HTTP response
- */
- "init":function(method, data, sendResponseCallback) {
- if(method != "POST") {
- sendResponseCallback(400);
- return;
- }
-
- var translators = Zotero.Translators.getAllForType("web");
- var jsons = [];
- for each(var translator in translators) {
- let json = {};
- for each(var key in ["translatorID", "label", "creator", "target", "priority", "detectXPath"]) {
- json[key] = translator[key];
- }
- json["localExecution"] = translator.browserSupport.indexOf(data["browser"]) !== -1;
-
- // Do not pass targetless translators that do not support this browser (since that
- // would mean passing each page back to Zotero)
- if(json["target"] || json["detectXPath"] || json["localExecution"]) {
- jsons.push(json);
- }
- }
-
- sendResponseCallback(200, "application/json", JSON.stringify(jsons));
- }
-}
-
-/**
- * Detects whether there is an available translator to handle a given page
- */
-Zotero.Connector.Translate.Detect = function() {};
-
-Zotero.Connector.Translate.Detect.prototype = {
- /**
- * Loads HTML into a hidden browser and initiates translator detection
- * @param {String} method "GET" or "POST"
- * @param {String} data POST data or GET query string
- * @param {Function} sendResponseCallback function to send HTTP response
- */
- "init":function(method, data, sendResponseCallback) {
- if(method != "POST") {
- sendResponseCallback(400);
- return;
- }
-
- this.sendResponse = sendResponseCallback;
- this._parsedPostData = JSON.parse(data);
-
- this._translate = new Zotero.Translate("web");
- this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
-
- Zotero.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
- this._browser = Zotero.Browser.createHiddenBrowser();
-
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
-
- var pageShowCalled = false;
- var me = this;
- this._translate.setCookieManager(new Zotero.Connector.CookieManager(this._browser,
- this._parsedPostData["uri"], this._parsedPostData["cookie"]));
- this._browser.addEventListener("DOMContentLoaded", function() {
- try {
- if(me._browser.contentDocument.location.href == "about:blank") return;
- if(pageShowCalled) return;
- pageShowCalled = true;
- delete Zotero.Connector.Data[me._parsedPostData["uri"]];
-
- // get translators
- me._translate.setDocument(me._browser.contentDocument);
- me._translate.getTranslators();
- } catch(e) {
- Zotero.debug(e);
- throw e;
- }
- }, false);
-
- me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
- },
-
- /**
- * Callback to be executed when list of translators becomes available. Sends response with
- * item types, translator IDs, labels, and icons for available translators.
- * @param {Zotero.Translate} translate
- * @param {Zotero.Translator[]} translators
- */
- "_translatorsAvailable":function(obj, translators) {
- var jsons = [];
- for each(var translator in translators) {
- if(translator.itemType == "multiple") {
- var icon = "treesource-collection.png"
- } else {
- var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
- icon = icon.substr(icon.lastIndexOf("/")+1);
- }
- var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
- "label":translator.label, "icon":icon}
- jsons.push(json);
- }
- this.sendResponse(200, "application/json", JSON.stringify(jsons));
-
- this._translate.cookieManager.destroy();
- Zotero.Browser.deleteHiddenBrowser(this._browser);
- }
-}
-
-/**
- * Performs translation of a given page
- */
-Zotero.Connector.Translate.Save = function() {};
-Zotero.Connector.Translate.Save.prototype = {
- /**
- * Init method inherited from Zotero.Connector.Translate.Detect
- * @borrows Zotero.Connector.Translate.Detect as this.init
- */
- "init":Zotero.Connector.Translate.Detect.prototype.init,
-
- /**
- * Callback to be executed when items must be selected
- * @param {Zotero.Translate} translate
- * @param {Object} itemList ID=>text pairs representing available items
- */
- "_selectItems":function(translate, itemList) {
- var instanceID = Zotero.randomString();
- Zotero.Connector.Translate._waitingForSelection[instanceID] = this;
-
- // Fix for translators that don't create item lists as objects
- if(itemList.push && typeof itemList.push === "function") {
- var newItemList = {};
- for(var item in itemList) {
- Zotero.debug(item);
- newItemList[item] = itemList[item];
- }
- itemList = newItemList;
- }
-
- // Send "Multiple Choices" HTTP response
- this.sendResponse(300, "application/json", JSON.stringify({"items":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
-
- // We need this to make sure that we won't stop Firefox from quitting, even if the user
- // didn't close the selectItems window
- var observerService = Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- var me = this;
- var quitObserver = {observe:function() { me.selectedItems = false; }};
- observerService.addObserver(quitObserver, "quit-application", false);
-
- this.selectedItems = null;
- var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
- // permanently slow Firefox with this loop
- while(this.selectedItems === null && Date.now() < endTime) {
- Zotero.mainThread.processNextEvent(true);
- }
-
- observerService.removeObserver(quitObserver, "quit-application");
- if(!this.selectedItems) this._progressWindow.close();
- return this.selectedItems;
- },
-
- /**
- * Callback to be executed when list of translators becomes available. Opens progress window,
- * selects specified translator, and initiates translation.
- * @param {Zotero.Translate} translate
- * @param {Zotero.Translator[]} translators
- */
- "_translatorsAvailable":function(translate, translators) {
- // make sure translatorsAvailable succeded
- if(!translators.length) {
- Zotero.Browser.deleteHiddenBrowser(this._browser);
- this.sendResponse(500);
- return;
- }
-
- // set up progress window
- var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator)
- .getMostRecentWindow("navigator:browser");
-
- this._progressWindow = win.Zotero_Browser.progress;
- if(Zotero.locked) {
- this._progressWindow.changeHeadline(Zotero.getString("ingester.scrapeError"));
- var desc = Zotero.localeJoin([
- Zotero.getString('general.operationInProgress'), Zotero.getString('general.operationInProgress.waitUntilFinishedAndTryAgain')
- ]);
- this._progressWindow.addDescription(desc);
- this._progressWindow.show();
- this._progressWindow.startCloseTimer(8000);
- return;
- }
-
- this._progressWindow.show();
-
- // set save callbacks
- this._libraryID = null;
- var collection = null;
- try {
- this._libraryID = win.ZoteroPane.getSelectedLibraryID();
- collection = win.ZoteroPane.getSelectedCollection();
- } catch(e) {}
- var me = this;
- translate.setHandler("select", function(obj, item) { return me._selectItems(obj, item) });
- translate.setHandler("itemDone", function(obj, item) { win.Zotero_Browser.itemDone(obj, item, collection) });
- translate.setHandler("done", function(obj, item) {
- win.Zotero_Browser.finishScraping(obj, item, collection);
- me._translate.cookieManager.destroy();
- Zotero.Browser.deleteHiddenBrowser(me._browser);
- me.sendResponse(201);
- });
-
- // set translator and translate
- translate.setTranslator(this._parsedPostData.translatorID);
- translate.translate(this._libraryID);
- }
-}
-
-/**
- * Handle item selection
- */
-Zotero.Connector.Translate.Select = function() {};
-Zotero.Connector.Translate.Select.prototype = {
- /**
- * Finishes up translation when item selection is complete
- * @param {String} method "GET" or "POST"
- * @param {String} data POST data or GET query string
- * @param {Function} sendResponseCallback function to send HTTP response
- */
- "init":function(method, postData, sendResponseCallback) {
- if(method != "POST") {
- sendResponseCallback(400);
- return;
- }
-
- var postData = JSON.parse(postData);
- var saveInstance = Zotero.Connector.Translate._waitingForSelection[postData.instanceID];
- saveInstance.sendResponse = sendResponseCallback;
-
- saveInstance.selectedItems = false;
- for(var i in postData.items) {
- saveInstance.selectedItems = postData.items;
- break;
- }
- }
-}
-
-/**
- * Endpoints for the Connector HTTP server
- *
- * Each endpoint should take the form of an object. The init() method of this object will be passed:
- * method - the method of the request ("GET" or "POST")
- * data - the query string (for a "GET" request) or POST data (for a "POST" request)
- * sendResponseCallback - a function to send a response to the HTTP request. This can be passed
- * a response code alone (e.g., sendResponseCallback(404)) or a response
- * code, MIME type, and response body
- * (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
- */
-Zotero.Connector.Endpoints = {
- "/translate/list":Zotero.Connector.Translate.List,
- "/translate/detect":Zotero.Connector.Translate.Detect,
- "/translate/save":Zotero.Connector.Translate.Save,
- "/translate/select":Zotero.Connector.Translate.Select
-}
-\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js
@@ -0,0 +1,113 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+/**
+ * Emulates very small parts of cachedTypes.js and itemFields.js APIs for use with connector
+ */
+
+/**
+ * @namespace
+ */
+Zotero.Connector.Types = new function() {
+ /**
+ * Initializes types
+ * @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
+ */
+ this.init = function(typeSchema) {
+ const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
+
+ // attach IDs and make referenceable by either ID or name
+ for(var i=0; i<schemaTypes.length; i++) {
+ var schemaType = schemaTypes[i];
+ this[schemaType] = typeSchema[schemaType];
+ for(var id in this[schemaType]) {
+ var entry = this[schemaType][id];
+ entry.id = id;
+ this[schemaType][entry.name] = entry;
+ }
+ }
+ }
+}
+
+Zotero.CachedTypes = function() {
+ this.getID = function(idOrName) {
+ if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
+ return Zotero.Connector.Types[this.schemaType][idOrName].id;
+ }
+
+ this.getName = function(idOrName) {
+ if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
+ return Zotero.Connector.Types[this.schemaType][idOrName].name;
+ }
+
+ this.getLocalizedString = function(idOrName) {
+ if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
+ return Zotero.Connector.Types[this.schemaType][idOrName].localizedString;
+ }
+}
+
+Zotero.ItemTypes = new function() {
+ this.schemaType = "itemTypes";
+ Zotero.CachedTypes.call(this);
+
+ this.getImageSrc = function(idOrName) {
+ if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
+
+ if(Zotero.isFx) {
+ return "chrome://zotero/skin/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
+ } else {
+ return "images/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
+ }
+ }
+}
+
+Zotero.CreatorTypes = new function() {
+ this.schemaType = "creatorTypes";
+ Zotero.CachedTypes.call(this);
+
+ this.getTypesForItemType = function(idOrName) {
+ if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
+ var itemType = Zotero.Connector.Types["itemTypes"][idOrName];
+ var creatorTypes = [];
+ for(var i=0; i<itemType.creatorTypes.length; i++) {
+ creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]);
+ }
+ return creatorTypes;
+ }
+}
+
+Zotero.ItemFields = new function() {
+ this.schemaType = "fields";
+ Zotero.CachedTypes.call(this);
+
+ this.isValidForType = function(fieldIdOrName, typeIdOrName) {
+ // mimics itemFields.js
+ if(!Zotero.Connector.Types["fields"][fieldIdOrName]
+ || !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
+
+ return Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf(
+ Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1;
+ }
+}
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/connector.js b/chrome/content/zotero/xpcom/connector/connector.js
@@ -0,0 +1,176 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2011 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+Zotero.Connector = new function() {
+ const CONNECTOR_URI = "http://127.0.0.1:23119/";
+
+ this.isOnline = true;
+ this.haveRefreshedData = false;
+ this.data = null;
+
+ /**
+ * Called to initialize Zotero
+ */
+ this.init = function() {
+ Zotero.Connector.getData();
+ }
+
+ function _getDataFile() {
+ var dataFile = Zotero.getZoteroDirectory();
+ dataFile.append("connector.json");
+ return dataFile;
+ }
+
+ /**
+ * Serializes the Zotero.Connector.data object to localStorage/preferences
+ * @param {String} [json] The
+ */
+ this.serializeData = function(json) {
+ if(!json) json = JSON.stringify(Zotero.Connector.data);
+
+ if(Zotero.isFx) {
+ Zotero.File.putContents(_getDataFile(), json);
+ } else {
+ localStorage.data = json;
+ }
+ }
+
+ /**
+ * Unserializes the Zotero.Connector.data object from localStorage/preferences
+ */
+ this.unserializeData = function() {
+ var data = null;
+
+ if(Zotero.isFx) {
+ var dataFile = _getDataFile();
+ if(dataFile.exists()) data = Zotero.File.getContents(dataFile);
+ } else {
+ if(localStorage.data) data = localStorage.data;
+ }
+
+ if(data) Zotero.Connector.data = JSON.parse(data);
+ }
+
+ // saner descriptions of some HTTP error codes
+ this.EXCEPTION_NOT_AVAILABLE = 0;
+ this.EXCEPTION_BAD_REQUEST = 400;
+ this.EXCEPTION_NO_ENDPOINT = 404;
+ this.EXCEPTION_CONNECTOR_INTERNAL = 500;
+ this.EXCEPTION_METHOD_NOT_IMPLEMENTED = 501;
+ this.EXCEPTION_CODES = [0, 400, 404, 500, 501];
+
+ /**
+ * Updates Zotero's status depending on the success or failure of a request
+ *
+ * @param {Boolean} isOnline Whether or not Zotero was online
+ * @param {Function} successCallback Function to be called after loading new data if
+ * Zotero is online
+ * @param {Function} failureCallback Function to be called if Zotero is offline
+ *
+ * Calls Zotero.Connector.Browser.onStateChange(isOnline, method, context) if status has changed
+ */
+ function _checkState(isOnline, callback) {
+ if(isOnline) {
+ if(Zotero.Connector.haveRefreshedData) {
+ if(callback) callback(true);
+ } else {
+ Zotero.Connector.getData(callback);
+ }
+ } else {
+ if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE);
+ }
+
+ if(Zotero.Connector.isOnline !== isOnline) {
+ Zotero.Connector.isOnline = isOnline;
+ if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) {
+ Zotero.Connector_Browser.onStateChange(isOnline);
+ }
+ }
+
+ return isOnline;
+ }
+
+ /**
+ * Loads list of translators and other relevant data from local Zotero instance
+ *
+ * @param {Function} successCallback Function to be called after loading new data if
+ * Zotero is online
+ * @param {Function} failureCallback Function to be called if Zotero is offline
+ */
+ this.getData = function(callback) {
+ Zotero.HTTP.doPost(CONNECTOR_URI+"connector/getData",
+ JSON.stringify({"browser":Zotero.Connector_Browser}),
+ function(req) {
+ var isOnline = req.status !== 0;
+
+ if(isOnline) {
+ // if request succeded, update data
+ Zotero.Connector.haveRefreshedData = true;
+ Zotero.Connector.serializeData(req.responseText);
+ Zotero.Connector.data = JSON.parse(req.responseText);
+ } else {
+ // if request failed, unserialize saved data
+ Zotero.Connector.unserializeData();
+ }
+ Zotero.Connector.Types.init(Zotero.Connector.data.schema);
+
+ // update online state. this shouldn't loop, since haveRefreshedData should
+ // be true if isOnline is true.
+ _checkState(isOnline, callback);
+ }, {"Content-Type":"application/json"});
+ }
+
+ /**
+ * Sends the XHR to execute an RPC call.
+ *
+ * @param {String} method RPC method. See documentation above.
+ * @param {Object} data RPC data. See documentation above.
+ * @param {Function} successCallback Function to be called if request succeeded.
+ * @param {Function} failureCallback Function to be called if request failed.
+ */
+ this.callMethod = function(method, data, callback) {
+ Zotero.HTTP.doPost(CONNECTOR_URI+"connector/"+method, JSON.stringify(data),
+ function(req) {
+ _checkState(req.status != 0, function() {
+ if(!callback) callback(false);
+
+ if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) {
+ if(callback) callback(false, req.status);
+ } else {
+ if(callback) {
+ var val = undefined;
+ if(req.responseText) {
+ if(req.getResponseHeader("Content-Type") === "application/json") {
+ val = JSON.parse(req.responseText);
+ } else {
+ val = req.responseText;
+ }
+ }
+ callback(val, req.status);
+ }
+ }
+ });
+ }, {"Content-Type":"application/json"});
+ }
+}
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -0,0 +1,40 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
+ this.newItems = [];
+}
+
+Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
+Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
+Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
+
+Zotero.Translate.ItemSaver.prototype = {
+ "saveItem":function(item) {
+ this.newItems.push(item);
+ Zotero.debug("Saving item");
+ Zotero.Connector.callMethod("saveItems", {"items":[item]});
+ }
+};
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js
@@ -0,0 +1,341 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+// Enumeration of types of translators
+const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
+
+/**
+ * Singleton to handle loading and caching of translators
+ * @namespace
+ */
+Zotero.Translators = new function() {
+ var _cache, _translators;
+ var _initialized = false;
+
+ /**
+ * Initializes translator cache, loading all relevant translators into memory
+ */
+ this.init = function() {
+ _cache = {"import":[], "export":[], "web":[], "search":[]};
+ _translators = {};
+ _initialized = true;
+
+ // Build caches
+ var translators = Zotero.Connector.data.translators;
+ for(var i=0; i<translators.length; i++) {
+ var translator = new Zotero.Translator(translators[i]);
+ _translators[translator.translatorID] = translator;
+
+ for(var type in TRANSLATOR_TYPES) {
+ if(translator.translatorType & TRANSLATOR_TYPES[type]) {
+ _cache[type].push(translator);
+ }
+ }
+ }
+
+ // Sort by priority
+ var cmp = function (a, b) {
+ if (a.priority > b.priority) {
+ return 1;
+ }
+ else if (a.priority < b.priority) {
+ return -1;
+ }
+ }
+ for(var type in _cache) {
+ _cache[type].sort(cmp);
+ }
+ }
+
+ /**
+ * Gets the translator that corresponds to a given ID
+ * @param {String} id The ID of the translator
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned.
+ */
+ this.get = function(id, callback) {
+ if(!_initialized) Zotero.Translators.init();
+ var translator = _translators[id];
+ if(!translator) {
+ callback(false);
+ return false;
+ }
+
+ // only need to get code if it is of some use
+ if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ translator.getCode(function() { callback(translator) });
+ } else {
+ callback(translator);
+ }
+ }
+
+ /**
+ * Gets all translators for a specific type of translation
+ * @param {String} type The type of translators to get (import, export, web, or search)
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned.
+ */
+ this.getAllForType = function(type, callback) {
+ if(!_initialized) Zotero.Translators.init()
+ var translators = _cache[type].slice(0);
+ new Zotero.Translators.CodeGetter(translators, callback, translators);
+ return true;
+ }
+
+ /**
+ * Gets web translators for a specific location
+ * @param {String} uri The URI for which to look for translators
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned. The callback is passed a set of functions for
+ * converting URLs from proper to proxied forms as the second
+ * argument.
+ */
+ this.getWebTranslatorsForLocation = function(uri, callback) {
+ if(!_initialized) Zotero.Translators.init();
+ var allTranslators = _cache["web"];
+ var potentialTranslators = [];
+ var searchURIs = [uri];
+
+ Zotero.debug("Translators: Looking for translators for "+uri);
+
+ // if there is a subdomain that is also a TLD, also test against URI with the domain
+ // dropped after the TLD
+ // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
+ var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
+ var properHosts = [];
+ var proxyHosts = [];
+ if(m) {
+ var hostnames = m[2].split(".");
+ for(var i=1; i<hostnames.length-2; i++) {
+ if(TLDS[hostnames[i].toLowerCase()]) {
+ var properHost = hostnames.slice(0, i+1).join(".");
+ searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
+ properHosts.push(properHost);
+ proxyHosts.push(hostnames.slice(i+1).join("."));
+ }
+ }
+ }
+
+ var converterFunctions = [];
+ for(var i=0; i<allTranslators.length; i++) {
+ for(var j=0; j<searchURIs.length; j++) {
+ // don't attempt to use translators with no target that can't be run in this browser
+ // since that would require transmitting every page to Zotero host
+ if(!allTranslators[i].webRegexp
+ && allTranslators[i].runMode !== Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ continue;
+ }
+
+ if(!allTranslators[i].webRegexp
+ || (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
+ // add translator to list
+ potentialTranslators.push(allTranslators[i]);
+
+ if(j === 0) {
+ converterFunctions.push(null);
+ } else if(Zotero.isFx) {
+ // in Firefox, push the converterFunction
+ converterFunctions.push(new function() {
+ var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
+ var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
+ return function(uri) { return uri.replace(re, "$&."+proxyHost) };
+ });
+ } else {
+ // in Chrome/Safari, the converterFunction needs to be passed as JSON, so
+ // just push an array with the proper and proxyHosts
+ converterFunctions.push([properHosts[j-1], proxyHosts[j-1]]);
+ }
+
+ // don't add translator more than once
+ break;
+ }
+ }
+ }
+
+ new Zotero.Translators.CodeGetter(potentialTranslators, callback,
+ [potentialTranslators, converterFunctions]);
+ return true;
+ }
+
+ /**
+ * Converts translators to JSON-serializable objects
+ */
+ this.serialize = function(translator) {
+ // handle translator arrays
+ if(translator.length !== undefined) {
+ var newTranslators = new Array(translator.length);
+ for(var i in translator) {
+ newTranslators[i] = Zotero.Translators.serialize(translator[i]);
+ }
+ return newTranslators;
+ }
+
+ // handle individual translator
+ var newTranslator = {};
+ for(var i in PRESERVE_PROPERTIES) {
+ var property = PRESERVE_PROPERTIES[i];
+ newTranslator[property] = translator[property];
+ }
+ return newTranslator;
+ }
+}
+
+/**
+ * A class to get the code for a set of translators at once
+ */
+Zotero.Translators.CodeGetter = function(translators, callback, callbackArgs) {
+ this._translators = translators;
+ this._callbackArgs = callbackArgs;
+ this._callback = callback;
+ this.getCodeFor(0);
+}
+
+Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
+ var me = this;
+ while(true) {
+ if(i === this._translators.length) {
+ // all done; run callback
+ this._callback(this._callbackArgs);
+ return;
+ }
+
+ if(this._translators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ // get next translator
+ this._translators[i].getCode(function() { me.getCodeFor(i+1) });
+ return;
+ }
+
+ // if we are not at end of list and there is no reason to retrieve the code, keep going
+ // through the list of potential translators
+ i++;
+ }
+}
+
+const TRANSLATOR_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
+ "priority", "browserSupport"];
+var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions",
+ "code", "runMode"]);
+/**
+ * @class Represents an individual translator
+ * @constructor
+ * @property {String} translatorID Unique GUID of the translator
+ * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
+ * @property {String} label Human-readable name of the translator
+ * @property {String} creator Author(s) of the translator
+ * @property {String} target Location that the translator processes
+ * @property {String} minVersion Minimum Zotero version
+ * @property {String} maxVersion Minimum Zotero version
+ * @property {Integer} priority Lower-priority translators will be selected first
+ * @property {String} browserSupport String indicating browser supported by the translator
+ * g = Gecko (Firefox)
+ * c = Google Chrome (WebKit & V8)
+ * s = Safari (WebKit & Nitro/Squirrelfish Extreme)
+ * i = Internet Explorer
+ * @property {Object} configOptions Configuration options for import/export
+ * @property {Object} displayOptions Display options for export
+ * @property {Boolean} inRepository Whether the translator may be found in the repository
+ * @property {String} lastUpdated SQL-style date and time of translator's last update
+ * @property {String} code The executable JavaScript for the translator
+ */
+Zotero.Translator = function(info) {
+ // make sure we have all the properties
+ for(var i in TRANSLATOR_PROPERTIES) {
+ var property = TRANSLATOR_PROPERTIES[i];
+ if(info[property] === undefined) {
+ this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label);
+ haveMetadata = false;
+ break;
+ } else {
+ this[property] = info[property];
+ }
+ }
+
+ if(info["browserSupport"].indexOf(Zotero.browser) !== -1) {
+ this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
+ } else {
+ this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
+ }
+
+ this._configOptions = info["configOptions"] ? info["configOptions"] : {};
+ this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
+
+ if(this.translatorType & TRANSLATOR_TYPES["import"]) {
+ // compile import regexp to match only file extension
+ this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
+ }
+
+ if(this.translatorType & TRANSLATOR_TYPES["web"]) {
+ // compile web regexp
+ this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
+ }
+
+ if(info.code) {
+ this.code = info.code;
+ }
+}
+
+Zotero.Translator.prototype.getCode = function(callback) {
+ if(this.code) {
+ callback(true);
+ } else {
+ var me = this;
+ Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID},
+ function(code) {
+ if(!code) {
+ callback(false);
+ } else {
+ me.code = code;
+ callback(true);
+ }
+ }
+ );
+ }
+}
+
+Zotero.Translator.prototype.__defineGetter__("displayOptions", function() {
+ return Zotero.Utilities.deepCopy(this._displayOptions);
+});
+Zotero.Translator.prototype.__defineGetter__("configOptions", function() {
+ return Zotero.Utilities.deepCopy(this._configOptions);
+});
+
+/**
+ * Log a translator-related error
+ * @param {String} message The error message
+ * @param {String} [type] The error type ("error", "warning", "exception", or "strict")
+ * @param {String} [line] The text of the line on which the error occurred
+ * @param {Integer} lineNumber
+ * @param {Integer} colNumber
+ */
+Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
+ Zotero.log(message, type ? type : "error", this.label);
+}
+
+Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
+Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
+Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/date.js b/chrome/content/zotero/xpcom/date.js
@@ -61,7 +61,7 @@ Zotero.Date = new function(){
.getService(Components.interfaces.nsIStringBundleService).createBundle(src, appLocale);
_months = {"short":[], "long":[]};
- for(let i=1; i<=12; i++) {
+ for(var i=1; i<=12; i++) {
_months.short.push(bundle.GetStringFromName("month."+i+".Mmm"));
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
}
diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js
@@ -129,7 +129,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
}
dataset.push(row);
}
- statement.reset();
+ statement.finalize();
return dataset.length ? dataset : false;
}
@@ -170,12 +170,12 @@ Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
// No rows
if (!statement.executeStep()) {
- statement.reset();
+ statement.finalize();
return false;
}
var value = this._getTypedValue(statement, 0);
- statement.reset();
+ statement.finalize();
return value;
}
@@ -202,7 +202,7 @@ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
while (statement.executeStep()) {
column.push(this._getTypedValue(statement, 0));
}
- statement.reset();
+ statement.finalize();
return column.length ? column : false;
}
return false;
@@ -630,7 +630,7 @@ Zotero.DBConnection.prototype.getColumns = function (table) {
for (var i=0,len=statement.columnCount; i<len; i++) {
cols.push(statement.getColumnName(i));
}
- statement.reset();
+ statement.finalize();
return cols;
}
catch (e) {
@@ -771,8 +771,11 @@ Zotero.DBConnection.prototype.checkException = function (e) {
Zotero.DBConnection.prototype.closeDatabase = function () {
- var db = this._getDBConnection();
- db.close();
+ if(this._connection) {
+ this.stopDummyStatement();
+ this._connection.close();
+ return true;
+ }
}
@@ -855,10 +858,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
var hadDummyStatement = !!this._dummyStatement;
try {
if (dbLockExclusive) {
- Zotero.DB.query("PRAGMA locking_mode=NORMAL");
+ this.query("PRAGMA locking_mode=NORMAL");
}
if (hadDummyStatement) {
- Zotero.DB.stopDummyStatement();
+ this.stopDummyStatement();
}
var store = Components.classes["@mozilla.org/storage/service;1"].
@@ -872,10 +875,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
}
finally {
if (dbLockExclusive) {
- Zotero.DB.query("PRAGMA locking_mode=EXCLUSIVE");
+ this.query("PRAGMA locking_mode=EXCLUSIVE");
}
if (hadDummyStatement) {
- Zotero.DB.startDummyStatement();
+ this.startDummyStatement();
}
}
@@ -1003,8 +1006,10 @@ Zotero.DBConnection.prototype.stopDummyStatement = function () {
}
Zotero.debug("Stopping dummy statement for '" + this._dbName + "'");
- this._dummyStatement.reset();
- this._dummyStatement = null;
+ this._dummyStatement.finalize();
+ this._dummyConnection.close();
+ delete this._dummyConnection;
+ delete this._dummyStatement;
}
diff --git a/chrome/content/zotero/xpcom/debug.js b/chrome/content/zotero/xpcom/debug.js
@@ -25,8 +25,8 @@
Zotero.Debug = new function () {
- this.__defineGetter__('storing', function () _store);
- this.__defineGetter__('enabled', function () _console || _store);
+ this.__defineGetter__('storing', function () { return _store; });
+ this.__defineGetter__('enabled', function () { return _console || _store; });
var _console;
var _store;
@@ -81,7 +81,12 @@ Zotero.Debug = new function () {
}
if (_console) {
- dump('zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message + "\n\n");
+ var output = 'zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message;
+ if(Zotero.isFx) {
+ dump(output+"\n\n");
+ } else {
+ console.log(output);
+ }
}
if (_store) {
if (Math.random() < 1/1000) {
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
@@ -31,7 +31,6 @@ const DATA_VERSION = 3;
// this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
-const INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
Zotero.Integration = new function() {
var _fifoFile = null;
@@ -39,8 +38,7 @@ Zotero.Integration = new function() {
var _osascriptFile;
var _inProgress = false;
var _integrationVersionsOK = null;
- var _pipeMode = false;
- var _winUser32;
+ var INTEGRATION_MIN_VERSIONS;
// these need to be global because of GC
var _timer;
@@ -52,29 +50,32 @@ Zotero.Integration = new function() {
* Initializes the pipe used for integration on non-Windows platforms.
*/
this.init = function() {
- // initialize SOAP server just to throw version errors
- Zotero.Integration.Compat.init();
+ if(Zotero.isMac || Zotero.isWin) { // on Mac or Windows, we don't have pipe issues
+ INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
+ } else { // on *NIX, there's no point in supporting 3.1b1
+ INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.5a1", "3.1b1"];
+ }
- // Windows uses a command line handler for integration. See
+ // We only use an integration pipe on OS X.
+ // On Linux, we use the alternative communication method in the OOo plug-in
+ // On Windows, we use a command line handler for integration. See
// components/zotero-integration-service.js for this implementation.
- if(Zotero.isWin) return;
+ if(!Zotero.isMac) return;
// Determine where to put the pipe
- if(Zotero.isMac) {
- // on OS X, first try /Users/Shared for those who can't put pipes in their home
- // directories
- _fifoFile = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- _fifoFile.initWithPath("/Users/Shared");
-
- if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
- var logname = Components.classes["@mozilla.org/process/environment;1"].
- getService(Components.interfaces.nsIEnvironment).
- get("LOGNAME");
- _fifoFile.append(".zoteroIntegrationPipe_"+logname);
- } else {
- _fifoFile = null;
- }
+ // on OS X, first try /Users/Shared for those who can't put pipes in their home
+ // directories
+ _fifoFile = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ _fifoFile.initWithPath("/Users/Shared");
+
+ if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
+ var logname = Components.classes["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment).
+ get("LOGNAME");
+ _fifoFile.append(".zoteroIntegrationPipe_"+logname);
+ } else {
+ _fifoFile = null;
}
if(!_fifoFile) {
@@ -85,8 +86,6 @@ Zotero.Integration = new function() {
_fifoFile.append(".zoteroIntegrationPipe");
}
- Zotero.debug("Initializing Zotero integration pipe at "+_fifoFile.path);
-
// destroy old pipe, if one exists
try {
if(_fifoFile.exists()) {
@@ -100,35 +99,27 @@ Zotero.Integration = new function() {
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
+ "for instructions on correcting this problem."
);
- if(Zotero.isMac) {
- // can attempt to delete on OS X
- try {
- var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
- var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
- if(!deletePipe) return;
- let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
- _executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
- if(_fifoFile.exists()) return;
- } catch(e) {
- Zotero.logError(e);
- return;
- }
+
+ // can attempt to delete on OS X
+ try {
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
+ if(!deletePipe) return;
+ let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
+ _executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
+ if(_fifoFile.exists()) return;
+ } catch(e) {
+ Zotero.logError(e);
+ return;
}
}
// try to initialize pipe
try {
- var pipeInitialized = _initializeIntegrationPipe();
+ Zotero.IPC.Pipe.initPipeListener(_fifoFile, _parseIntegrationPipeCommand);
} catch(e) {
- Components.utils.reportError(e);
- }
-
- if(pipeInitialized) {
- // if initialization succeeded, add an observer so that we don't hang shutdown
- var observerService = Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- observerService.addObserver({ observe: Zotero.Integration.destroy }, "quit-application", false);
+ Zotero.logError(e);
}
_updateTimer = Components.classes["@mozilla.org/timer;1"].
@@ -210,244 +201,12 @@ Zotero.Integration = new function() {
if(parts) {
var agent = parts[1].toString();
var cmd = parts[2].toString();
-
- // return if we were told to shutdown
- if(agent === "Zotero" && cmd === "shutdown") return;
-
- _initializePipeStreamPump();
-
var document = parts[3] ? parts[3].toString() : null;
Zotero.Integration.execCommand(agent, cmd, document);
} else {
- _initializePipeStreamPump();
Components.utils.reportError("Zotero: Invalid integration input received: "+string);
}
- } else {
- _initializePipeStreamPump();
- }
- }
-
- /**
- * Listens asynchronously for data on the integration pipe and reads it when available
- *
- * Used to read from the integration pipe on Fx 4.2
- */
- var _integrationPipeListenerFx42 = {
- "onStartRequest":function() {},
- "onStopRequest":function() {},
-
- "onDataAvailable":function(request, context, inputStream, offset, count) {
- // read from pipe
- var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
- .createInstance(Components.interfaces.nsIConverterInputStream);
- converterInputStream.init(inputStream, "UTF-8", 4096,
- Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- var out = {};
- converterInputStream.readString(count, out);
- inputStream.close();
-
- _parseIntegrationPipeCommand(out.value);
- }};
-
- /**
- * Polling mechanism for file
- */
- var _integrationPipeObserverFx36 = {"notify":function() {
- if(_fifoFile.fileSize === 0) return;
-
- // read from pipe (file, actually)
- var string = Zotero.File.getContents(_fifoFile);
-
- // clear file
- var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
- createInstance(Components.interfaces.nsIFileOutputStream);
- foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
- foStream.close();
-
- // run command
- _parseIntegrationPipeCommand(string);
- }};
-
- /**
- * Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
- */
- function _initializePipeStreamPump() {
- // Fx >4 supports deferred open; no need to use sh
- var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
- createInstance(Components.interfaces.nsIFileInputStream);
- fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
- // 16 = open as deferred so that we don't block on open
- fifoStream.init(_fifoFile, -1, 0, 16);
-
- var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
- createInstance(Components.interfaces.nsIInputStreamPump);
- pump.init(fifoStream, -1, -1, 4096, 1, true);
- pump.asyncRead(_integrationPipeListenerFx42, null);
- }
-
- /**
- * Initializes the Zotero Integration Pipe
- */
- function _initializeIntegrationPipe() {
- var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
- .getService(Components.interfaces.nsIVersionComparator);
- var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
- getService(Components.interfaces.nsIXULAppInfo);
- if(Zotero.isFx4) {
- if(verComp.compare("2.0b9pre", appInfo.platformVersion) > 0) {
- Components.utils.reportError("Zotero word processor integration requires "+
- "Firefox 4.0b9 or later. Please update to the latest Firefox 4.0 beta.");
- return;
- } else if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) {
- _pipeMode = "deferredOpen";
- } else {
- _pipeMode = "fx4thread";
- }
- } else {
- if(Zotero.isMac) {
- _pipeMode = "poll";
- } else {
- _pipeMode = "fx36thread";
- }
- }
-
- Zotero.debug("Using integration pipe mode "+_pipeMode);
-
- if(_pipeMode === "poll") {
- // create empty file
- var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
- createInstance(Components.interfaces.nsIFileOutputStream);
- foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
- foStream.close();
-
- // no deferred open capability, so we need to poll
- // has to be global so that we don't get garbage collected
- _timer = Components.classes["@mozilla.org/timer;1"].
- createInstance(Components.interfaces.nsITimer);
- _timer.initWithCallback(_integrationPipeObserverFx36, 1000,
- Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
- } else {
- // make a new pipe
- var mkfifo = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- mkfifo.initWithPath("/usr/bin/mkfifo");
- if(!mkfifo.exists()) mkfifo.initWithPath("/bin/mkfifo");
- if(!mkfifo.exists()) mkfifo.initWithPath("/usr/local/bin/mkfifo");
-
- if(mkfifo.exists()) {
- // create named pipe
- var proc = Components.classes["@mozilla.org/process/util;1"].
- createInstance(Components.interfaces.nsIProcess);
- proc.init(mkfifo);
- proc.run(true, [_fifoFile.path], 1);
-
- if(_fifoFile.exists()) {
- if(_pipeMode === "deferredOpen") {
- _initializePipeStreamPump();
- } else if(_pipeMode === "fx36thread") {
- var main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
- var background = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);
-
- function mainThread(agent, cmd, doc) {
- this.agent = agent;
- this.cmd = cmd;
- this.document = doc;
- }
- mainThread.prototype.run = function() {
- Zotero.Integration.execCommand(this.agent, this.cmd, this.document);
- }
-
- function fifoThread() {}
- fifoThread.prototype.run = function() {
- var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
- createInstance(Components.interfaces.nsIFileInputStream);
- var line = {};
- while(true) {
- fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
- fifoStream.init(_fifoFile, -1, 0, 0);
- fifoStream.QueryInterface(Components.interfaces.nsILineInputStream);
- fifoStream.readLine(line);
- fifoStream.close();
-
- var parts = line.value.split(" ");
- var agent = parts[0];
- var cmd = parts[1];
- var document = parts.length >= 3 ? line.value.substr(agent.length+cmd.length+2) : null;
- if(agent == "Zotero" && cmd == "shutdown") return;
- main.dispatch(new mainThread(agent, cmd, document), background.DISPATCH_NORMAL);
- }
- }
-
- fifoThread.prototype.QueryInterface = mainThread.prototype.QueryInterface = function(iid) {
- if (iid.equals(Components.interfaces.nsIRunnable) ||
- iid.equals(Components.interfaces.nsISupports)) return this;
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
-
- background.dispatch(new fifoThread(), background.DISPATCH_NORMAL);
- } else if(_pipeMode === "fx4thread") {
- Components.utils.import("resource://gre/modules/ctypes.jsm");
-
- // get possible names for libc
- if(Zotero.isMac) {
- var possibleLibcs = ["/usr/lib/libc.dylib"];
- } else {
- var possibleLibcs = [
- "libc.so.6",
- "libc.so.6.1",
- "libc.so"
- ];
- }
-
- // try all possibilities
- while(possibleLibcs.length) {
- var libc = possibleLibcs.shift();
- try {
- var lib = ctypes.open(libc);
- break;
- } catch(e) {}
- }
-
- // throw appropriate error on failure
- if(!lib) {
- throw "libc could not be loaded. Please post on the Zotero Forums so we can add "+
- "support for your operating system.";
- }
-
- // int mkfifo(const char *path, mode_t mode);
- var mkfifo = lib.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
-
- // make pipe
- var ret = mkfifo(_fifoFile.path, 0600);
- if(!_fifoFile.exists()) return false;
- lib.close();
-
- // set up worker
- var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
- .createInstance(Components.interfaces.nsIWorkerFactory)
- .newChromeWorker("chrome://zotero/content/xpcom/integration_worker.js");
- worker.onmessage = function(event) {
- if(event.data[0] == "Exception") {
- throw event.data[1];
- } else if(event.data[0] == "Debug") {
- Zotero.debug(event.data[1]);
- } else {
- Zotero.Integration.execCommand(event.data[0], event.data[1], event.data[2]);
- }
- }
- worker.postMessage({"path":_fifoFile.path, "libc":libc});
- }
- } else {
- Components.utils.reportError("Zotero: mkfifo failed -- not initializing integration pipe");
- return false;
- }
- } else {
- Components.utils.reportError("Zotero: mkfifo or sh not found -- not initializing integration pipe");
- return false;
- }
}
-
- return true;
}
/**
@@ -547,22 +306,6 @@ Zotero.Integration = new function() {
}
/**
- * Destroys the integration pipe.
- */
- this.destroy = function() {
- if(_pipeMode !== "poll") {
- // send shutdown message to fifo thread
- var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
- getService(Components.interfaces.nsIFileOutputStream);
- oStream.init(_fifoFile, 0x02 | 0x10, 0, 0);
- var cmd = "Zotero shutdown\n";
- oStream.write(cmd, cmd.length);
- oStream.close();
- }
- _fifoFile.remove(false);
- }
-
- /**
* Activates Firefox
*/
this.activate = function() {
diff --git a/chrome/content/zotero/xpcom/integration_worker.js b/chrome/content/zotero/xpcom/integration_worker.js
@@ -1,72 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-onmessage = function(event) {
- var path = event.data.path;
-
- // ctypes declarations follow
- var lib = ctypes.open(event.data.libc);
-
- // int open(const char *path, int oflag, ...);
- var open = lib.declare("open", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int);
-
- // ssize_t read(int fildes, void *buf, size_t nbyte);
- var read = lib.declare("read", ctypes.default_abi, ctypes.ssize_t, ctypes.int,
- ctypes.char.ptr, ctypes.size_t);
-
- // int close(int fildes);
- var close = lib.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
-
- // define buffer for reading from fifo
- const BUFFER_SIZE = 4096;
-
- while(true) {
- var buf = ctypes.char.array(BUFFER_SIZE)("");
-
- // open fifo (this will block until something writes to it)
- var fd = open(path, 0);
-
- // read from fifo and close it
- read(fd, buf, BUFFER_SIZE-1);
- close(fd);
-
- // extract message
- var string = buf.readString();
- var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
- if(!parts) {
- postMessage(["Exception", "Integration Worker: Invalid input received: "+string]);
- continue;
- }
- var agent = parts[1].toString();
- var cmd = parts[2].toString();
- var document = parts[3] ? parts[3] : null;
- if(agent == "Zotero" && cmd == "shutdown") {
- postMessage(["Debug", "Integration Worker: Shutting down"]);
- lib.close();
- return;
- }
- postMessage([agent, cmd, document]);
- }
-};
-\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/ipc.js b/chrome/content/zotero/xpcom/ipc.js
@@ -0,0 +1,484 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.IPC = new function() {
+ var _libc, _libcPath, _instancePipe, _user32;
+
+ /**
+ * Initialize pipe for communication with connector
+ */
+ this.init = function() {
+ if(!Zotero.isWin && (Zotero.isFx4 || Zotero.isMac)) { // no pipe support on Fx 3.6
+ _instancePipe = _getPipeDirectory();
+ if(!_instancePipe.exists()) {
+ _instancePipe.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+ }
+ _instancePipe.append(Zotero.instanceID);
+
+ Zotero.IPC.Pipe.initPipeListener(_instancePipe, this.parsePipeInput);
+ }
+ }
+
+ /**
+ * Parses input received via instance pipe
+ */
+ this.parsePipeInput = function(msg) {
+ // remove a newline if there is one
+ if(msg[msg.length-1] === "\n") msg = msg.substr(0, msg.length-1);
+
+ Zotero.debug('IPC: Received "'+msg+'"');
+
+ if(msg === "releaseLock" && !Zotero.isConnector) {
+ switchConnectorMode(true);
+ } else if(msg === "lockReleased") {
+ Zotero.onDBLockReleased();
+ } else if(msg === "initComplete") {
+ Zotero.onInitComplete();
+ }
+ }
+
+ /**
+ * Broadcast a message to all other Zotero instances
+ */
+ this.broadcast = function(msg) {
+ if(Zotero.isWin) { // communicate via WM_COPYDATA method
+ // there is no ctypes struct support in Fx 3.6
+ // while we could mimic it, it's easier just to require users to upgrade if they
+ // want connector sharing
+ if(!Zotero.isFx4) return false;
+
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+ // communicate via message window
+ var user32 = ctypes.open("user32.dll");
+
+ /* http://msdn.microsoft.com/en-us/library/ms633499%28v=vs.85%29.aspx
+ * HWND WINAPI FindWindow(
+ * __in_opt LPCTSTR lpClassName,
+ * __in_opt LPCTSTR lpWindowName
+ * );
+ */
+ var FindWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.int32_t,
+ ctypes.jschar.ptr, ctypes.jschar.ptr);
+
+ /* http://msdn.microsoft.com/en-us/library/ms633539%28v=vs.85%29.aspx
+ * BOOL WINAPI SetForegroundWindow(
+ * __in HWND hWnd
+ * );
+ */
+ var SetForegroundWindow = user32.declare("SetForegroundWindow", ctypes.winapi_abi,
+ ctypes.bool, ctypes.int32_t);
+
+ /*
+ * LRESULT WINAPI SendMessage(
+ * __in HWND hWnd,
+ * __in UINT Msg,
+ * __in WPARAM wParam,
+ * __in LPARAM lParam
+ * );
+ */
+ var SendMessage = user32.declare("SendMessageW", ctypes.winapi_abi, ctypes.uintptr_t,
+ ctypes.int32_t, ctypes.unsigned_int, ctypes.voidptr_t, ctypes.voidptr_t);
+
+ /* http://msdn.microsoft.com/en-us/library/ms649010%28v=vs.85%29.aspx
+ * typedef struct tagCOPYDATASTRUCT {
+ * ULONG_PTR dwData;
+ * DWORD cbData;
+ * PVOID lpData;
+ * } COPYDATASTRUCT, *PCOPYDATASTRUCT;
+ */
+ var COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT", [
+ {"dwData":ctypes.voidptr_t},
+ {"cbData":ctypes.uint32_t},
+ {"lpData":ctypes.voidptr_t}
+ ]);
+
+ const appNames = ["Firefox", "Zotero", "Nightly", "Aurora", "Minefield"];
+ for each(var appName in appNames) {
+ // don't send messages to ourself
+ if(appName === Zotero.appName) continue;
+
+ var thWnd = FindWindow(appName+"MessageWindow", null);
+ if(thWnd) {
+ Zotero.debug('IPC: Broadcasting "'+msg+'" to window "'+appName+'MessageWindow"');
+
+ // allocate message
+ var data = ctypes.char.array()('firefox.exe -ZoteroIPC "'+msg.replace('"', '""', "g")+'"\x00C:\\');
+ var dataSize = data.length*data.constructor.size;
+
+ // create new COPYDATASTRUCT
+ var cds = new COPYDATASTRUCT();
+ cds.dwData = null;
+ cds.cbData = dataSize;
+ cds.lpData = data.address();
+
+ // send COPYDATASTRUCT
+ var success = SendMessage(thWnd, 0x004A /** WM_COPYDATA **/, null, cds.address());
+
+ user32.close();
+ return !!success;
+ }
+ }
+
+ user32.close();
+ return false;
+ } else { // communicate via pipes
+
+ // look for other Zotero instances
+ var pipes = [];
+ var pipeDir = _getPipeDirectory();
+ if(pipeDir.exists()) {
+ var dirEntries = pipeDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ var pipe = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
+ if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) {
+ pipes.push(pipe);
+ }
+ }
+ }
+
+ if(!pipes.length) return false;
+
+ // safely write to instance pipes
+ var lib = this.getLibc();
+ if(!lib) return false;
+
+ // int open(const char *path, int oflag);
+ if(Zotero.isFx36) {
+ var open = lib.declare("open", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.int32_t);
+ } else {
+ var open = lib.declare("open", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int);
+ }
+ // ssize_t write(int fildes, const void *buf, size_t nbyte);
+ if(Zotero.isFx36) {
+ } else {
+ var write = lib.declare("write", ctypes.default_abi, ctypes.ssize_t, ctypes.int, ctypes.char.ptr, ctypes.size_t);
+ }
+ // int close(int filedes);
+ if(Zotero.isFx36) {
+ } else {
+ var close = lib.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
+ }
+
+ var success = false;
+ for each(var pipe in pipes) {
+ var fd = open(pipe.path, 0x0004 | 0x0001); // O_NONBLOCK | O_WRONLY
+ if(fd !== -1) {
+ Zotero.debug('IPC: Broadcasting "'+msg+'" to instance '+pipe.leafName);
+ success = true;
+ write(fd, msg+"\n", msg.length);
+ close(fd);
+ } else {
+ try {
+ pipe.remove(true);
+ } catch(e) {};
+ }
+ }
+
+ return success;
+ }
+ }
+
+ /**
+ * Get directory containing Zotero pipes
+ */
+ function _getPipeDirectory() {
+ var dir = Zotero.getZoteroDirectory();
+ dir.append("pipes");
+ return dir;
+ }
+
+ /**
+ * Gets the path to libc as a string
+ */
+ this.getLibcPath = function() {
+ if(_libcPath) return _libcPath;
+
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+ // get possible names for libc
+ if(Zotero.isMac) {
+ var possibleLibcs = ["/usr/lib/libc.dylib"];
+ } else {
+ var possibleLibcs = [
+ "libc.so.6",
+ "libc.so.6.1",
+ "libc.so"
+ ];
+ }
+
+ // try all possibilities
+ while(possibleLibcs.length) {
+ var libPath = possibleLibcs.shift();
+ try {
+ var lib = ctypes.open(libPath);
+ break;
+ } catch(e) {}
+ }
+
+ // throw appropriate error on failure
+ if(!lib) {
+ Components.utils.reportError("Zotero: libc could not be loaded. Word processor integration "+
+ "and other functionality will not be available. Please post on the Zotero Forums so we "+
+ "can add support for your operating system.");
+ return;
+ }
+
+ _libc = lib;
+ _libcPath = libPath;
+ return libPath;
+ }
+
+ /**
+ * Gets standard C library via ctypes
+ */
+ this.getLibc = function() {
+ if(!_libc) this.getLibcPath();
+ return _libc;
+ }
+}
+
+/**
+ * Methods for reading from and writing to a pipe
+ */
+Zotero.IPC.Pipe = new function() {
+ var _mkfifo, _pipeClass;
+
+ /**
+ * Creates and listens on a pipe
+ *
+ * @param {nsIFile} file The location where the pipe should be created
+ * @param {Function} callback A function to be passed any data recevied on the pipe
+ */
+ this.initPipeListener = function(file, callback) {
+ Zotero.debug("IPC: Initializing pipe at "+file.path);
+
+ // determine type of pipe
+ if(!_pipeClass) {
+ var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Components.interfaces.nsIVersionComparator);
+ var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
+ getService(Components.interfaces.nsIXULAppInfo);
+ if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) { // Gecko 5
+ _pipeClass = Zotero.IPC.Pipe.DeferredOpen;
+ } else if(verComp.compare("2.0b9pre", appInfo.platformVersion) <= 0) { // Gecko 2.0b9+
+ _pipeClass = Zotero.IPC.Pipe.WorkerThread;
+ } else { // Gecko 1.9.2
+ _pipeClass = Zotero.IPC.Pipe.Poll;
+ }
+ }
+
+ // make new pipe
+ new _pipeClass(file, callback);
+ }
+
+ /**
+ * Makes a fifo
+ * @param {nsIFile} file Location to create the fifo
+ */
+ this.mkfifo = function(file) {
+ // int mkfifo(const char *path, mode_t mode);
+ if(!_mkfifo) {
+ var libc = Zotero.IPC.getLibc();
+ if(!libc) return false;
+ if(Zotero.isFx36) {
+ _mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.uint32_t);
+ } else {
+ _mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
+ }
+ }
+
+ // make pipe
+ var ret = _mkfifo(file.path, 0600);
+ return file.exists();
+ }
+
+ /**
+ * Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then
+ * deletes it
+ */
+ this.writeShutdownMessage = function(file) {
+ var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+ getService(Components.interfaces.nsIFileOutputStream);
+ oStream.init(file, 0x02 | 0x10, 0, 0);
+ const cmd = "Zotero shutdown\n";
+ oStream.write(cmd, cmd.length);
+ oStream.close();
+ file.remove(false);
+ Zotero.debug("IPC: Closing pipe "+file.path);
+ }
+}
+
+/**
+ * Listens asynchronously for data on the integration pipe and reads it when available
+ *
+ * Used to read from pipe on Gecko 5+
+ */
+Zotero.IPC.Pipe.DeferredOpen = function(file, callback) {
+ this._file = file;
+ this._callback = callback;
+
+ if(!Zotero.IPC.Pipe.mkfifo(file)) return;
+
+ this._initPump();
+
+ // add shutdown listener
+ Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
+}
+
+Zotero.IPC.Pipe.DeferredOpen.prototype = {
+ "onStartRequest":function() {},
+ "onStopRequest":function() {},
+ "onDataAvailable":function(request, context, inputStream, offset, count) {
+ // read from pipe
+ var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterInputStream);
+ converterInputStream.init(inputStream, "UTF-8", 4096,
+ Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ var out = {};
+ converterInputStream.readString(count, out);
+ inputStream.close();
+
+ if(out.value === "Zotero shutdown\n") return
+
+ this._initPump();
+ this._callback(out.value);
+ },
+
+ /**
+ * Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
+ *
+ * Used after reading from file on Gecko 5+
+ */
+ "_initPump":function() {
+ var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
+ // 16 = open as deferred so that we don't block on open
+ fifoStream.init(this._file, -1, 0, 16);
+
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Components.interfaces.nsIInputStreamPump);
+ pump.init(fifoStream, -1, -1, 4096, 1, true);
+ pump.asyncRead(this, null);
+ }
+};
+
+/**
+ * Listens synchronously for data on the integration pipe on a separate JS thread and reads it
+ * when available
+ *
+ * Used to read from pipe on Gecko 2
+ */
+Zotero.IPC.Pipe.WorkerThread = function(file, callback) {
+ this._callback = callback;
+
+ if(!Zotero.IPC.Pipe.mkfifo(file)) return;
+
+ // set up worker
+ var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
+ .createInstance(Components.interfaces.nsIWorkerFactory)
+ .newChromeWorker("chrome://zotero/content/xpcom/pipe_worker.js");
+ worker.onmessage = this.onmessage.bind(this);
+ worker.postMessage({"path":file.path, "libc":Zotero.IPC.getLibcPath()});
+
+ // add shutdown listener
+ Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
+}
+
+Zotero.IPC.Pipe.WorkerThread.prototype = {
+ /**
+ * onmessage call for worker thread, to get data from it
+ */
+ "onmessage":function(event) {
+ if(event.data[0] === "Exception") {
+ throw event.data[1];
+ } else if(event.data[0] === "Debug") {
+ Zotero.debug(event.data[1]);
+ } else if(event.data[0] === "Read") {
+ this._callback(event.data[1]);
+ }
+ }
+}
+
+/**
+ * Polling mechanism for file
+ *
+ * Used to read from integration "pipe" on Gecko 1.9.2/Firefox 3.6
+ */
+Zotero.IPC.Pipe.Poll = function(file, callback) {
+ this._file = file;
+ this._callback = callback;
+
+ // create empty file
+ this._clearFile();
+
+ // no deferred open capability, so we need to poll
+ this._timer = Components.classes["@mozilla.org/timer;1"].
+ createInstance(Components.interfaces.nsITimer);
+ this._timer.initWithCallback(this, 1000,
+ Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+
+ // this has to be in global scope so we don't get garbage collected
+ Zotero.IPC.Pipe.Poll._activePipes.push(this);
+
+ // add shutdown listener
+ Zotero.addShutdownListener(this);
+}
+Zotero.IPC.Pipe.Poll._activePipes = [];
+
+Zotero.IPC.Pipe.Poll.prototype = {
+ /**
+ * Called every second to check if there is new data to be read
+ */
+ "notify":function() {
+ if(this._file.fileSize === 0) return;
+
+ // read from pipe (file, actually)
+ var string = Zotero.File.getContents(this._file);
+ this._clearFile();
+
+ // run command
+ this._callback(string);
+ },
+
+ /**
+ * Called on quit to remove the file
+ */
+ "observe":function() {
+ this._file.remove();
+ },
+
+ /**
+ * Clears the old contents of the fifo file
+ */
+ "_clearFile":function() {
+ // clear file
+ var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Components.interfaces.nsIFileOutputStream);
+ foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
+ foStream.close();
+ }
+};
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/mimeTypeHandler.js b/chrome/content/zotero/xpcom/mimeTypeHandler.js
@@ -180,6 +180,8 @@ Zotero.MIMETypeHandler = new function () {
*/
var _Observer = new function() {
this.observe = function(channel) {
+ if(Zotero.isConnector) return;
+
channel.QueryInterface(Components.interfaces.nsIRequest);
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
@@ -222,6 +224,7 @@ Zotero.MIMETypeHandler = new function () {
* Called to see if we can handle a content type
*/
this.canHandleContent = this.isPreferred = function(contentType, isContentPreferred, desiredContentType) {
+ if(Zotero.isConnector) return false;
return !!_typeHandlers[contentType.toLowerCase()];
}
diff --git a/chrome/content/zotero/xpcom/pipe_worker.js b/chrome/content/zotero/xpcom/pipe_worker.js
@@ -0,0 +1,65 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+onmessage = function(event) {
+ var path = event.data.path;
+
+ // ctypes declarations follow
+ var lib = ctypes.open(event.data.libc);
+
+ // int open(const char *path, int oflag, ...);
+ var open = lib.declare("open", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int);
+
+ // ssize_t read(int fildes, void *buf, size_t nbyte);
+ var read = lib.declare("read", ctypes.default_abi, ctypes.ssize_t, ctypes.int,
+ ctypes.char.ptr, ctypes.size_t);
+
+ // int close(int fildes);
+ var close = lib.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
+
+ // define buffer for reading from fifo
+ const BUFFER_SIZE = 4096;
+
+ while(true) {
+ var buf = ctypes.char.array(BUFFER_SIZE)("");
+
+ // open fifo (this will block until something writes to it)
+ var fd = open(path, 0);
+
+ // read from fifo and close it
+ read(fd, buf, BUFFER_SIZE-1);
+ close(fd);
+
+ // extract message
+ var string = buf.readString();
+ if(string === "Zotero shutdown\n") {
+ postMessage(["Debug", "IPC: Worker closing "+event.data.path]);
+ lib.close();
+ return;
+ }
+
+ postMessage(["Read", string]);
+ }
+};
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/proxy.js b/chrome/content/zotero/xpcom/proxy.js
@@ -480,8 +480,6 @@ const Zotero_Proxy_schemeParameterRegexps = {
"%a":/([^%])%a/
};
-const Zotero_Proxy_metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g;
-
/**
* Compiles the regular expression against which we match URLs to determine if this proxy is in use
* and saves it in this.regexp
@@ -514,7 +512,7 @@ Zotero.Proxy.prototype.compileRegexp = function() {
})
// now replace with regexp fragment in reverse order
- var re = "^"+this.scheme.replace(Zotero_Proxy_metaRegexp, "\\$&")+"$";
+ var re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
for(var i=this.parameters.length-1; i>=0; i--) {
var param = this.parameters[i];
re = re.replace(Zotero_Proxy_schemeParameterRegexps[param], "$1"+parametersToCheck[param]);
@@ -571,7 +569,7 @@ Zotero.Proxy.prototype.save = function(transparent) {
if(hasErrors) throw "Zotero.Proxy: could not be saved because it is invalid: error "+hasErrors[0];
// we never save any changes to non-persisting proxies, so this works
- var newProxy = !!this.proxyID;
+ var newProxy = !this.proxyID;
this.autoAssociate = this.multiHost && this.autoAssociate;
this.compileRegexp();
diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js
@@ -0,0 +1,379 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Server = new function() {
+ var _onlineObserverRegistered;
+ var responseCodes = {
+ 200:"OK",
+ 201:"Created",
+ 300:"Multiple Choices",
+ 400:"Bad Request",
+ 404:"Not Found",
+ 500:"Internal Server Error",
+ 501:"Method Not Implemented"
+ };
+
+ /**
+ * initializes a very rudimentary web server
+ */
+ this.init = function() {
+ if (Zotero.HTTP.browserIsOffline()) {
+ Zotero.debug('Browser is offline -- not initializing HTTP server');
+ _registerOnlineObserver();
+ return;
+ }
+
+ // start listening on socket
+ var serv = Components.classes["@mozilla.org/network/server-socket;1"]
+ .createInstance(Components.interfaces.nsIServerSocket);
+ try {
+ // bind to a random port on loopback only
+ serv.init(Zotero.Prefs.get('httpServer.port'), true, -1);
+ serv.asyncListen(Zotero.Server.SocketListener);
+
+ Zotero.debug("HTTP server listening on 127.0.0.1:"+serv.port);
+ } catch(e) {
+ Zotero.debug("Not initializing HTTP server");
+ }
+
+ _registerOnlineObserver()
+ }
+
+ /**
+ * generates the response to an HTTP request
+ */
+ this.generateResponse = function (status, contentType, body) {
+ var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
+ response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
+ response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
+
+ if(body) {
+ if(contentType) {
+ response += "Content-Type: "+contentType+"\r\n";
+ }
+ response += "\r\n"+body;
+ } else {
+ response += "Content-Length: 0\r\n\r\n";
+ }
+
+ return response;
+ }
+
+ function _registerOnlineObserver() {
+ if (_onlineObserverRegistered) {
+ return;
+ }
+
+ // Observer to enable the integration when we go online
+ var observer = {
+ observe: function(subject, topic, data) {
+ if (data == 'online') {
+ Zotero.Server.init();
+ }
+ }
+ };
+
+ var observerService =
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ observerService.addObserver(observer, "network:offline-status-changed", false);
+
+ _onlineObserverRegistered = true;
+ }
+}
+
+Zotero.Server.SocketListener = new function() {
+ this.onSocketAccepted = onSocketAccepted;
+ this.onStopListening = onStopListening;
+
+ /*
+ * called when a socket is opened
+ */
+ function onSocketAccepted(socket, transport) {
+ // get an input stream
+ var iStream = transport.openInputStream(0, 0, 0);
+ var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
+
+ var dataListener = new Zotero.Server.DataListener(iStream, oStream);
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Components.interfaces.nsIInputStreamPump);
+ pump.init(iStream, -1, -1, 0, 0, false);
+ pump.asyncRead(dataListener, null);
+ }
+
+ function onStopListening(serverSocket, status) {
+ Zotero.debug("HTTP server going offline");
+ }
+}
+
+/*
+ * handles the actual acquisition of data
+ */
+Zotero.Server.DataListener = function(iStream, oStream) {
+ this.header = "";
+ this.headerFinished = false;
+
+ this.body = "";
+ this.bodyLength = 0;
+
+ this.iStream = iStream;
+ this.oStream = oStream;
+ this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ this.sStream.init(iStream);
+
+ this.foundReturn = false;
+}
+
+/*
+ * called when a request begins (although the request should have begun before
+ * the DataListener was generated)
+ */
+Zotero.Server.DataListener.prototype.onStartRequest = function(request, context) {}
+
+/*
+ * called when a request stops
+ */
+Zotero.Server.DataListener.prototype.onStopRequest = function(request, context, status) {
+ this.iStream.close();
+ this.oStream.close();
+}
+
+/*
+ * called when new data is available
+ */
+Zotero.Server.DataListener.prototype.onDataAvailable = function(request, context,
+ inputStream, offset, count) {
+ var readData = this.sStream.read(count);
+
+ if(this.headerFinished) { // reading body
+ this.body += readData;
+ // check to see if data is done
+ this._bodyData();
+ } else { // reading header
+ // see if there's a magic double return
+ var lineBreakIndex = readData.indexOf("\r\n\r\n");
+ if(lineBreakIndex != -1) {
+ if(lineBreakIndex != 0) {
+ this.header += readData.substr(0, lineBreakIndex+4);
+ this.body = readData.substr(lineBreakIndex+4);
+ }
+
+ this._headerFinished();
+ return;
+ }
+ var lineBreakIndex = readData.indexOf("\n\n");
+ if(lineBreakIndex != -1) {
+ if(lineBreakIndex != 0) {
+ this.header += readData.substr(0, lineBreakIndex+2);
+ this.body = readData.substr(lineBreakIndex+2);
+ }
+
+ this._headerFinished();
+ return;
+ }
+ if(this.header && this.header[this.header.length-1] == "\n" &&
+ (readData[0] == "\n" || readData[0] == "\r")) {
+ if(readData.length > 1 && readData[1] == "\n") {
+ this.header += readData.substr(0, 2);
+ this.body = readData.substr(2);
+ } else {
+ this.header += readData[0];
+ this.body = readData.substr(1);
+ }
+
+ this._headerFinished();
+ return;
+ }
+ this.header += readData;
+ }
+}
+
+/*
+ * processes an HTTP header and decides what to do
+ */
+Zotero.Server.DataListener.prototype._headerFinished = function() {
+ this.headerFinished = true;
+
+ Zotero.debug(this.header);
+
+ const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
+ const contentTypeRe = /[\r\n]Content-Type: +([^ \r\n]+)/i;
+
+ // get first line of request
+ var method = methodRe.exec(this.header);
+ // get content-type
+ var contentType = contentTypeRe.exec(this.header);
+ if(contentType) {
+ var splitContentType = contentType[1].split(/\s*;/);
+ this.contentType = splitContentType[0];
+ }
+
+ if(!method) {
+ this._requestFinished(Zotero.Server.generateResponse(400));
+ return;
+ }
+ if(!Zotero.Server.Endpoints[method[2]]) {
+ this._requestFinished(Zotero.Server.generateResponse(404));
+ return;
+ }
+ this.endpoint = Zotero.Server.Endpoints[method[2]];
+
+ if(method[1] == "HEAD" || method[1] == "OPTIONS") {
+ this._requestFinished(Zotero.Server.generateResponse(200));
+ } else if(method[1] == "GET") {
+ this._requestFinished(this._processEndpoint("GET", method[3]));
+ } else if(method[1] == "POST") {
+ const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i;
+
+ // parse content length
+ var m = contentLengthRe.exec(this.header);
+ if(!m) {
+ this._requestFinished(Zotero.Server.generateResponse(400));
+ return;
+ }
+
+ this.bodyLength = parseInt(m[1]);
+ this._bodyData();
+ } else {
+ this._requestFinished(Zotero.Server.generateResponse(501));
+ return;
+ }
+}
+
+/*
+ * checks to see if Content-Length bytes of body have been read and, if so, processes the body
+ */
+Zotero.Server.DataListener.prototype._bodyData = function() {
+ if(this.body.length >= this.bodyLength) {
+ // convert to UTF-8
+ var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Components.interfaces.nsIStringInputStream);
+ dataStream.setData(this.body, this.bodyLength);
+
+ var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterInputStream);
+ utf8Stream.init(dataStream, "UTF-8", 4096, "?");
+
+ this.body = "";
+ var string = {};
+ while(utf8Stream.readString(this.bodyLength, string)) {
+ this.body += string.value;
+ }
+
+ // handle envelope
+ this._processEndpoint("POST", this.body);
+ }
+}
+
+/**
+ * Generates a response based on calling the function associated with the endpoint
+ */
+Zotero.Server.DataListener.prototype._processEndpoint = function(method, postData) {
+ try {
+ var endpoint = new this.endpoint;
+
+ // check that endpoint supports method
+ if(endpoint.supportedMethods.indexOf(method) === -1) {
+ this._requestFinished(Zotero.Server.generateResponse(400));
+ return;
+ }
+
+ var decodedData = null;
+ if(postData && this.contentType) {
+ // check that endpoint supports contentType
+ if(endpoint.supportedDataTypes.indexOf(this.contentType) === -1) {
+ this._requestFinished(Zotero.Server.generateResponse(400));
+ return;
+ }
+
+ // decode JSON or urlencoded post data, and pass through anything else
+ if(this.contentType === "application/json") {
+ decodedData = JSON.parse(postData);
+ } else if(this.contentType === "application/x-www-urlencoded") {
+ var splitData = postData.split("&");
+ decodedData = {};
+ for each(var variable in splitData) {
+ var splitIndex = variable.indexOf("=");
+ data[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
+ }
+ } else {
+ decodedData = postData;
+ }
+ }
+
+ // set up response callback
+ var me = this;
+ var sendResponseCallback = function(code, contentType, arg) {
+ me._requestFinished(Zotero.Server.generateResponse(code, contentType, arg));
+ }
+
+ // pass to endpoint
+ endpoint.init(decodedData, sendResponseCallback);
+ } catch(e) {
+ Zotero.debug(e);
+ this._requestFinished(Zotero.Server.generateResponse(500));
+ throw e;
+ }
+}
+
+/*
+ * returns HTTP data from a request
+ */
+Zotero.Server.DataListener.prototype._requestFinished = function(response) {
+ // close input stream
+ this.iStream.close();
+
+ // open UTF-8 converter for output stream
+ var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterOutputStream);
+
+ // write
+ try {
+ intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
+
+ // write response
+ Zotero.debug(response);
+ intlStream.writeString(response);
+ } finally {
+ intlStream.close();
+ }
+}
+
+
+/**
+ * Endpoints for the HTTP server
+ *
+ * Each endpoint should take the form of an object. The init() method of this object will be passed:
+ * method - the method of the request ("GET" or "POST")
+ * data - the query string (for a "GET" request) or POST data (for a "POST" request)
+ * sendResponseCallback - a function to send a response to the HTTP request. This can be passed
+ * a response code alone (e.g., sendResponseCallback(404)) or a response
+ * code, MIME type, and response body
+ * (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
+ *
+ * See connector/server_connector.js for examples
+ */
+Zotero.Server.Endpoints = {}
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js
@@ -0,0 +1,607 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Server.Connector = function() {};
+Zotero.Server.Connector._waitingForSelection = {};
+Zotero.Server.Connector.Data = {};
+
+/**
+ * Manage cookies in a sandboxed fashion
+ *
+ * @param {browser} browser Hidden browser object
+ * @param {String} uri URI of page to manage cookies for (cookies for domains that are not
+ * subdomains of this URI are ignored)
+ * @param {String} cookieData Cookies with which to initiate the sandbox
+ */
+Zotero.Server.Connector.CookieManager = function(browser, uri, cookieData) {
+ this._webNav = browser.webNavigation;
+ this._browser = browser;
+ this._watchedBrowsers = [browser];
+ this._observerService = Components.classes["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService);
+
+ this._uri = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(uri, null, null);
+
+ var splitCookies = cookieData.split(/; ?/);
+ this._cookies = {};
+ for each(var cookie in splitCookies) {
+ var key = cookie.substr(0, cookie.indexOf("="));
+ var value = cookie.substr(cookie.indexOf("=")+1);
+ this._cookies[key] = value;
+ }
+
+ [this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
+}
+
+Zotero.Server.Connector.CookieManager.prototype = {
+ "_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
+ "_watchedXHRs":[],
+
+ /**
+ * nsIObserver implementation for adding, clearing, and slurping cookies
+ */
+ "observe": function(channel, topic) {
+ if(topic == "quit-application") {
+ Zotero.debug("WARNING: A Zotero.Server.CookieManager for "+this._uri.spec+" was still open on shutdown");
+ } else {
+ channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ var isTracked = null;
+ try {
+ var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
+ for each(var browser in this._watchedBrowsers) {
+ isTracked = topDoc == browser.contentDocument;
+ if(isTracked) break;
+ }
+ } catch(e) {}
+ if(isTracked === null) {
+ try {
+ isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
+ } catch(e) {}
+ }
+ if(isTracked === null) {
+ try {
+ isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
+ } catch(e) {}
+ }
+
+ // isTracked is now either true, false, or null
+ // true => we should manage cookies for this request
+ // false => we should not manage cookies for this request
+ // null => this request is of a type we couldn't match to this request. one such type
+ // is a link prefetch (nsPrefetchNode) but there might be others as well. for
+ // now, we are paranoid and reject these.
+
+ if(isTracked === false) {
+ Zotero.debug("Zotero.Server.CookieManager: not touching channel for "+channel.URI.spec);
+ return;
+ } else if(isTracked) {
+ Zotero.debug("Zotero.Server.CookieManager: managing cookies for "+channel.URI.spec);
+ } else {
+ Zotero.debug("Zotero.Server.CookieManager: being paranoid about channel for "+channel.URI.spec);
+ }
+
+ if(topic == "http-on-modify-request") {
+ // clear cookies to be sent to other domains
+ if(isTracked === null || channel.URI.host != this._uri.host) {
+ channel.setRequestHeader("Cookie", "", false);
+ channel.setRequestHeader("Cookie2", "", false);
+ Zotero.debug("Zotero.Server.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
+ return;
+ }
+
+ // add cookies to be sent to this domain
+ var cookies = [key+"="+this._cookies[key]
+ for(key in this._cookies)].join("; ");
+ channel.setRequestHeader("Cookie", cookies, false);
+ Zotero.debug("Zotero.Server.CookieManager: added cookies for request to "+channel.URI.spec);
+ } else if(topic == "http-on-examine-response") {
+ // clear cookies being received
+ try {
+ var cookieHeader = channel.getResponseHeader("Set-Cookie");
+ } catch(e) {
+ return;
+ }
+ channel.setResponseHeader("Set-Cookie", "", false);
+ channel.setResponseHeader("Set-Cookie2", "", false);
+
+ // don't process further if these cookies are for another set of domains
+ if(isTracked === null || channel.URI.host != this._uri.host) {
+ Zotero.debug("Zotero.Server.CookieManager: rejected cookies from "+channel.URI.spec);
+ return;
+ }
+
+ // put new cookies into our sandbox
+ if(cookieHeader) {
+ var cookies = cookieHeader.split(/; ?/);
+ var newCookies = {};
+ for each(var cookie in cookies) {
+ var key = cookie.substr(0, cookie.indexOf("="));
+ var value = cookie.substr(cookie.indexOf("=")+1);
+ var lcCookie = key.toLowerCase();
+
+ if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
+ // ignore cookie parameters; we are only holding cookies for a few minutes
+ // with a single domain, and the path attribute doesn't allow any additional
+ // security.
+ // DEBUG: does ignoring the path attribute break any sites?
+ continue;
+ } else if(lcCookie == "secure") {
+ // don't accept secure cookies
+ newCookies = {};
+ break;
+ } else {
+ newCookies[key] = value;
+ }
+ }
+ [this._cookies[key] = newCookies[key] for(key in newCookies)];
+ }
+
+ Zotero.debug("Zotero.Server.CookieManager: slurped cookies from "+channel.URI.spec);
+ }
+ }
+ },
+
+ /**
+ * Attach CookieManager to a specific XMLHttpRequest
+ * @param {XMLHttpRequest} xhr
+ */
+ "attachToBrowser": function(browser) {
+ this._watchedBrowsers.push(browser);
+ },
+
+ /**
+ * Attach CookieManager to a specific XMLHttpRequest
+ * @param {XMLHttpRequest} xhr
+ */
+ "attachToXHR": function(xhr) {
+ this._watchedXHRs.push(xhr);
+ },
+
+ /**
+ * Destroys this CookieManager (intended to be executed when the browser is destroyed)
+ */
+ "destroy": function() {
+ [this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
+ }
+}
+
+
+/**
+ * Lists all available translators, including code for translators that should be run on every page
+ *
+ * Accepts:
+ * browser - one-letter code of the current browser
+ * g = Gecko (Firefox)
+ * c = Google Chrome (WebKit & V8)
+ * s = Safari (WebKit & Nitro/Squirrelfish Extreme)
+ * i = Internet Explorer
+ * Returns:
+ * translators - Zotero.Translator objects
+ * schema - Some information about the database. Currently includes:
+ * itemTypes
+ * name
+ * localizedString
+ * creatorTypes
+ * fields
+ * baseFields
+ * creatorTypes
+ * name
+ * localizedString
+ * fields
+ * name
+ * localizedString
+ */
+Zotero.Server.Connector.GetData = function() {};
+Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData;
+Zotero.Server.Connector.GetData.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Gets available translator list and other important data
+ * @param {Object} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ // Translator data
+ var responseData = {"preferences":{}, "translators":[]};
+
+ // TODO only send necessary translators
+ var translators = Zotero.Translators.getAll();
+ for each(var translator in translators) {
+ let serializableTranslator = {};
+ for each(var key in ["translatorID", "translatorType", "label", "creator", "target",
+ "priority", "browserSupport"]) {
+ serializableTranslator[key] = translator[key];
+ }
+
+ // Do not pass targetless translators that do not support this browser (since that
+ // would mean passing each page back to Zotero)
+ responseData.translators.push(serializableTranslator);
+ }
+
+ // Various DB data (only sending what is required at the moment)
+ var systemVersion = Zotero.Schema.getDBVersion("system");
+ if(systemVersion != data.systemVersion) {
+ responseData.schema = this._generateTypeSchema();
+ }
+
+ // Preferences
+ var prefs = Zotero.Prefs.prefBranch.getChildList("", {}, {});
+ for each(var pref in prefs) {
+ responseData.preferences[pref] = Zotero.Prefs.get(pref);
+ }
+
+ sendResponseCallback(200, "application/json", JSON.stringify(responseData));
+ },
+
+ /**
+ * Generates a type schema. This is used by connector/type.js to handle types without DB access.
+ */
+ "_generateTypeSchema":function() {
+ var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
+ var types = Zotero.ItemTypes.getTypes();
+
+ var fieldIDs = Zotero.DB.columnQuery("SELECT fieldID FROM fieldsCombined");
+ var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
+ for each(var fieldID in fieldIDs) {
+ var fieldObj = {"name":Zotero.ItemFields.getName(fieldID)};
+ try {
+ fieldObj.localizedString = Zotero.getString("itemFields." + fieldObj.name)
+ } catch(e) {}
+ schema.fields[fieldID] = fieldObj;
+ }
+
+ // names, localizedStrings, creatorTypes, and fields for each item type
+ for each(var type in types) {
+ var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
+ var baseFields = {};
+ for each(var fieldID in fieldIDs) {
+ if(baseMappedFields.indexOf(fieldID) !== -1) {
+ baseFields[fieldID] = Zotero.ItemFields.getFieldIDFromTypeAndBase(type.id, fieldID);
+ }
+ }
+
+ var icon = Zotero.ItemTypes.getImageSrc(type.name);
+ icon = icon.substr(icon.lastIndexOf("/")+1);
+
+ schema.itemTypes[type.id] = {"name":type.name,
+ "localizedString":Zotero.ItemTypes.getLocalizedString(type.name),
+ "creatorTypes":[creatorType.id for each(creatorType in Zotero.CreatorTypes.getTypesForItemType(type.id))],
+ "fields":fieldIDs, "baseFields":baseFields, "icon":icon};
+
+ }
+
+ var types = Zotero.CreatorTypes.getTypes();
+ for each(var type in types) {
+ schema.creatorTypes[type.id] = {"name":type.name,
+ "localizedString":Zotero.CreatorTypes.getLocalizedString(type.name)};
+ }
+
+ return schema;
+ }
+}
+
+/**
+ * Detects whether there is an available translator to handle a given page
+ *
+ * Accepts:
+ * uri - The URI of the page to be saved
+ * html - document.innerHTML or equivalent
+ * cookie - document.cookie or equivalent
+ *
+ * Returns a list of available translators as an array
+ */
+Zotero.Server.Connector.Detect = function() {};
+Zotero.Server.Endpoints["/connector/detect"] = Zotero.Server.Connector.Detect;
+Zotero.Server.Connector.Data = {};
+Zotero.Server.Connector.Detect.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Loads HTML into a hidden browser and initiates translator detection
+ * @param {Object} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ this._sendResponse = sendResponseCallback;
+ this._parsedPostData = data;
+
+ this._translate = new Zotero.Translate("web");
+ this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
+
+ Zotero.Server.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
+ this._browser = Zotero.Browser.createHiddenBrowser();
+
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
+
+ var pageShowCalled = false;
+ var me = this;
+ this._translate.setCookieManager(new Zotero.Server.Connector.CookieManager(this._browser,
+ this._parsedPostData["uri"], this._parsedPostData["cookie"]));
+ this._browser.addEventListener("DOMContentLoaded", function() {
+ try {
+ if(me._browser.contentDocument.location.href == "about:blank") return;
+ if(pageShowCalled) return;
+ pageShowCalled = true;
+ delete Zotero.Server.Connector.Data[me._parsedPostData["uri"]];
+
+ // get translators
+ me._translate.setDocument(me._browser.contentDocument);
+ me._translate.getTranslators();
+ } catch(e) {
+ Zotero.debug(e);
+ throw e;
+ }
+ }, false);
+
+ me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
+ },
+
+ /**
+ * Callback to be executed when list of translators becomes available. Sends response with
+ * item types, translator IDs, labels, and icons for available translators.
+ * @param {Zotero.Translate} translate
+ * @param {Zotero.Translator[]} translators
+ */
+ "_translatorsAvailable":function(obj, translators) {
+ var jsons = [];
+ for each(var translator in translators) {
+ if(translator.itemType == "multiple") {
+ var icon = "treesource-collection.png"
+ } else {
+ var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
+ icon = icon.substr(icon.lastIndexOf("/")+1);
+ }
+ var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
+ "label":translator.label, "priority":translator.priority}
+ jsons.push(json);
+ }
+ this._sendResponse(200, "application/json", JSON.stringify(jsons));
+
+ this._translate.cookieManager.destroy();
+ Zotero.Browser.deleteHiddenBrowser(this._browser);
+ }
+}
+
+/**
+ * Performs translation of a given page
+ *
+ * Accepts:
+ * uri - The URI of the page to be saved
+ * html - document.innerHTML or equivalent
+ * cookie - document.cookie or equivalent
+ *
+ * Returns:
+ * If a single item, sends response code 201 with no 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
+ * uri - the URI of the page for which multiple items are available
+ */
+Zotero.Server.Connector.SavePage = function() {};
+Zotero.Server.Endpoints["/connector/savePage"] = Zotero.Server.Connector.SavePage;
+Zotero.Server.Connector.SavePage.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Either loads HTML into a hidden browser and initiates translation, or saves items directly
+ * to the database
+ * @param {Object} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ this._sendResponse = sendResponseCallback;
+ Zotero.Server.Connector.Detect.prototype.init.apply(this, [data, sendResponseCallback])
+ },
+
+ /**
+ * Callback to be executed when items must be selected
+ * @param {Zotero.Translate} translate
+ * @param {Object} itemList ID=>text pairs representing available items
+ */
+ "_selectItems":function(translate, itemList, callback) {
+ var instanceID = Zotero.randomString();
+ Zotero.Server.Connector._waitingForSelection[instanceID] = this;
+
+ // Fix for translators that don't create item lists as objects
+ if(itemList.push && typeof itemList.push === "function") {
+ var newItemList = {};
+ for(var item in itemList) {
+ newItemList[item] = itemList[item];
+ }
+ itemList = newItemList;
+ }
+
+ // Send "Multiple Choices" HTTP response
+ this._sendResponse(300, "application/json", JSON.stringify({"selectItems":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
+
+ // We need this to make sure that we won't stop Firefox from quitting, even if the user
+ // didn't close the selectItems window
+ var observerService = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ var me = this;
+ var quitObserver = {observe:function() { me.selectedItems = false; }};
+ observerService.addObserver(quitObserver, "quit-application", false);
+
+ this.selectedItems = null;
+ var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
+ // permanently slow Firefox with this loop
+ while(this.selectedItems === null && Date.now() < endTime) {
+ Zotero.mainThread.processNextEvent(true);
+ }
+
+ observerService.removeObserver(quitObserver, "quit-application");
+ callback(this.selectedItems);
+ },
+
+ /**
+ * Callback to be executed when list of translators becomes available. Opens progress window,
+ * selects specified translator, and initiates translation.
+ * @param {Zotero.Translate} translate
+ * @param {Zotero.Translator[]} translators
+ */
+ "_translatorsAvailable":function(translate, translators) {
+ // make sure translatorsAvailable succeded
+ if(!translators.length) {
+ Zotero.Browser.deleteHiddenBrowser(this._browser);
+ this._sendResponse(500);
+ return;
+ }
+
+ // figure out where to save
+ var libraryID = null;
+ var collectionID = null;
+ var zp = Zotero.getActiveZoteroPane();
+ try {
+ var libraryID = zp.getSelectedLibraryID();
+ var collection = zp.getSelectedCollection();
+ } catch(e) {}
+
+ // set handlers for translation
+ var me = this;
+ var jsonItems = [];
+ translate.setHandler("select", function(obj, item, callback) { return me._selectItems(obj, item, callback) });
+ translate.setHandler("itemDone", function(obj, item, jsonItem) {
+ if(collection) {
+ collection.addItem(item.id);
+ }
+ jsonItems.push(jsonItem);
+ });
+ translate.setHandler("done", function(obj, item) {
+ me._translate.cookieManager.destroy();
+ Zotero.Browser.deleteHiddenBrowser(me._browser);
+ me._sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
+ });
+
+ // set translator and translate
+ translate.setTranslator(this._parsedPostData.translatorID);
+ translate.translate(libraryID);
+ }
+}
+
+/**
+ * Performs translation of a given page, or, alternatively, saves items directly
+ *
+ * Accepts:
+ * items - an array of JSON format items
+ * Returns:
+ * 201 response code with empty body
+ */
+Zotero.Server.Connector.SaveItem = function() {};
+Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
+Zotero.Server.Connector.SaveItem.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Either loads HTML into a hidden browser and initiates translation, or saves items directly
+ * to the database
+ * @param {Object} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ // figure out where to save
+ var libraryID = null;
+ var collectionID = null;
+ var zp = Zotero.getActiveZoteroPane();
+ try {
+ var libraryID = zp.getSelectedLibraryID();
+ var collection = zp.getSelectedCollection();
+ } catch(e) {}
+
+ // save items
+ var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
+ Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1);
+ for each(var item in data.items) {
+ var savedItem = itemSaver.saveItem(item);
+ if(collection) collection.addItem(savedItem.id);
+ }
+ sendResponseCallback(201);
+ }
+}
+
+/**
+ * Handle item selection
+ *
+ * Accepts:
+ * selectedItems - a list of items to translate in ID => text format as returned by a selectItems handler
+ * instanceID - as returned by savePage call
+ * Returns:
+ * 201 response code with empty body
+ */
+Zotero.Server.Connector.SelectItems = function() {};
+Zotero.Server.Endpoints["/connector/selectItems"] = Zotero.Server.Connector.SelectItems;
+Zotero.Server.Connector.SelectItems.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Finishes up translation when item selection is complete
+ * @param {String} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(data, sendResponseCallback) {
+ var saveInstance = Zotero.Server.Connector._waitingForSelection[data.instanceID];
+ saveInstance._sendResponse = sendResponseCallback;
+
+ saveInstance.selectedItems = false;
+ for(var i in data.selectedItems) {
+ saveInstance.selectedItems = data.selectedItems;
+ break;
+ }
+ }
+}
+
+/**
+ * Get code for a translator
+ *
+ * Accepts:
+ * translatorID
+ * Returns:
+ * code - translator code
+ */
+Zotero.Server.Connector.GetTranslatorCode = function() {};
+Zotero.Server.Endpoints["/connector/getTranslatorCode"] = Zotero.Server.Connector.GetTranslatorCode;
+Zotero.Server.Connector.GetTranslatorCode.prototype = {
+ "supportedMethods":["POST"],
+ "supportedDataTypes":["application/json"],
+
+ /**
+ * Finishes up translation when item selection is complete
+ * @param {String} data POST data or GET query string
+ * @param {Function} sendResponseCallback function to send HTTP response
+ */
+ "init":function(postData, sendResponseCallback) {
+ var translator = Zotero.Translators.get(postData.translatorID);
+ sendResponseCallback(200, "application/javascript", translator.code);
+ }
+}
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/translation/browser_firefox.js b/chrome/content/zotero/xpcom/translation/browser_firefox.js
@@ -1,503 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-const BOMs = {
- "UTF-8":"\xEF\xBB\xBF",
- "UTF-16BE":"\xFE\xFF",
- "UTF-16LE":"\xFF\xFE",
- "UTF-32BE":"\x00\x00\xFE\xFF",
- "UTF-32LE":"\xFF\xFE\x00\x00"
-}
-
-Components.utils.import("resource://gre/modules/NetUtil.jsm");
-
-/**
- * @class Manages the translator sandbox
- * @param {Zotero.Translate} translate
- * @param {String|window} sandboxLocation
- */
-Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
- this.sandbox = new Components.utils.Sandbox(sandboxLocation);
- this.sandbox.Zotero = {};
- this._translate = translate;
-
- // import functions missing from global scope into Fx sandbox
- this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
- this.sandbox.DOMParser = function() {
- // get URI
- // DEBUG: In Fx 4 we can just use document.nodePrincipal, but in Fx 3.6 this doesn't work
- if(typeof sandboxLocation === "string") { // if sandbox specified by URI
- var uri = sandboxLocation;
- } else { // if sandbox specified by DOM document
- var uri = sandboxLocation.location.toString();
- }
-
- // get principal from URI
- var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
- .getService(Components.interfaces.nsIScriptSecurityManager);
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- uri = ioService.newURI(uri, "UTF-8", null);
- var principal = secMan.getCodebasePrincipal(uri);
-
- // initialize DOM parser
- var _DOMParser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
- .createInstance(Components.interfaces.nsIDOMParser);
- _DOMParser.init(principal, uri, uri);
-
- // expose parseFromString
- this.__exposedProps__ = {"parseFromString":"r"};
- this.parseFromString = function(str, contentType) _DOMParser.parseFromString(str, contentType);
- }
- this.sandbox.DOMParser.__exposedProps__ = {"prototype":"r"};
- this.sandbox.DOMParser.prototype = {};
-}
-
-Zotero.Translate.SandboxManager.prototype = {
- /**
- * Evaluates code in the sandbox
- */
- "eval":function(code) {
- Components.utils.evalInSandbox(code, this.sandbox);
- },
-
- /**
- * Imports an object into the sandbox
- *
- * @param {Object} object Object to be imported (under Zotero)
- * @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
- * as the first argument to the function.
- */
- "importObject":function(object, passAsFirstArgument, attachTo) {
- if(!attachTo) attachTo = this.sandbox.Zotero;
- var newExposedProps = false;
- if(!object.__exposedProps__) newExposedProps = {};
- for(var key in (newExposedProps ? object : object.__exposedProps__)) {
- let localKey = key;
- if(newExposedProps) newExposedProps[localKey] = "r";
-
- // magical XPCSafeJSObjectWrappers for sandbox
- if(typeof object[localKey] === "function" || typeof object[localKey] === "object") {
- if(attachTo == this.sandbox) Zotero.debug(localKey);
- attachTo[localKey] = function() {
- var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
- for(var i=0; i<arguments.length; i++) {
- args.push((typeof arguments[i] === "object" && arguments[i] !== null)
- || typeof arguments[i] === "function"
- ? new XPCSafeJSObjectWrapper(arguments[i]) : arguments[i]);
- }
-
- return object[localKey].apply(object, args);
- };
-
- // attach members
- if(!(object instanceof Components.interfaces.nsISupports)) {
- this.importObject(object[localKey], passAsFirstArgument ? passAsFirstArgument : null, attachTo[localKey]);
- }
- } else {
- attachTo[localKey] = object[localKey];
- }
- }
-
- if(newExposedProps) {
- attachTo.__exposedProps__ = newExposedProps;
- } else {
- attachTo.__exposedProps__ = object.__exposedProps__;
- }
- }
-}
-
-/**
- * This variable holds a reference to all open nsIInputStreams and nsIOutputStreams in the global
- * scope at all times. Otherwise, our streams might get garbage collected when we allow other code
- * to run during Zotero.wait().
- */
-Zotero.Translate.IO.maintainedInstances = [];
-
-/******* (Native) Read support *******/
-
-Zotero.Translate.IO.Read = function(file, mode) {
- Zotero.Translate.IO.maintainedInstances.push(this);
-
- this.file = file;
-
- // open file
- this._openRawStream();
-
- // start detecting charset
- var charset = null;
-
- // look for a BOM in the document
- var binStream = Components.classes["@mozilla.org/binaryinputstream;1"].
- createInstance(Components.interfaces.nsIBinaryInputStream);
- binStream.setInputStream(this._rawStream);
- var first4 = binStream.readBytes(4);
-
- for(var possibleCharset in BOMs) {
- if(first4.substr(0, BOMs[possibleCharset].length) == BOMs[possibleCharset]) {
- this._charset = possibleCharset;
- break;
- }
- }
-
- if(this._charset) {
- // BOM found; store its length and go back to the beginning of the file
- this._bomLength = BOMs[this._charset].length;
- this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
- .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
- } else {
- // look for an XML parse instruction
- this._bomLength = 0;
-
- var sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Components.interfaces.nsIScriptableInputStream);
- sStream.init(this._rawStream);
-
- // read until we see if the file begins with a parse instruction
- const whitespaceRe = /\s/g;
- var read;
- do {
- read = sStream.read(1);
- } while(whitespaceRe.test(read))
-
- if(read == "<") {
- var firstPart = read + sStream.read(4);
- if(firstPart == "<?xml") {
- // got a parse instruction, read until it ends
- read = true;
- while((read !== false) && (read !== ">")) {
- read = sStream.read(1);
- firstPart += read;
- }
-
- const encodingRe = /encoding=['"]([^'"]+)['"]/;
- var m = encodingRe.exec(firstPart);
- if(m) {
- try {
- var charconv = Components.classes["@mozilla.org/charset-converter-manager;1"]
- .getService(Components.interfaces.nsICharsetConverterManager)
- .getCharsetTitle(m[1]);
- if(charconv) this._charset = m[1];
- } catch(e) {}
- }
-
- // if we know for certain document is XML, we also know for certain that the
- // default charset for XML is UTF-8
- if(!this._charset) this._charset = "UTF-8";
- }
- }
-
- // If we managed to get a charset here, then translators shouldn't be able to override it,
- // since it's almost certainly correct. Otherwise, we allow override.
- this._allowCharsetOverride = !!this._charset;
- this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
- .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
-
- if(!this._charset) {
- // No XML parse instruction or BOM.
-
- // Check whether the user has specified a charset preference
- var charsetPref = Zotero.Prefs.get("import.charset");
- if(charsetPref == "auto") {
- Zotero.debug("Translate: Checking whether file is UTF-8");
- // For auto-detect, we are basically going to check if the file could be valid
- // UTF-8, and if this is true, we will treat it as UTF-8. Prior likelihood of
- // UTF-8 is very high, so this should be a reasonable strategy.
-
- // from http://codex.wordpress.org/User:Hakre/UTF8
- const UTF8Regex = new RegExp('^(?:' +
- '[\x09\x0A\x0D\x20-\x7E]' + // ASCII
- '|[\xC2-\xDF][\x80-\xBF]' + // non-overlong 2-byte
- '|\xE0[\xA0-\xBF][\x80-\xBF]' + // excluding overlongs
- '|[\xE1-\xEC\xEE][\x80-\xBF]{2}' + // 3-byte, but exclude U-FFFE and U-FFFF
- '|\xEF[\x80-\xBE][\x80-\xBF]' +
- '|\xEF\xBF[\x80-\xBD]' +
- '|\xED[\x80-\x9F][\x80-\xBF]' + // excluding surrogates
- '|\xF0[\x90-\xBF][\x80-\xBF]{2}' + // planes 1-3
- '|[\xF1-\xF3][\x80-\xBF]{3}' + // planes 4-15
- '|\xF4[\x80-\x8F][\x80-\xBF]{2}' + // plane 16
- ')*$');
-
- // Read all currently available bytes from file. This seems to be the entire file,
- // since the IO is blocking anyway.
- this._charset = "UTF-8";
- let bytesAvailable;
- while(bytesAvailable = this._rawStream.available()) {
- // read 131072 bytes
- let fileContents = binStream.readBytes(Math.min(131072, bytesAvailable));
-
- // on failure, try reading up to 3 more bytes and see if that makes this
- // valid (since we have chunked it)
- let isUTF8;
- for(let i=1; !(isUTF8 = UTF8Regex.test(fileContents)) && i <= 3; i++) {
- if(this._rawStream.available()) {
- fileContents += binStream.readBytes(1);
- }
- }
-
- // if the regexp continues to fail, this is not UTF-8
- if(!isUTF8) {
- // Can't be UTF-8; see if a default charset is defined
- this._charset = Zotero.Prefs.get("intl.charset.default", true);
-
- // ISO-8859-1 by default
- if(!this._charset) this._charset = "ISO-8859-1";
-
- break;
- }
- }
- } else {
- // No need to auto-detect; user has specified a charset
- this._charset = charsetPref;
- }
- }
- }
-
- Zotero.debug("Translate: Detected file charset as "+this._charset);
-
- // We know the charset now. Open a converter stream.
- if(mode) this.reset(mode);
-}
-
-Zotero.Translate.IO.Read.prototype = {
- "__exposedProps__":{
- "_getXML":"r",
- "RDF":"r",
- "read":"r",
- "setCharacterSet":"r"
- },
-
- "_openRawStream":function() {
- if(this._rawStream) this._rawStream.close();
- this._rawStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
- .createInstance(Components.interfaces.nsIFileInputStream);
- this._rawStream.init(this.file, 0x01, 0664, 0);
- },
-
- "_seekToStart":function(charset) {
- this._openRawStream();
-
- this._linesExhausted = false;
- this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
- .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
- this.bytesRead = this._bomLength;
-
- this.inputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
- .createInstance(Components.interfaces.nsIConverterInputStream);
- this.inputStream.init(this._rawStream, charset, 32768,
- Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- },
-
- "_readToString":function() {
- var str = {};
- var stringBits = [];
- this.inputStream.QueryInterface(Components.interfaces.nsIUnicharInputStream);
- while(1) {
- var read = this.inputStream.readString(32768, str);
- if(!read) break;
- stringBits.push(str.value);
- }
- return stringBits.join("");
- },
-
- "_initRDF":function() {
- // call Zotero.wait() to do UI repaints
- Zotero.wait();
-
- // get URI
- var IOService = Components.classes['@mozilla.org/network/io-service;1']
- .getService(Components.interfaces.nsIIOService);
- var fileHandler = IOService.getProtocolHandler("file")
- .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
- var baseURI = fileHandler.getURLSpecFromFile(this.file);
-
- Zotero.debug("Translate: Initializing RDF data store");
- this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula();
- var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore);
- try {
- var nodes = Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize);
- parser.parse(nodes, baseURI);
-
- this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore);
- } catch(e) {
- this.close();
- throw "Translate: No RDF found";
- }
- },
-
- "setCharacterSet":function(charset) {
- if(typeof charset !== "string") {
- throw "Translate: setCharacterSet: charset must be a string";
- }
-
- // seek back to the beginning
- this._seekToStart(this._allowCharsetOverride ? this._allowCharsetOverride : this._charset);
-
- if(!_allowCharsetOverride) {
- Zotero.debug("Translate: setCharacterSet: translate charset override ignored due to BOM or XML parse instruction");
- }
- },
-
- "read":function(bytes) {
- var str = {};
-
- if(bytes) {
- // read number of bytes requested
- this.inputStream.QueryInterface(Components.interfaces.nsIUnicharInputStream);
- var amountRead = this.inputStream.readString(bytes, str);
- if(!amountRead) return false;
- this.bytesRead += amountRead;
- } else {
- // bytes not specified; read a line
- this.inputStream.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);
- if(this._linesExhausted) return false;
- this._linesExhausted = !this.inputStream.readLine(str);
- this.bytesRead += str.value.length+1; // only approximate
- }
-
- return str.value;
- },
-
- "_getXML":function() {
- if(this._mode == "xml/dom") {
- return Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize);
- } else {
- return this._readToString().replace(/<\?xml[^>]+\?>/, "");
- }
- },
-
- "reset":function(newMode) {
- if(Zotero.Translate.IO.maintainedInstances.indexOf(this) === -1) {
- Zotero.Translate.IO.maintainedInstances.push(this);
- }
- this._seekToStart(this._charset);
-
- this._mode = newMode;
- if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && !this.RDF) {
- this._initRDF();
- }
- },
-
- "close":function() {
- var myIndex = Zotero.Translate.IO.maintainedInstances.indexOf(this);
- if(myIndex !== -1) Zotero.Translate.IO.maintainedInstances.splice(myIndex, 1);
-
- if(this._rawStream) {
- this._rawStream.close();
- delete this._rawStream;
- }
- }
-}
-Zotero.Translate.IO.Read.prototype.__defineGetter__("contentLength",
-function() {
- return this.file.fileSize;
-});
-
-/******* Write support *******/
-
-Zotero.Translate.IO.Write = function(file, mode, charset) {
- Zotero.Translate.IO.maintainedInstances.push(this);
- this._rawStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
- .createInstance(Components.interfaces.nsIFileOutputStream);
- this._rawStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
- this._writtenToStream = false;
- if(mode || charset) this.reset(mode, charset);
-}
-
-Zotero.Translate.IO.Write.prototype = {
- "__exposedProps__":{
- "RDF":"r",
- "write":"r",
- "setCharacterSet":"r"
- },
-
- "_initRDF":function() {
- Zotero.debug("Translate: Initializing RDF data store");
- this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula();
- this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore);
- },
-
- "setCharacterSet":function(charset) {
- if(typeof charset !== "string") {
- throw "Translate: setCharacterSet: charset must be a string";
- }
-
- if(!this.outputStream) {
- this.outputStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
- .createInstance(Components.interfaces.nsIConverterOutputStream);
- }
-
- if(charset == "UTF-8xBOM") charset = "UTF-8";
- this.outputStream.init(this._rawStream, charset, 1024, "?".charCodeAt(0));
- this._charset = charset;
- },
-
- "write":function(data) {
- if(!this._charset) this.setCharacterSet("UTF-8");
-
- if(!this._writtenToStream && this._charset.substr(this._charset.length-4) == "xBOM"
- && BOMs[this._charset.substr(0, this._charset.length-4).toUpperCase()]) {
- // If stream has not yet been written to, and a UTF type has been selected, write BOM
- this._rawStream.write(BOMs[streamCharset], BOMs[streamCharset].length);
- }
-
- if(this._charset == "MACINTOSH") {
- // fix buggy Mozilla MacRoman
- var splitData = data.split(/([\r\n]+)/);
- for(var i=0; i<splitData.length; i+=2) {
- // write raw newlines straight to the string
- this.outputStream.writeString(splitData[i]);
- if(splitData[i+1]) {
- this._rawStream.write(splitData[i+1], splitData[i+1].length);
- }
- }
- } else {
- this.outputStream.writeString(data);
- }
-
- this._writtenToStream = true;
- },
-
- "reset":function(newMode, charset) {
- this._mode = newMode;
- if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) {
- this._initRDF();
- if(!this._writtenToString) this.setCharacterSet("UTF-8");
- } else if(!this._writtenToString) {
- this.setCharacterSet(charset ? charset : "UTF-8");
- }
- },
-
- "close":function() {
- if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) {
- this.write(this.RDF.serialize());
- }
-
- var myIndex = Zotero.Translate.IO.maintainedInstances.indexOf(this);
- if(myIndex !== -1) Zotero.Translate.IO.maintainedInstances.splice(myIndex, 1);
-
- this._rawStream.close();
- }
-}
diff --git a/chrome/content/zotero/xpcom/translation/browser_other.js b/chrome/content/zotero/xpcom/translation/browser_other.js
@@ -1,66 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-/**
- * @class Manages the translator sandbox
- * @param {Zotero.Translate} translate
- * @param {String|window} sandboxLocation
- */
-Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
- this.sandbox = {};
- this._translate = translate;
-}
-
-Zotero.Translate.SandboxManager.prototype = {
- /**
- * Evaluates code in the sandbox
- */
- "eval":function(code) {
- // eval in sandbox scope
- (new Function("with(this) { " + code + " }")).call(this.sandbox);
- },
-
- /**
- * Imports an object into the sandbox
- *
- * @param {Object} object Object to be imported (under Zotero)
- * @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
- * as the first argument to the function.
- */
- "importObject":function(object, passAsFirstArgument) {
- var translate = this._translate;
-
- for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) {
- var fn = (function(object, key) { return object[key] })();
-
- // magic "this"-preserving wrapping closure
- this.sandbox[key] = function() {
- var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
- for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
- fn.apply(object, args);
- };
- }
- }
-}
-\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/translation/item_connector.js b/chrome/content/zotero/xpcom/translation/item_connector.js
@@ -1,30 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-Zotero.Translate.Item = {
- "saveItem":function (translate, item) {
-
- }
-}
-\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/translation/tlds.js b/chrome/content/zotero/xpcom/translation/tlds.js
@@ -0,0 +1,271 @@
+const TLDS = {
+ "ac":true,
+ "ad":true,
+ "ae":true,
+ "aero":true,
+ "af":true,
+ "ag":true,
+ "ai":true,
+ "al":true,
+ "am":true,
+ "an":true,
+ "ao":true,
+ "aq":true,
+ "ar":true,
+ "arpa":true,
+ "as":true,
+ "asia":true,
+ "at":true,
+ "au":true,
+ "aw":true,
+ "ax":true,
+ "az":true,
+ "ba":true,
+ "bb":true,
+ "bd":true,
+ "be":true,
+ "bf":true,
+ "bg":true,
+ "bh":true,
+ "bi":true,
+ "biz":true,
+ "bj":true,
+ "bm":true,
+ "bn":true,
+ "bo":true,
+ "br":true,
+ "bs":true,
+ "bt":true,
+ "bv":true,
+ "bw":true,
+ "by":true,
+ "bz":true,
+ "ca":true,
+ "cat":true,
+ "cc":true,
+ "cd":true,
+ "cf":true,
+ "cg":true,
+ "ch":true,
+ "ci":true,
+ "ck":true,
+ "cl":true,
+ "cm":true,
+ "cn":true,
+ "co":true,
+ "com":true,
+ "coop":true,
+ "cr":true,
+ "cu":true,
+ "cv":true,
+ "cx":true,
+ "cy":true,
+ "cz":true,
+ "de":true,
+ "dj":true,
+ "dk":true,
+ "dm":true,
+ "do":true,
+ "dz":true,
+ "ec":true,
+ "edu":true,
+ "ee":true,
+ "eg":true,
+ "er":true,
+ "es":true,
+ "et":true,
+ "eu":true,
+ "fi":true,
+ "fj":true,
+ "fk":true,
+ "fm":true,
+ "fo":true,
+ "fr":true,
+ "ga":true,
+ "gb":true,
+ "gd":true,
+ "ge":true,
+ "gf":true,
+ "gg":true,
+ "gh":true,
+ "gi":true,
+ "gl":true,
+ "gm":true,
+ "gn":true,
+ "gov":true,
+ "gp":true,
+ "gq":true,
+ "gr":true,
+ "gs":true,
+ "gt":true,
+ "gu":true,
+ "gw":true,
+ "gy":true,
+ "hk":true,
+ "hm":true,
+ "hn":true,
+ "hr":true,
+ "ht":true,
+ "hu":true,
+ "id":true,
+ "ie":true,
+ "il":true,
+ "im":true,
+ "in":true,
+ "info":true,
+ "int":true,
+ "io":true,
+ "iq":true,
+ "ir":true,
+ "is":true,
+ "it":true,
+ "je":true,
+ "jm":true,
+ "jo":true,
+ "jobs":true,
+ "jp":true,
+ "ke":true,
+ "kg":true,
+ "kh":true,
+ "ki":true,
+ "km":true,
+ "kn":true,
+ "kp":true,
+ "kr":true,
+ "kw":true,
+ "ky":true,
+ "kz":true,
+ "la":true,
+ "lb":true,
+ "lc":true,
+ "li":true,
+ "lk":true,
+ "lr":true,
+ "ls":true,
+ "lt":true,
+ "lu":true,
+ "lv":true,
+ "ly":true,
+ "ma":true,
+ "mc":true,
+ "md":true,
+ "me":true,
+ "mg":true,
+ "mh":true,
+ "mil":true,
+ "mk":true,
+ "ml":true,
+ "mm":true,
+ "mn":true,
+ "mo":true,
+ "mobi":true,
+ "mp":true,
+ "mq":true,
+ "mr":true,
+ "ms":true,
+ "mt":true,
+ "mu":true,
+ "museum":true,
+ "mv":true,
+ "mw":true,
+ "mx":true,
+ "my":true,
+ "mz":true,
+ "na":true,
+ "name":true,
+ "nc":true,
+ "ne":true,
+ "net":true,
+ "nf":true,
+ "ng":true,
+ "ni":true,
+ "nl":true,
+ "no":true,
+ "np":true,
+ "nr":true,
+ "nu":true,
+ "nz":true,
+ "om":true,
+ "org":true,
+ "pa":true,
+ "pe":true,
+ "pf":true,
+ "pg":true,
+ "ph":true,
+ "pk":true,
+ "pl":true,
+ "pm":true,
+ "pn":true,
+ "pr":true,
+ "pro":true,
+ "ps":true,
+ "pt":true,
+ "pw":true,
+ "py":true,
+ "qa":true,
+ "re":true,
+ "ro":true,
+ "rs":true,
+ "ru":true,
+ "rw":true,
+ "sa":true,
+ "sb":true,
+ "sc":true,
+ "sd":true,
+ "se":true,
+ "sg":true,
+ "sh":true,
+ "si":true,
+ "sj":true,
+ "sk":true,
+ "sl":true,
+ "sm":true,
+ "sn":true,
+ "so":true,
+ "sr":true,
+ "st":true,
+ "su":true,
+ "sv":true,
+ "sy":true,
+ "sz":true,
+ "tc":true,
+ "td":true,
+ "tel":true,
+ "tf":true,
+ "tg":true,
+ "th":true,
+ "tj":true,
+ "tk":true,
+ "tl":true,
+ "tm":true,
+ "tn":true,
+ "to":true,
+ "tp":true,
+ "tr":true,
+ "travel":true,
+ "tt":true,
+ "tv":true,
+ "tw":true,
+ "tz":true,
+ "ua":true,
+ "ug":true,
+ "uk":true,
+ "us":true,
+ "uy":true,
+ "uz":true,
+ "va":true,
+ "vc":true,
+ "ve":true,
+ "vg":true,
+ "vi":true,
+ "vn":true,
+ "vu":true,
+ "wf":true,
+ "ws":true,
+ "xxx":true,
+ "ye":true,
+ "yt":true,
+ "za":true,
+ "zm":true,
+ "zw":true
+};
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
@@ -25,14 +25,15 @@
/**
* @class
- * Deprecated class for creating new Zotero.Translate instances
+ * Deprecated class for creating new Zotero.Translate instances<br/>
+ * <br/>
* New code should use Zotero.Translate.Web, Zotero.Translate.Import, Zotero.Translate.Export, or
* Zotero.Translate.Search
*/
Zotero.Translate = function(type) {
Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to");
// hack
- var translate = Zotero.Translate.new(type);
+ var translate = Zotero.Translate.newInstance(type);
for(var i in translate) {
this[i] = translate[i];
}
@@ -43,10 +44,14 @@ Zotero.Translate = function(type) {
/**
* Create a new translator by a string type
*/
-Zotero.Translate.new = function(type) {
+Zotero.Translate.newInstance = function(type) {
return new Zotero.Translate[type[0].toUpperCase()+type.substr(1).toLowerCase()];
}
+/**
+ * Namespace for Zotero sandboxes
+ * @namespace
+ */
Zotero.Translate.Sandbox = {
/**
* Combines a sandbox with the base sandbox
@@ -67,10 +72,11 @@ Zotero.Translate.Sandbox = {
/**
* Base sandbox. These methods are available to all translators.
+ * @namespace
*/
"Base": {
/**
- * Called as Zotero.Item#complete() from translators to save items to the database.
+ * Called as {@link Zotero.Item#complete} from translators to save items to the database.
* @param {Zotero.Translate} translate
* @param {SandboxItem} An item created using the Zotero.Item class from the sandbox
*/
@@ -86,7 +92,7 @@ Zotero.Translate.Sandbox = {
// just return the item array
if(translate._libraryID === false || translate._parentTranslator) {
translate.newItems.push(item);
- translate._runHandler("itemDone", item);
+ translate._runHandler("itemDone", item, item);
return;
}
@@ -99,7 +105,8 @@ Zotero.Translate.Sandbox = {
Zotero.wait();
}
- translate._runHandler("itemDone", newItem);
+ // pass both the saved item and the original JS array item
+ translate._runHandler("itemDone", newItem, item);
},
/**
@@ -142,16 +149,19 @@ Zotero.Translate.Sandbox = {
}
Zotero.debug("Translate: creating translate instance of type "+type+" in sandbox");
- var translation = Zotero.Translate.new(type);
+ var translation = Zotero.Translate.newInstance(type);
translation._parentTranslator = translate;
if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) {
throw("Translate: only export translators may call other export translators");
}
- // for security reasons, safeTranslator wraps the translator object.
- // note that setLocation() is not allowed
- var safeTranslator = new Object();
+ /**
+ * @class Wrapper for {@link Zotero.Translate} for safely calling another translator
+ * from inside an existing translator
+ * @inner
+ */
+ var safeTranslator = {};
safeTranslator.__exposedProps__ = {
"setSearch":"r",
"setDocument":"r",
@@ -185,43 +195,61 @@ Zotero.Translate.Sandbox = {
);
};
safeTranslator.setString = function(arg) { translation.setString(arg) };
- safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) };
+ safeTranslator.setTranslator = function(arg) {
+ var success = translation.setTranslator(arg);
+ if(!success) {
+ throw "Translator "+translate.translator[0].translatorID+" attempted to call invalid translatorID "+arg;
+ }
+ };
safeTranslator.getTranslators = function() { return translation.getTranslators() };
safeTranslator.translate = function() {
setDefaultHandlers(translate, translation);
return translation.translate(false);
};
+ // TODO
safeTranslator.getTranslatorObject = function(callback) {
- translation._loadTranslator(translation.translator[0]);
-
- if(Zotero.isFx) {
- // do same origin check
- var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
- .getService(Components.interfaces.nsIScriptSecurityManager);
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
+ var haveTranslatorFunction = function(translator) {
+ translation.translator[0] = translator;
+ if(!Zotero._loadTranslator(translator)) throw "Translator could not be loaded";
- var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
- translate._sandboxLocation.location : translate._sandboxLocation, null, null);
- var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
- translation._sandboxLocation.location : translation._sandboxLocation, null, null);
- Zotero.debug(outerSandboxURI.spec);
- Zotero.debug(innerSandboxURI.spec);
+ if(Zotero.isFx) {
+ // do same origin check
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
+ translate._sandboxLocation.location : translate._sandboxLocation, null, null);
+ var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
+ translation._sandboxLocation.location : translation._sandboxLocation, null, null);
+
+ try {
+ secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
+ } catch(e) {
+ throw "Translate: getTranslatorObject() may not be called from web or search "+
+ "translators to web or search translators from different origins.";
+ }
+ }
- try {
- secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
- } catch(e) {
- throw "Translate: getTranslatorObject() may not be called from web or search "+
- "translators to web or search translators from different origins.";
+ translation._prepareTranslation();
+ setDefaultHandlers(translate, translation);
+
+ if(callback) callback(translation._sandboxManager.sandbox);
+ };
+
+ if(typeof translation.translator[0] === "object") {
+ haveTranslatorFunction(translation.translator[0]);
+ return translation._sandboxManager.sandbox;
+ } else {
+ if(Zotero.isConnector && !callback) {
+ throw "Translate: Translator must accept a callback to getTranslatorObject() to "+
+ "operate in this translation environment.";
}
+
+ Zotero.Translators.get(translation.translator[0], haveTranslatorFunction);
+ if(!Zotero.isConnector) return translation._sandboxManager.sandbox;
}
-
- translation._prepareTranslation();
- setDefaultHandlers(translate, translation);
-
- // return sandbox
- if(callback) callback(translation._sandboxManager.sandbox);
- return translation._sandboxManager.sandbox;
};
// TODO security is not super-tight here, as someone could pass something into arg
@@ -275,22 +303,57 @@ Zotero.Translate.Sandbox = {
/**
* Lets user pick which items s/he wants to put in his/her library
* @param {Zotero.Translate} translate
- * @param {Object} options An set of id => name pairs in object format
+ * @param {Object} items An set of id => name pairs in object format
*/
- "selectItems":function(translate, options, callback) {
- // hack to see if there are options
- var haveOptions = false;
- for(var i in options) {
- haveOptions = true;
- break;
- }
-
- if(!haveOptions) {
+ "selectItems":function(translate, items, callback) {
+ if(Zotero.Utilities.isEmpty(items)) {
throw "Translate: translator called select items with no items";
}
- if(translate._handlers.select) {
- options = translate._runHandler("select", options);
+ if(translate._selectedItems) {
+ // if we have a set of selected items for this translation, use them
+ return translate._selectedItems;
+ } else if(translate._handlers.select) {
+ var haveAsyncCallback = !!callback;
+ var haveAsyncHandler = false;
+ var returnedItems = null;
+
+ // if this translator doesn't provide an async callback for selectItems, set things
+ // up so that we can wait to see if the select handler returns synchronously. If it
+ // doesn't, we will need to restart translation.
+ if(!haveAsyncCallback) {
+ callback = function(selectedItems) {
+ if(haveAsyncHandler) {
+ translate.translate(this._libraryID, this._saveAttachments, selectedItems);
+ } else {
+ returnedItems = selectedItems;
+ }
+ };
+ }
+
+ translate._runHandler("select", items, callback);
+
+ if(!haveAsyncCallback) {
+ if(translate.translator[0].browserSupport !== "g") {
+ Zotero.debug("Translate: WARNING: This translator is configured for "+
+ "non-Firefox browser support, but no callback was provided for "+
+ "selectItems(). When executed outside of Firefox, a selectItems() call "+
+ "will require that this translator to be called multiple times.", 3);
+ }
+
+ if(returnedItems === null) {
+ // The select handler is asynchronous, but this translator doesn't support
+ // asynchronous select. We return false to abort translation in this
+ // instance, and we will restart it later when the selectItems call is
+ // complete.
+ haveAsyncHandler = true;
+ return false;
+ } else {
+ return returnedItems;
+ }
+ }
+ } else { // no handler defined; assume they want all of them
+ return options;
}
if(callback) callback(options);
@@ -378,7 +441,7 @@ Zotero.Translate.Sandbox = {
"Import":{
/**
* Saves a collection to the DB
- * Called as Zotero.Collection#complete() from the sandbox
+ * Called as {@link Zotero.Collection#complete} from the sandbox
* @param {Zotero.Translate} translate
* @param {SandboxCollection} collection
*/
@@ -520,7 +583,7 @@ Zotero.Translate.Base.prototype = {
throw("No translatorID specified");
}
} else {
- this.translator = [Zotero.Translators.get(translator)];
+ this.translator = [translator];
}
return !!this.translator;
@@ -591,17 +654,25 @@ Zotero.Translate.Base.prototype = {
* @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values
* @param {Any} argument Argument to be passed to handler
*/
- "_runHandler":function(type, argument) {
+ "_runHandler":function(type) {
var returnValue = undefined;
if(this._handlers[type]) {
+ // compile list of arguments
+ if(this._parentTranslator) {
+ // if there is a parent translator, make sure we don't the Zotero.Translate
+ // object, since it could open a security hole
+ var args = [null];
+ } else {
+ var args = [this];
+ }
+ for(var i=1; i<arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+
for(var i in this._handlers[type]) {
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
try {
- if(this._parentTranslator) {
- returnValue = this._handlers[type][i](null, argument);
- } else {
- returnValue = this._handlers[type][i](this, argument);
- }
+ returnValue = this._handlers[type][i].apply(null, args);
} catch(e) {
if(this._parentTranslator) {
// throw handler errors if they occur when a translator is
@@ -635,16 +706,73 @@ Zotero.Translate.Base.prototype = {
if(this._currentState == "detect") throw "Translate: getTranslators: detection is already running";
this._currentState = "detect";
this._getAllTranslators = getAllTranslators;
- this._potentialTranslators = this._getPotentialTranslators();
+ this._getTranslatorsGetPotentialTranslators();
+
+ // if detection returns immediately, return found translators
+ if(!this._currentState) return this._foundTranslators;
+ },
+
+ /**
+ * Get all potential translators
+ * @return {Zotero.Translator[]}
+ */
+ "_getTranslatorsGetPotentialTranslators":function() {
+ var me = this;
+ Zotero.Translators.getAllForType(this.type,
+ function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
+ },
+
+ /**
+ * Called on completion of {@link #_getTranslatorsGetPotentialTranslators} call
+ */
+ "_getTranslatorsTranslatorsReceived":function(allPotentialTranslators, properToProxyFunctions) {
+ this._potentialTranslators = [];
this._foundTranslators = [];
- Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3);
+ // this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is
+ // specific for each translator, but we want to avoid making a copy of a translator whenever
+ // possible.
+ this._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null;
+ this._waitingForRPC = false;
- this._detect();
+ for(var i in allPotentialTranslators) {
+ var translator = allPotentialTranslators[i];
+ if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ this._potentialTranslators.push(translator);
+ } else {
+ this._waitingForRPC = true;
+ }
+ }
- // if detection returns immediately, return found translators
- if(!this._currentState) return this._foundTranslators;
+ // TODO maybe this should only be in the web translator
+ if(this._waitingForRPC) {
+ var me = this;
+ Zotero.Connector.callMethod("detect", {"uri":this.location.toString(),
+ "cookie":this.document.cookie,
+ "html":this.document.documentElement.innerHTML},
+ function(returnValue) { me._getTranslatorsRPCComplete(returnValue) });
+ }
+
+ this._detect();
},
+
+ /**
+ * Called on completion of detect RPC for
+ * {@link Zotero.Translate.Base#_getTranslatorsTranslatorsReceived}
+ */
+ "_getTranslatorsRPCComplete":function(rpcTranslators) {
+ this._waitingForRPC = false;
+
+ // if there are translators, add them to the list of found translators
+ if(rpcTranslators) {
+ this._foundTranslators = this._foundTranslators.concat(rpcTranslators);
+ }
+
+ // call _detectTranslatorsCollected to return detected translators
+ if(this._currentState === null) {
+ this._detectTranslatorsCollected();
+ }
+ },
/**
* Begins the actual translation. At present, this returns immediately for import/export
@@ -656,14 +784,34 @@ Zotero.Translate.Base.prototype = {
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
*/
- "translate":function(libraryID, saveAttachments) {
- // initialize properties specific to each translation
+ "translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
this._currentState = "translate";
if(!this.translator || !this.translator.length) {
throw("Translate: Failed: no translator specified");
}
+ this._libraryID = libraryID;
+ this._saveAttachments = saveAttachments === undefined || saveAttachments;
+
+ if(typeof this.translator[0] === "object") {
+ // already have a translator object, so use it
+ this._translateHaveTranslator();
+ } else {
+ // need to get translator first
+ var me = this;
+ Zotero.Translators.get(this.translator[0],
+ function(translator) {
+ me.translator[0] = translator;
+ me._translateHaveTranslator();
+ });
+ }
+ },
+
+ /**
+ * Called when translator has been retrieved
+ */
+ "_translateHaveTranslator":function() {
// load translators
if(!this._loadTranslator(this.translator[0])) return;
@@ -671,15 +819,13 @@ Zotero.Translate.Base.prototype = {
if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions;
// prepare translation
- this._libraryID = libraryID;
- this._saveAttachments = typeof saveAttachments === "undefined" ? true : saveAttachments;
this._prepareTranslation();
Zotero.debug("Translate: Beginning translation with "+this.translator[0].label);
// translate
try {
- this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(this.null, this._getParameters());
+ this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(null, this._getParameters());
} catch(e) {
if(this._parentTranslator) {
throw(e);
@@ -715,9 +861,10 @@ Zotero.Translate.Base.prototype = {
if(oldState === "detect") {
if(this._potentialTranslators.length) {
var lastTranslator = this._potentialTranslators.shift();
+ var lastProperToProxyFunction = this._properToProxyFunctions ? this._properToProxyFunctions.shift() : null;
if(returnValue) {
- var dupeTranslator = {"itemType":returnValue};
+ var dupeTranslator = {"itemType":returnValue, "properToProxy":lastProperToProxyFunction};
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
this._foundTranslators.push(dupeTranslator);
} else if(error) {
@@ -730,7 +877,7 @@ Zotero.Translate.Base.prototype = {
this._detect();
} else {
this._currentState = null;
- this._runHandler("translators", this._foundTranslators ? this._foundTranslators : false);
+ if(!this._waitingForRPC) this._detectTranslatorsCollected();
}
} else {
this._currentState = null;
@@ -762,6 +909,12 @@ Zotero.Translate.Base.prototype = {
* Runs detect code for a translator
*/
"_detect":function() {
+ // there won't be any translators if we need an RPC call
+ if(!this._potentialTranslators.length) {
+ this.complete(true);
+ return;
+ }
+
if(!this._loadTranslator(this._potentialTranslators[0])) {
this.complete(false, "Error loading translator into sandbox");
return;
@@ -779,6 +932,15 @@ Zotero.Translate.Base.prototype = {
},
/**
+ * Called when all translators have been collected for detection
+ */
+ "_detectTranslatorsCollected":function() {
+ Zotero.debug("Translate: All translator detect calls and RPC calls complete");
+ this._foundTranslators.sort(function(a, b) { return a.priority-b.priority });
+ this._runHandler("translators", this._foundTranslators);
+ },
+
+ /**
* Loads the translator into its sandbox
* @param {Zotero.Translator} translator
* @return {Boolean} Whether the translator could be successfully loaded
@@ -795,7 +957,6 @@ Zotero.Translate.Base.prototype = {
try {
this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox);
- return true;
} catch(e) {
if(translator.logError) {
translator.logError(e.toString());
@@ -815,13 +976,14 @@ Zotero.Translate.Base.prototype = {
*/
"_generateSandbox":function() {
Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4);
- this._sandboxManager = new Zotero.Translate.SandboxManager(this, this._sandboxLocation);
+ this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation);
const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']";
var src = "var Zotero = {};"+
"Zotero.Item = function (itemType) {"+
+ "const createArrays = "+createArrays+";"+
"this.itemType = itemType;"+
- "for each(var array in "+createArrays+") {"+
- "this[array] = [];"+
+ "for(var i in createArrays) {"+
+ "this[createArrays[i]] = [];"+
"}"+
"};"+
"Zotero.Collection = function () {};"+
@@ -927,17 +1089,11 @@ Zotero.Translate.Base.prototype = {
* No-op for preparing translation
*/
"_prepareTranslation":function() {},
-
- /**
- * Get all potential translators
- * @return {Zotero.Translator[]}
- */
- "_getPotentialTranslators":function() {
- return Zotero.Translators.getAllForType(this.type);
- }
}
/**
+ * @class Web translation
+ *
* @property {Document} document The document object to be used for web scraping (set with setDocument)
* @property {Zotero.Connector.CookieManager} cookieManager A CookieManager to manage cookies for
* this Translate instance.
@@ -975,30 +1131,21 @@ Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) {
* @param {String} location The URL of the page to translate
*/
Zotero.Translate.Web.prototype.setLocation = function(location) {
- // account for proxies
- this.location = Zotero.Proxies.proxyToProper(location);
- if(this.location != location) {
- // figure out if this URL is being proxies
- this.locationIsProxied = true;
- }
+ this.location = location;
this.path = this.location;
}
/**
- * Get all potential translators
+ * Get potential web translators
*/
-Zotero.Translate.Web.prototype._getPotentialTranslators = function() {
- var allTranslators = Zotero.Translators.getAllForType("web");
- var potentialTranslators = [];
-
- Zotero.debug("Translate: Running regular expressions");
- for(var i=0; i<allTranslators.length; i++) {
- if(!allTranslators[i].webRegexp || (this.location.length < 8192 && allTranslators[i].webRegexp.test(this.location))) {
- potentialTranslators.push(allTranslators[i]);
- }
- }
-
- return potentialTranslators;
+Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() {
+ var me = this;
+ Zotero.Translators.getWebTranslatorsForLocation(this.location,
+ function(data) {
+ // data[0] = list of translators
+ // data[1] = list of functions to convert proper URIs to proxied URIs
+ me._getTranslatorsTranslatorsReceived(data[0], data[1]);
+ });
}
/**
@@ -1023,14 +1170,67 @@ Zotero.Translate.Web.prototype._prepareTranslation = function() {
}
/**
- * Overload detect to test regexp first
+ * Overload translate to set selectedItems
+ */
+Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments, selectedItems) {
+ this._selectedItems = selectedItems;
+ Zotero.Translate.Base.prototype.translate.apply(this, libraryID, saveAttachments);
+}
+
+/**
+ * Overload _translateHaveTranslator to send an RPC call if necessary
+ */
+Zotero.Translate.Web.prototype._translateHaveTranslator = function() {
+ if(this.translator[0].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ // begin process to run translator in browser
+ Zotero.Translate.Base.prototype._translateHaveTranslator.apply(this);
+ } else {
+ // otherwise, ferry translator load to RPC
+ var me = this;
+ Zotero.Connector.callMethod("savePage", {
+ "uri":this.location.toString(),
+ "translatorID":(typeof this.translator[0] === "object"
+ ? this.translator[0].translatorID : this.translator[0]),
+ "cookie":this.document.cookie,
+ "html":this.document.documentElement.innerHTML
+ }, function(obj) { me._translateRPCComplete(obj) });
+ }
+}
+
+/**
+ * Called when an RPC call for remote translation completes
+ */
+Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode) {
+ if(!obj) this.complete(false, 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,
+ function(selectedItems) {
+ Zotero.Connector.callMethod("selectItems",
+ {"instanceID":obj.instanceID, "selectedItems":selectedItems},
+ function(obj) { me._translateRPCComplete(obj) })
+ }
+ );
+ } else {
+ // if we don't have to select items, continue
+ for(var i in obj.items) {
+ this._runHandler("itemDone", null, obj.items[i]);
+ }
+ this.complete(true);
+ }
+}
+
+/**
+ * Overload complete to report translation failure
*/
Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
// call super
var oldState = this._currentState;
var errorString = Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]);
- // Report translaton failure if we failed
+ // Report translation failure if we failed
if(oldState == "translate" && errorString && this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) {
// Don't report failure if in private browsing mode
if(Zotero.isFx && !Zotero.isStandalone) {
@@ -1049,6 +1249,9 @@ Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
}
}
+/**
+ * @class Import translation
+ */
Zotero.Translate.Import = function() {
this.init();
}
@@ -1081,35 +1284,26 @@ Zotero.Translate.Import.prototype.complete = function(returnValue, error) {
}
/**
- * Get all potential translators, ordering translators with the right file extension first
+ * Get all potential import translators, ordering translators with the right file extension first
*/
-Zotero.Translate.Import.prototype._getPotentialTranslators = function() {
- var allTranslators = Zotero.Translators.getAllForType("import");
- var tier1Translators = [];
- var tier2Translators = [];
-
- for(var i=0; i<allTranslators.length; i++) {
- if(allTranslators[i].importRegexp.test(this.location)) {
- tier1Translators.push(allTranslators[i]);
- } else {
- tier2Translators.push(allTranslators[i]);
- }
- }
-
- return tier1Translators.concat(tier2Translators);
+Zotero.Translate.Import.prototype._getTranslatorsGetPotentialTranslators = function() {
+ var me = this;
+ Zotero.Translators.getImportTranslatorsForLocation(this.location,
+ function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
}
/**
- * Overload Zotero.Translate.Base#_detect to return all translators immediately only if no string
- * or location is set
+ * Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately only
+ * if no string or location is set
*/
-Zotero.Translate.Import.prototype._detect = function() {
+Zotero.Translate.Import.prototype.getTranslators = function() {
if(!this._string && !this.location) {
- this._foundTranslators = this._potentialTranslators;
+ this._foundTranslators = Zotero.Translators.getAllForType(this.type);
this._potentialTranslators = [];
this.complete(true);
+ return this._foundTranslators;
} else {
- Zotero.Translate.Base.prototype._detect.call(this);
+ Zotero.Translate.Base.prototype.getTranslators.call(this);
}
}
@@ -1156,7 +1350,7 @@ Zotero.Translate.Import.prototype._loadTranslator = function(translator) {
this._sandboxManager.importObject(this._io);
return true;
-},
+}
/**
* Prepare translation
@@ -1182,6 +1376,9 @@ function() {
});
+/**
+ * @class Export translation
+ */
Zotero.Translate.Export = function() {
this.init();
}
@@ -1235,12 +1432,13 @@ Zotero.Translate.Export.prototype.setDisplayOptions = function(displayOptions) {
Zotero.Translate.Export.prototype.complete = Zotero.Translate.Import.prototype.complete;
/**
- * Overload Zotero.Translate.Base#_detect to return all translators immediately
+ * Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately
*/
-Zotero.Translate.Export.prototype._detect = function() {
- this._foundTranslators = this._potentialTranslators;
+Zotero.Translate.Export.prototype.getTranslators = function() {
+ this._foundTranslators = Zotero.Translators.getAllForType(this.type);
this._potentialTranslators = [];
this.complete(true);
+ return this._foundTranslators;
}
/**
@@ -1295,6 +1493,7 @@ function() {
});
/**
+ * @class Search translation
* @property {Array[]} search Item (in {@link Zotero.Item#serialize} format) to extrapolate data
* (set with setSearch)
*/
@@ -1307,7 +1506,7 @@ Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
/**
- * @borrows Zotero.Translate.Web#setCookieManager as Zotero.Translate.Search#setCookieManager
+ * @borrows Zotero.Translate.Web#setCookieManager
*/
Zotero.Translate.Search.prototype.setCookieManager = Zotero.Translate.Web.prototype.setCookieManager;
@@ -1339,11 +1538,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) {
// accept a list of objects
this.translator = [];
for(var i in translator) {
- if(typeof(translator[i]) == "object") {
- this.translator.push(translator[i]);
- } else {
- this.translator.push(Zotero.Translators.get(translator[i]));
- }
+ this.translator.push(translator[i]);
}
return true;
} else {
@@ -1436,6 +1631,9 @@ Zotero.Translate.IO = {
/******* String support *******/
+/**
+ * @class Translate backend for translating from a string
+ */
Zotero.Translate.IO.String = function(string, uri, mode) {
if(string && typeof string === "string") {
this._string = string;
diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js
@@ -0,0 +1,503 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+const BOMs = {
+ "UTF-8":"\xEF\xBB\xBF",
+ "UTF-16BE":"\xFE\xFF",
+ "UTF-16LE":"\xFF\xFE",
+ "UTF-32BE":"\x00\x00\xFE\xFF",
+ "UTF-32LE":"\xFF\xFE\x00\x00"
+}
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+/**
+ * @class Manages the translator sandbox
+ * @param {Zotero.Translate} translate
+ * @param {String|window} sandboxLocation
+ */
+Zotero.Translate.SandboxManager = function(sandboxLocation) {
+ this.sandbox = new Components.utils.Sandbox(sandboxLocation);
+ this.sandbox.Zotero = {};
+
+ // import functions missing from global scope into Fx sandbox
+ this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
+ this.sandbox.DOMParser = function() {
+ // get URI
+ // DEBUG: In Fx 4 we can just use document.nodePrincipal, but in Fx 3.6 this doesn't work
+ if(typeof sandboxLocation === "string") { // if sandbox specified by URI
+ var uri = sandboxLocation;
+ } else { // if sandbox specified by DOM document
+ var uri = sandboxLocation.location.toString();
+ }
+
+ // get principal from URI
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ uri = ioService.newURI(uri, "UTF-8", null);
+ var principal = secMan.getCodebasePrincipal(uri);
+
+ // initialize DOM parser
+ var _DOMParser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Components.interfaces.nsIDOMParser);
+ _DOMParser.init(principal, uri, uri);
+
+ // expose parseFromString
+ this.__exposedProps__ = {"parseFromString":"r"};
+ this.parseFromString = function(str, contentType) _DOMParser.parseFromString(str, contentType);
+ }
+ this.sandbox.DOMParser.__exposedProps__ = {"prototype":"r"};
+ this.sandbox.DOMParser.prototype = {};
+}
+
+Zotero.Translate.SandboxManager.prototype = {
+ /**
+ * Evaluates code in the sandbox
+ */
+ "eval":function(code) {
+ Components.utils.evalInSandbox(code, this.sandbox);
+ },
+
+ /**
+ * Imports an object into the sandbox
+ *
+ * @param {Object} object Object to be imported (under Zotero)
+ * @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
+ * as the first argument to the function.
+ */
+ "importObject":function(object, passAsFirstArgument, attachTo) {
+ if(!attachTo) attachTo = this.sandbox.Zotero;
+ var newExposedProps = false;
+ if(!object.__exposedProps__) newExposedProps = {};
+ for(var key in (newExposedProps ? object : object.__exposedProps__)) {
+ let localKey = key;
+ if(newExposedProps) newExposedProps[localKey] = "r";
+
+ // magical XPCSafeJSObjectWrappers for sandbox
+ if(typeof object[localKey] === "function" || typeof object[localKey] === "object") {
+ if(attachTo == this.sandbox) Zotero.debug(localKey);
+ attachTo[localKey] = function() {
+ var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
+ for(var i=0; i<arguments.length; i++) {
+ args.push((typeof arguments[i] === "object" && arguments[i] !== null)
+ || typeof arguments[i] === "function"
+ ? new XPCSafeJSObjectWrapper(arguments[i]) : arguments[i]);
+ }
+
+ return object[localKey].apply(object, args);
+ };
+ attachTo[localKey].name = localKey;
+
+ // attach members
+ if(!(object instanceof Components.interfaces.nsISupports)) {
+ this.importObject(object[localKey], passAsFirstArgument ? passAsFirstArgument : null, attachTo[localKey]);
+ }
+ } else {
+ attachTo[localKey] = object[localKey];
+ }
+ }
+
+ if(newExposedProps) {
+ attachTo.__exposedProps__ = newExposedProps;
+ } else {
+ attachTo.__exposedProps__ = object.__exposedProps__;
+ }
+ }
+}
+
+/**
+ * This variable holds a reference to all open nsIInputStreams and nsIOutputStreams in the global
+ * scope at all times. Otherwise, our streams might get garbage collected when we allow other code
+ * to run during Zotero.wait().
+ */
+Zotero.Translate.IO.maintainedInstances = [];
+
+/******* (Native) Read support *******/
+
+Zotero.Translate.IO.Read = function(file, mode) {
+ Zotero.Translate.IO.maintainedInstances.push(this);
+
+ this.file = file;
+
+ // open file
+ this._openRawStream();
+
+ // start detecting charset
+ var charset = null;
+
+ // look for a BOM in the document
+ var binStream = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ binStream.setInputStream(this._rawStream);
+ var first4 = binStream.readBytes(4);
+
+ for(var possibleCharset in BOMs) {
+ if(first4.substr(0, BOMs[possibleCharset].length) == BOMs[possibleCharset]) {
+ this._charset = possibleCharset;
+ break;
+ }
+ }
+
+ if(this._charset) {
+ // BOM found; store its length and go back to the beginning of the file
+ this._bomLength = BOMs[this._charset].length;
+ this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
+ .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
+ } else {
+ // look for an XML parse instruction
+ this._bomLength = 0;
+
+ var sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ sStream.init(this._rawStream);
+
+ // read until we see if the file begins with a parse instruction
+ const whitespaceRe = /\s/g;
+ var read;
+ do {
+ read = sStream.read(1);
+ } while(whitespaceRe.test(read))
+
+ if(read == "<") {
+ var firstPart = read + sStream.read(4);
+ if(firstPart == "<?xml") {
+ // got a parse instruction, read until it ends
+ read = true;
+ while((read !== false) && (read !== ">")) {
+ read = sStream.read(1);
+ firstPart += read;
+ }
+
+ const encodingRe = /encoding=['"]([^'"]+)['"]/;
+ var m = encodingRe.exec(firstPart);
+ if(m) {
+ try {
+ var charconv = Components.classes["@mozilla.org/charset-converter-manager;1"]
+ .getService(Components.interfaces.nsICharsetConverterManager)
+ .getCharsetTitle(m[1]);
+ if(charconv) this._charset = m[1];
+ } catch(e) {}
+ }
+
+ // if we know for certain document is XML, we also know for certain that the
+ // default charset for XML is UTF-8
+ if(!this._charset) this._charset = "UTF-8";
+ }
+ }
+
+ // If we managed to get a charset here, then translators shouldn't be able to override it,
+ // since it's almost certainly correct. Otherwise, we allow override.
+ this._allowCharsetOverride = !!this._charset;
+ this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
+ .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
+
+ if(!this._charset) {
+ // No XML parse instruction or BOM.
+
+ // Check whether the user has specified a charset preference
+ var charsetPref = Zotero.Prefs.get("import.charset");
+ if(charsetPref == "auto") {
+ Zotero.debug("Translate: Checking whether file is UTF-8");
+ // For auto-detect, we are basically going to check if the file could be valid
+ // UTF-8, and if this is true, we will treat it as UTF-8. Prior likelihood of
+ // UTF-8 is very high, so this should be a reasonable strategy.
+
+ // from http://codex.wordpress.org/User:Hakre/UTF8
+ const UTF8Regex = new RegExp('^(?:' +
+ '[\x09\x0A\x0D\x20-\x7E]' + // ASCII
+ '|[\xC2-\xDF][\x80-\xBF]' + // non-overlong 2-byte
+ '|\xE0[\xA0-\xBF][\x80-\xBF]' + // excluding overlongs
+ '|[\xE1-\xEC\xEE][\x80-\xBF]{2}' + // 3-byte, but exclude U-FFFE and U-FFFF
+ '|\xEF[\x80-\xBE][\x80-\xBF]' +
+ '|\xEF\xBF[\x80-\xBD]' +
+ '|\xED[\x80-\x9F][\x80-\xBF]' + // excluding surrogates
+ '|\xF0[\x90-\xBF][\x80-\xBF]{2}' + // planes 1-3
+ '|[\xF1-\xF3][\x80-\xBF]{3}' + // planes 4-15
+ '|\xF4[\x80-\x8F][\x80-\xBF]{2}' + // plane 16
+ ')*$');
+
+ // Read all currently available bytes from file. This seems to be the entire file,
+ // since the IO is blocking anyway.
+ this._charset = "UTF-8";
+ let bytesAvailable;
+ while(bytesAvailable = this._rawStream.available()) {
+ // read 131072 bytes
+ let fileContents = binStream.readBytes(Math.min(131072, bytesAvailable));
+
+ // on failure, try reading up to 3 more bytes and see if that makes this
+ // valid (since we have chunked it)
+ let isUTF8;
+ for(let i=1; !(isUTF8 = UTF8Regex.test(fileContents)) && i <= 3; i++) {
+ if(this._rawStream.available()) {
+ fileContents += binStream.readBytes(1);
+ }
+ }
+
+ // if the regexp continues to fail, this is not UTF-8
+ if(!isUTF8) {
+ // Can't be UTF-8; see if a default charset is defined
+ this._charset = Zotero.Prefs.get("intl.charset.default", true);
+
+ // ISO-8859-1 by default
+ if(!this._charset) this._charset = "ISO-8859-1";
+
+ break;
+ }
+ }
+ } else {
+ // No need to auto-detect; user has specified a charset
+ this._charset = charsetPref;
+ }
+ }
+ }
+
+ Zotero.debug("Translate: Detected file charset as "+this._charset);
+
+ // We know the charset now. Open a converter stream.
+ if(mode) this.reset(mode);
+}
+
+Zotero.Translate.IO.Read.prototype = {
+ "__exposedProps__":{
+ "_getXML":"r",
+ "RDF":"r",
+ "read":"r",
+ "setCharacterSet":"r"
+ },
+
+ "_openRawStream":function() {
+ if(this._rawStream) this._rawStream.close();
+ this._rawStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ this._rawStream.init(this.file, 0x01, 0664, 0);
+ },
+
+ "_seekToStart":function(charset) {
+ this._openRawStream();
+
+ this._linesExhausted = false;
+ this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream)
+ .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength);
+ this.bytesRead = this._bomLength;
+
+ this.inputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterInputStream);
+ this.inputStream.init(this._rawStream, charset, 32768,
+ Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ },
+
+ "_readToString":function() {
+ var str = {};
+ var stringBits = [];
+ this.inputStream.QueryInterface(Components.interfaces.nsIUnicharInputStream);
+ while(1) {
+ var read = this.inputStream.readString(32768, str);
+ if(!read) break;
+ stringBits.push(str.value);
+ }
+ return stringBits.join("");
+ },
+
+ "_initRDF":function() {
+ // call Zotero.wait() to do UI repaints
+ Zotero.wait();
+
+ // get URI
+ var IOService = Components.classes['@mozilla.org/network/io-service;1']
+ .getService(Components.interfaces.nsIIOService);
+ var fileHandler = IOService.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var baseURI = fileHandler.getURLSpecFromFile(this.file);
+
+ Zotero.debug("Translate: Initializing RDF data store");
+ this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula();
+ var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore);
+ try {
+ var nodes = Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize);
+ parser.parse(nodes, baseURI);
+
+ this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore);
+ } catch(e) {
+ this.close();
+ throw "Translate: No RDF found";
+ }
+ },
+
+ "setCharacterSet":function(charset) {
+ if(typeof charset !== "string") {
+ throw "Translate: setCharacterSet: charset must be a string";
+ }
+
+ // seek back to the beginning
+ this._seekToStart(this._allowCharsetOverride ? this._allowCharsetOverride : this._charset);
+
+ if(!_allowCharsetOverride) {
+ Zotero.debug("Translate: setCharacterSet: translate charset override ignored due to BOM or XML parse instruction");
+ }
+ },
+
+ "read":function(bytes) {
+ var str = {};
+
+ if(bytes) {
+ // read number of bytes requested
+ this.inputStream.QueryInterface(Components.interfaces.nsIUnicharInputStream);
+ var amountRead = this.inputStream.readString(bytes, str);
+ if(!amountRead) return false;
+ this.bytesRead += amountRead;
+ } else {
+ // bytes not specified; read a line
+ this.inputStream.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);
+ if(this._linesExhausted) return false;
+ this._linesExhausted = !this.inputStream.readLine(str);
+ this.bytesRead += str.value.length+1; // only approximate
+ }
+
+ return str.value;
+ },
+
+ "_getXML":function() {
+ if(this._mode == "xml/dom") {
+ return Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize);
+ } else {
+ return this._readToString().replace(/<\?xml[^>]+\?>/, "");
+ }
+ },
+
+ "reset":function(newMode) {
+ if(Zotero.Translate.IO.maintainedInstances.indexOf(this) === -1) {
+ Zotero.Translate.IO.maintainedInstances.push(this);
+ }
+ this._seekToStart(this._charset);
+
+ this._mode = newMode;
+ if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && !this.RDF) {
+ this._initRDF();
+ }
+ },
+
+ "close":function() {
+ var myIndex = Zotero.Translate.IO.maintainedInstances.indexOf(this);
+ if(myIndex !== -1) Zotero.Translate.IO.maintainedInstances.splice(myIndex, 1);
+
+ if(this._rawStream) {
+ this._rawStream.close();
+ delete this._rawStream;
+ }
+ }
+}
+Zotero.Translate.IO.Read.prototype.__defineGetter__("contentLength",
+function() {
+ return this.file.fileSize;
+});
+
+/******* Write support *******/
+
+Zotero.Translate.IO.Write = function(file, mode, charset) {
+ Zotero.Translate.IO.maintainedInstances.push(this);
+ this._rawStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ this._rawStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
+ this._writtenToStream = false;
+ if(mode || charset) this.reset(mode, charset);
+}
+
+Zotero.Translate.IO.Write.prototype = {
+ "__exposedProps__":{
+ "RDF":"r",
+ "write":"r",
+ "setCharacterSet":"r"
+ },
+
+ "_initRDF":function() {
+ Zotero.debug("Translate: Initializing RDF data store");
+ this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula();
+ this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore);
+ },
+
+ "setCharacterSet":function(charset) {
+ if(typeof charset !== "string") {
+ throw "Translate: setCharacterSet: charset must be a string";
+ }
+
+ if(!this.outputStream) {
+ this.outputStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterOutputStream);
+ }
+
+ if(charset == "UTF-8xBOM") charset = "UTF-8";
+ this.outputStream.init(this._rawStream, charset, 1024, "?".charCodeAt(0));
+ this._charset = charset;
+ },
+
+ "write":function(data) {
+ if(!this._charset) this.setCharacterSet("UTF-8");
+
+ if(!this._writtenToStream && this._charset.substr(this._charset.length-4) == "xBOM"
+ && BOMs[this._charset.substr(0, this._charset.length-4).toUpperCase()]) {
+ // If stream has not yet been written to, and a UTF type has been selected, write BOM
+ this._rawStream.write(BOMs[streamCharset], BOMs[streamCharset].length);
+ }
+
+ if(this._charset == "MACINTOSH") {
+ // fix buggy Mozilla MacRoman
+ var splitData = data.split(/([\r\n]+)/);
+ for(var i=0; i<splitData.length; i+=2) {
+ // write raw newlines straight to the string
+ this.outputStream.writeString(splitData[i]);
+ if(splitData[i+1]) {
+ this._rawStream.write(splitData[i+1], splitData[i+1].length);
+ }
+ }
+ } else {
+ this.outputStream.writeString(data);
+ }
+
+ this._writtenToStream = true;
+ },
+
+ "reset":function(newMode, charset) {
+ this._mode = newMode;
+ if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) {
+ this._initRDF();
+ if(!this._writtenToString) this.setCharacterSet("UTF-8");
+ } else if(!this._writtenToString) {
+ this.setCharacterSet(charset ? charset : "UTF-8");
+ }
+ },
+
+ "close":function() {
+ if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) {
+ this.write(this.RDF.serialize());
+ }
+
+ var myIndex = Zotero.Translate.IO.maintainedInstances.indexOf(this);
+ if(myIndex !== -1) Zotero.Translate.IO.maintainedInstances.splice(myIndex, 1);
+
+ this._rawStream.close();
+ }
+}
diff --git a/chrome/content/zotero/xpcom/translation/item_local.js b/chrome/content/zotero/xpcom/translation/translate_item.js
diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js
@@ -149,18 +149,153 @@ Zotero.Translators = new function() {
/**
* Gets the translator that corresponds to a given ID
+ * @param {String} id The ID of the translator
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned.
*/
- this.get = function(id) {
+ this.get = function(id, callback) {
if(!_initialized) this.init();
- return _translators[id] ? _translators[id] : false;
+ var translator = _translators[id] ? _translators[id] : false;
+
+ if(callback) {
+ callback(translator);
+ return true;
+ }
+ return translator;
+ }
+
+ /**
+ * Gets all translators for a specific type of translation
+ * @param {String} type The type of translators to get (import, export, web, or search)
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned.
+ */
+ this.getAllForType = function(type, callback) {
+ if(!_initialized) this.init()
+
+ var translators = _cache[type].slice(0);
+ if(callback) {
+ callback(translators);
+ return true;
+ }
+ return translators;
}
/**
* Gets all translators for a specific type of translation
*/
- this.getAllForType = function(type) {
+ this.getAll = function() {
if(!_initialized) this.init();
- return _cache[type].slice(0);
+ return [translator for each(translator in _translators)];
+ }
+
+ /**
+ * Gets web translators for a specific location
+ * @param {String} uri The URI for which to look for translators
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned. The callback is passed a set of functions for
+ * converting URLs from proper to proxied forms as the second
+ * argument.
+ */
+ this.getWebTranslatorsForLocation = function(uri, callback) {
+ var allTranslators = this.getAllForType("web");
+ var potentialTranslators = [];
+
+ var properHosts = [];
+ var proxyHosts = [];
+
+ var properURI = Zotero.Proxies.proxyToProper(uri);
+ var knownProxy = properURI !== uri;
+ if(knownProxy) {
+ // if we know this proxy, just use the proper URI for detection
+ var searchURIs = [properURI];
+ } else {
+ var searchURIs = [uri];
+
+ // if there is a subdomain that is also a TLD, also test against URI with the domain
+ // dropped after the TLD
+ // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
+ var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
+ if(m) {
+ var hostnames = m[2].split(".");
+ for(var i=1; i<hostnames.length-2; i++) {
+ if(TLDS[hostnames[i].toLowerCase()]) {
+ var properHost = hostnames.slice(0, i+1).join(".");
+ searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
+ properHosts.push(properHost);
+ proxyHosts.push(hostnames.slice(i+1).join("."));
+ }
+ }
+ }
+ }
+
+ Zotero.debug("Translators: Looking for translators for "+searchURIs.join(", "));
+
+ var converterFunctions = [];
+ for(var i=0; i<allTranslators.length; i++) {
+ for(var j=0; j<searchURIs.length; j++) {
+ if((!allTranslators[i].webRegexp
+ && allTranslators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER)
+ || (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
+ // add translator to list
+ potentialTranslators.push(allTranslators[i]);
+
+ if(j === 0) {
+ if(knownProxy) {
+ converterFunctions.push(Zotero.Proxies.properToProxy);
+ } else {
+ converterFunctions.push(null);
+ }
+ } else {
+ converterFunctions.push(new function() {
+ var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
+ var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
+ return function(uri) { return uri.replace(re, "$&."+proxyHost) };
+ });
+ }
+
+ // don't add translator more than once
+ break;
+ }
+ }
+ }
+
+ if(callback) {
+ callback([potentialTranslators, converterFunctions]);
+ return true;
+ }
+ return potentialTranslators;
+ }
+
+ /**
+ * Gets import translators for a specific location
+ * @param {String} location The location for which to look for translators
+ * @param {Function} [callback] An optional callback to be executed when translators have been
+ * retrieved. If no callback is specified, translators are
+ * returned.
+ */
+ this.getImportTranslatorsForLocation = function(location, callback) {
+ var allTranslators = Zotero.Translators.getAllForType("import");
+ var tier1Translators = [];
+ var tier2Translators = [];
+
+ for(var i=0; i<allTranslators.length; i++) {
+ if(allTranslators[i].importRegexp.test(location)) {
+ tier1Translators.push(allTranslators[i]);
+ } else {
+ tier2Translators.push(allTranslators[i]);
+ }
+ }
+
+ var translators = tier1Translators.concat(tier2Translators);
+ if(callback) {
+ callback(translators);
+ return true;
+ }
+ return translators;
}
/**
@@ -171,7 +306,6 @@ Zotero.Translators = new function() {
return Zotero.File.getValidFileName(label) + ".js";
}
-
/**
* @param {String} metadata
* @param {String} metadata.translatorID Translator GUID
@@ -262,29 +396,6 @@ Zotero.Translators = new function() {
}
}
-/**
- * @class Represents an individual translator
- * @constructor
- * @param {nsIFile} file File from which to generate a translator object
- * @property {String} translatorID Unique GUID of the translator
- * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
- * @property {String} label Human-readable name of the translator
- * @property {String} creator Author(s) of the translator
- * @property {String} target Location that the translator processes
- * @property {String} minVersion Minimum Zotero version
- * @property {String} maxVersion Minimum Zotero version
- * @property {Integer} priority Lower-priority translators will be selected first
- * @property {String} browserSupport String indicating browser supported by the translator
- * g = Gecko (Firefox)
- * c = Google Chrome (WebKit & V8)
- * s = Safari (WebKit & Nitro/Squirrelfish Extreme)
- * i = Internet Explorer
- * @property {Object} configOptions Configuration options for import/export
- * @property {Object} displayOptions Display options for export
- * @property {Boolean} inRepository Whether the translator may be found in the repository
- * @property {String} lastUpdated SQL-style date and time of translator's last update
- * @property {String} code The executable JavaScript for the translator
- */
Zotero.Translator = function(file, json, code) {
const codeGetterFunction = function() { return Zotero.File.getContents(this.file); }
// Maximum length for the info JSON in a translator
@@ -355,6 +466,7 @@ Zotero.Translator = function(file, json, code) {
this._configOptions = info["configOptions"] ? info["configOptions"] : {};
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
+ this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
// compile import regexp to match only file extension
@@ -374,7 +486,6 @@ Zotero.Translator = function(file, json, code) {
try {
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
} catch(e) {
- if(fStream) fStream.close();
this.logError("Invalid target in " + file.leafName);
this.webRegexp = null;
if(fStream) fStream.close();
@@ -420,4 +531,8 @@ Zotero.Translator.prototype.logError = function(message, type, line, lineNumber,
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec);
-}
-\ No newline at end of file
+}
+
+Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
+Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
+Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
@@ -595,6 +595,17 @@ Zotero.Utilities = {
} catch(e) {
return false;
}
+ },
+
+ /**
+ * Escapes metacharacters in a literal so that it may be used in a regular expression
+ */
+ "quotemeta":function(literal) {
+ if(typeof literal !== "string") {
+ throw "Argument "+literal+" must be a string in Zotero.Utilities.quotemeta()";
+ }
+ const metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g;
+ return literal.replace(metaRegexp, "\\$&");
}
}
@@ -746,13 +757,11 @@ Zotero.Utilities.Translate.prototype.loadDocument = function(url, succeeded, fai
* @ignore
*/
Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) {
- if(this._translate.locationIsProxied) {
- if(typeof(urls) == "string") {
- urls = [this._convertURL(urls)];
- } else {
- for(var i in urls) {
- urls[i] = this._convertURL(urls[i]);
- }
+ if(typeof(urls) == "string") {
+ urls = [this._convertURL(urls)];
+ } else {
+ for(var i in urls) {
+ urls[i] = this._convertURL(urls[i]);
}
}
@@ -776,7 +785,7 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
* @return {Document} DOM document object
*/
Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
- if(this._translate.locationIsProxied) url = this._convertURL(url);
+ url = this._convertURL(url);
var mainThread = Zotero.mainThread;
var loaded = false;
@@ -822,7 +831,7 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, header
/* Apparently, a synchronous XMLHttpRequest would have the behavior of this routine in FF3, but
* in FF3.5, synchronous XHR blocks all JavaScript on the thread. See
* http://hacks.mozilla.org/2009/07/synchronous-xhr/. */
- if(this._translate.locationIsProxied) url = this._convertURL(url);
+ url = this._convertURL(url);
if(!headers) headers = null;
if(!responseCharset) responseCharset = null;
@@ -911,15 +920,25 @@ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, header
*/
Zotero.Utilities.Translate.prototype._convertURL = function(url) {
const protocolRe = /^(?:(?:http|https|ftp):)/i;
- const fileRe = /^[^:]*/;
- if(this._translate.locationIsProxied) {
- url = Zotero.Proxies.properToProxy(url);
+ // convert proxy to proper if applicable
+ if(this._translate.translator && this._translate.translator[0]
+ && this._translate.translator[0].properToProxy) {
+ url = this._translate.translator[0].properToProxy(url);
}
- if(protocolRe.test(url)) return url;
- if(!fileRe.test(url)) {
- throw "Invalid URL supplied for HTTP request";
+
+ if(Zotero.isChrome || Zotero.isSafari) {
+ // this code is sandboxed, so we don't worry
+ return url;
} else {
+ if(protocolRe.test(url)) return url;
+
+ if(uri.indexOf(":") !== -1) {
+ // don't allow protocol switches
+ throw "Invalid URL supplied for HTTP request";
+ }
+
+ // resolve relative URIs
return Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService).
newURI(this._translate.location, "", null).resolve(url);
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -36,6 +36,8 @@ const ZOTERO_CONFIG = {
PREF_BRANCH: 'extensions.zotero.'
};
+const ZOTERO_METAREGEXP = /[-[\]{}()*+?.\\^$|,#\s]/g;
+
// Fx4.0b8+ use implicit SJOWs and get rid of explicit XPCSafeJSObjectWrapper constructor
// Ugly hack to get around this until we can just kill the XPCSafeJSObjectWrapper calls (when we
// drop Fx3.6 support)
@@ -45,10 +47,17 @@ try {
eval("var XPCSafeJSObjectWrapper = function(arg) { return arg }");
}
+// Load AddonManager for Firefox 4
+var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
+ getService(Components.interfaces.nsIXULAppInfo);
+if(appInfo.platformVersion[0] >= 2) {
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+}
+
/*
* Core functions
*/
-var Zotero = new function(){
+ (function(){
// Privileged (public) methods
this.init = init;
this.stateCheck = stateCheck;
@@ -173,34 +182,39 @@ var Zotero = new function(){
var _locked;
var _unlockCallbacks = [];
+ var _shutdownListeners = [];
var _progressMeters;
var _lastPercentage;
+ // whether we are waiting for another Zotero process to release its DB lock
+ var _waitingForDBLock = false;
+ // whether we are waiting for another Zotero process to initialize so we can use connector
+ var _waitingForInitComplete = false;
+
+ // whether we should broadcast an initComplete message when initialization finishes (we should
+ // do this if we forced another Zotero process to release its lock)
+ var _broadcastInitComplete = false;
+
/**
* A set of nsITimerCallbacks to be executed when Zotero.wait() completes
*/
var _waitTimerCallbacks = [];
- /*
+ /**
* Initialize the extension
*/
- function init(){
+ function init() {
if (this.initialized || this.skipLoading) {
return false;
}
- var start = (new Date()).getTime()
+ var start = (new Date()).getTime();
- // Register shutdown handler to call Zotero.shutdown()
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
- observerService.addObserver({
- observe: Zotero.shutdown
- }, "quit-application", false);
// Load in the preferences branch for the extension
Zotero.Prefs.init();
-
Zotero.Debug.init();
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
@@ -214,6 +228,7 @@ var Zotero = new function(){
this.isFx31 = this.isFx35;
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
this.isFx4 = appInfo.platformVersion[0] >= 2;
+ this.isFx5 = appInfo.platformVersion[0] >= 5;
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
if(this.isStandalone) {
@@ -242,6 +257,9 @@ var Zotero = new function(){
this.isLinux = (this.platform.substr(0, 5) == "Linux");
this.oscpu = win.navigator.oscpu;
+ // Browser
+ Zotero.browser = "g";
+
// Locale
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
@@ -275,19 +293,19 @@ var Zotero = new function(){
xmlhttp.send(null);
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
if (matches && matches[0] == 'rtl') {
- this.dir = 'rtl';
+ Zotero.dir = 'rtl';
}
else {
- this.dir = 'ltr';
+ Zotero.dir = 'ltr';
}
try {
- var dataDir = this.getZoteroDirectory();
+ var dataDir = Zotero.getZoteroDirectory();
}
catch (e) {
// Zotero dir not found
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
- this.startupError = Zotero.getString('dataDir.notFound');
+ Zotero.startupError = Zotero.getString('dataDir.notFound');
_startupErrorHandler = function() {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
@@ -300,7 +318,7 @@ var Zotero = new function(){
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
var index = ps.confirmEx(win,
Zotero.getString('general.error'),
- this.startupError + '\n\n' +
+ Zotero.startupError + '\n\n' +
Zotero.getString('dataDir.previousDir') + ' '
+ Zotero.Prefs.get('lastDataDir'),
buttonFlags, null,
@@ -382,7 +400,6 @@ var Zotero = new function(){
else if (index == 2) {
Zotero.chooseZoteroDirectory(true);
}
- var dataDir = this.getZoteroDirectory();
}
// DEBUG: handle more startup errors
else {
@@ -391,6 +408,54 @@ var Zotero = new function(){
}
}
+ Zotero.IPC.init();
+
+ // Load additional info for connector or not
+ if(Zotero.isConnector) {
+ Zotero.debug("Loading in connector mode");
+ Zotero.Connector.init();
+ } else {
+ Zotero.debug("Loading in full mode");
+ _initFull();
+ }
+
+ this.initialized = true;
+
+ // Register shutdown handler to call Zotero.shutdown()
+ var _shutdownObserver = {observe:Zotero.shutdown};
+ observerService.addObserver(_shutdownObserver, "quit-application", false);
+
+ // Add shutdown listerner to remove observer
+ this.addShutdownListener(function() {
+ observerService.removeObserver(_shutdownObserver, "quit-application", false);
+ });
+
+ Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
+
+ if(!Zotero.isFirstLoadThisSession) {
+ if(Zotero.isConnector) {
+ // wait for initComplete message if we switched to connector because standalone was
+ // started
+ _waitingForInitComplete = true;
+ while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
+ }
+
+ // trigger zotero-reloaded event
+ Zotero.debug('Triggering "zotero-reloaded" event');
+ observerService.notifyObservers(Zotero, "zotero-reloaded", null);
+ }
+
+ // Broadcast initComplete message if desired
+ if(_broadcastInitComplete) Zotero.IPC.broadcast("initComplete");
+
+ return true;
+ }
+
+ /**
+ * Initialization function to be called only if Zotero is in full mode
+ */
+ function _initFull() {
+ var dataDir = Zotero.getZoteroDirectory();
Zotero.VersionHeader.init();
// Check for DB restore
@@ -412,7 +477,7 @@ var Zotero = new function(){
Zotero.Schema.skipDefaultData = true;
Zotero.Schema.updateSchema();
- this.restoreFromServer = true;
+ Zotero.restoreFromServer = true;
}
catch (e) {
// Restore from backup?
@@ -420,54 +485,7 @@ var Zotero = new function(){
}
}
- try {
- // Test read access
- Zotero.DB.test();
-
- var dbfile = Zotero.getZoteroDatabase();
-
- // Test write access on Zotero data directory
- if (!dbfile.parent.isWritable()) {
- var msg = 'Cannot write to ' + dbfile.parent.path + '/';
- }
- // Test write access on Zotero database
- else if (!dbfile.isWritable()) {
- var msg = 'Cannot write to ' + dbfile.path;
- }
- else {
- var msg = false;
- }
-
- if (msg) {
- var e = {
- name: 'NS_ERROR_FILE_ACCESS_DENIED',
- message: msg,
- toString: function () {
- return this.name + ': ' + this.message;
- }
- };
- throw (e);
- }
- }
- catch (e) {
- if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
- var msg = Zotero.localeJoin([
- Zotero.getString('startupError.databaseCannotBeOpened'),
- Zotero.getString('startupError.checkPermissions')
- ]);
- this.startupError = msg;
- } else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
- var msg = Zotero.localeJoin([
- Zotero.getString('startupError.databaseInUse'),
- Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
- ]);
- this.startupError = msg;
- }
-
- Components.utils.reportError(e);
- this.skipLoading = true;
- return;
- }
+ if(!_initDB()) return;
// Add notifier queue callbacks to the DB layer
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
@@ -477,7 +495,7 @@ var Zotero = new function(){
Zotero.Fulltext.init();
// Require >=2.1b3 database to ensure proper locking
- if (this.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
+ if (Zotero.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
.getService(Components.interfaces.nsIAppStartup);
@@ -541,7 +559,7 @@ var Zotero = new function(){
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
}
- this.skipLoading = true;
+ Zotero.skipLoading = true;
return false;
}
@@ -549,7 +567,7 @@ var Zotero = new function(){
if (Zotero.Schema.userDataUpgradeRequired()) {
var upgraded = Zotero.Schema.showUpgradeWizard();
if (!upgraded) {
- this.skipLoading = true;
+ Zotero.skipLoading = true;
return false;
}
}
@@ -567,12 +585,12 @@ var Zotero = new function(){
]) + "\n\n"
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
+ Zotero.getString('general.seeForMoreInformation', kbURL);
- this.startupError = msg;
+ Zotero.startupError = msg;
}
else {
- this.startupError = Zotero.getString('startupError.databaseUpgradeError');
+ Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError');
}
- this.skipLoading = true;
+ Zotero.skipLoading = true;
Components.utils.reportError(e);
return false;
}
@@ -589,8 +607,8 @@ var Zotero = new function(){
// Initialize various services
Zotero.Integration.init();
- if(Zotero.Prefs.get("connector.enabled")) {
- Zotero.Connector.init();
+ if(Zotero.Prefs.get("httpServer.enabled")) {
+ Zotero.Server.init();
}
Zotero.Zeroconf.init();
@@ -607,18 +625,108 @@ var Zotero = new function(){
// Initialize Locate Manager
Zotero.LocateManager.init();
- this.initialized = true;
- Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
+ return true;
+ }
+
+ /**
+ * Initializes the DB connection
+ */
+ function _initDB() {
+ try {
+ // Test read access
+ Zotero.DB.test();
+
+ var dbfile = Zotero.getZoteroDatabase();
+
+ // Test write access on Zotero data directory
+ if (!dbfile.parent.isWritable()) {
+ var msg = 'Cannot write to ' + dbfile.parent.path + '/';
+ }
+ // Test write access on Zotero database
+ else if (!dbfile.isWritable()) {
+ var msg = 'Cannot write to ' + dbfile.path;
+ }
+ else {
+ var msg = false;
+ }
+
+ if (msg) {
+ var e = {
+ name: 'NS_ERROR_FILE_ACCESS_DENIED',
+ message: msg,
+ toString: function () {
+ return Zotero.name + ': ' + Zotero.message;
+ }
+ };
+ throw (e);
+ }
+ }
+ catch (e) {
+ if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
+ var msg = Zotero.localeJoin([
+ Zotero.getString('startupError.databaseCannotBeOpened'),
+ Zotero.getString('startupError.checkPermissions')
+ ]);
+ Zotero.startupError = msg;
+ } else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
+ if(Zotero.isStandalone) {
+ // Standalone should force Fx to release lock
+ if(Zotero.IPC.broadcast("releaseLock")) {
+ _waitingForDBLock = true;
+ while(_waitingForDBLock) Zotero.mainThread.processNextEvent(true);
+ // we will want to broadcast when initialization completes
+ _broadcastInitComplete = true;
+ return _initDB();
+ }
+ } else {
+ // Fx should start as connector if Standalone is running
+ var haveStandalone = Zotero.IPC.broadcast("test");
+ if(haveStandalone) {
+ throw "ZOTERO_SHOULD_START_AS_CONNECTOR";
+ }
+ }
+
+ var msg = Zotero.localeJoin([
+ Zotero.getString('startupError.databaseInUse'),
+ Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
+ ]);
+ Zotero.startupError = msg;
+ }
+
+ Components.utils.reportError(e);
+ Zotero.skipLoading = true;
+ return false;
+ }
return true;
}
+ /**
+ * Called when the DB has been released by another Zotero process to perform necessary
+ * initialization steps
+ */
+ this.onDBLockReleased = function() {
+ if(Zotero.isConnector) {
+ // if DB lock is released, switch out of connector mode
+ switchConnectorMode(false);
+ } else if(_waitingForDBLock) {
+ // if waiting for DB lock and we get it, continue init
+ _waitingForDBLock = false;
+ }
+ }
+
+ /**
+ * Called when an accessory process has been initialized to let use get data
+ */
+ this.onInitComplete = function() {
+ _waitingForInitComplete = false;
+ }
/*
* Check if a DB transaction is open and, if so, disable Zotero
*/
function stateCheck() {
- if (Zotero.DB.transactionInProgress()) {
+ if(!Zotero.isConnector && Zotero.DB.transactionInProgress()) {
this.initialized = false;
this.skipLoading = true;
return false;
@@ -630,7 +738,33 @@ var Zotero = new function(){
this.shutdown = function (subject, topic, data) {
Zotero.debug("Shutting down Zotero");
- Zotero.removeTempDirectory();
+
+ try {
+ // run shutdown listener
+ for each(var listener in _shutdownListeners) listener();
+
+ // remove temp directory
+ Zotero.removeTempDirectory();
+
+ if(Zotero.initialized && Zotero.DB) {
+ Zotero.debug("Closing database");
+
+ // run GC to finalize open statements
+ // TODO remove this and finalize statements created with
+ // Zotero.DBConnection.getStatement() explicitly
+ Components.utils.forceGC();
+
+ // unlock DB
+ Zotero.DB.closeDatabase();
+
+ // broadcast that DB lock has been released
+ Zotero.IPC.broadcast("lockReleased");
+ }
+ } catch(e) {
+ Zotero.debug(e);
+ throw e;
+ }
+
return true;
}
@@ -1511,6 +1645,12 @@ var Zotero = new function(){
return true;
}
+ /**
+ * Adds a listener to be called when Zotero shuts down (even if Firefox is not shut down)
+ */
+ this.addShutdownListener = function(listener) {
+ _shutdownListeners.push(listener);
+ }
function _showWindowZoteroPaneOverlay(doc) {
doc.getElementById('zotero-collections-tree').disabled = true;
@@ -1658,9 +1798,21 @@ var Zotero = new function(){
Zotero.Creators.reloadAll();
Zotero.Items.reloadAll();
}
-};
-
-
+
+ /**
+ * Brings Zotero Standalone to the foreground
+ */
+ this.activateStandalone = function() {
+ var io = Components.classes['@mozilla.org/network/io-service;1']
+ .getService(Components.interfaces.nsIIOService);
+ var uri = io.newURI('zotero://select', null, null);
+ var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1']
+ .getService(Components.interfaces.nsIExternalProtocolService)
+ .getProtocolHandlerInfo('zotero');
+ handler.preferredAction = Components.interfaces.nsIHandlerInfo.useSystemDefault;
+ handler.launchWithURI(uri, null);
+ }
+}).call(Zotero);
Zotero.Prefs = new function(){
// Privileged methods
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -90,18 +90,16 @@ var ZoteroPane = new function()
var self = this;
var _loaded = false;
- var titlebarcolorState, titleState;
+ var titlebarcolorState, titleState, observerService;
+ var _reloadFunctions = [];
// Also needs to be changed in collectionTreeView.js
var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/;
- /*
+ /**
* Called when the window containing Zotero pane is open
*/
- function init()
- {
- if(!Zotero || !Zotero.initialized) return;
-
+ function init() {
// Set "Report Errors..." label via property rather than DTD entity,
// since we need to reference it in script elsewhere
document.getElementById('zotero-tb-actions-reportErrors').setAttribute('label',
@@ -116,9 +114,9 @@ var ZoteroPane = new function()
var zp = document.getElementById('zotero-pane');
Zotero.setFontSize(zp);
- this.updateToolbarPosition();
- window.addEventListener("resize", this.updateToolbarPosition, false);
- window.setTimeout(this.updateToolbarPosition, 0);
+ ZoteroPane_Local.updateToolbarPosition();
+ window.addEventListener("resize", ZoteroPane_Local.updateToolbarPosition, false);
+ window.setTimeout(ZoteroPane_Local.updateToolbarPosition, 0);
Zotero.updateQuickSearchBox(document);
@@ -136,10 +134,34 @@ var ZoteroPane = new function()
zp.setAttribute("ignoreActiveAttribute", "true");
}
+ // register an observer for Zotero reload
+ observerService = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ observerService.addObserver(_reload, "zotero-reloaded", false);
+ this.addReloadListener(_loadPane);
+
+ // continue loading pane
+ _loadPane();
+ }
+
+ /**
+ * Called on window load or when has been reloaded after switching into or out of connector
+ * mode
+ */
+ function _loadPane() {
+ if(!Zotero || !Zotero.initialized) return;
+
+ if(Zotero.isConnector) {
+ ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('connector.standaloneOpen'));
+ return;
+ } else {
+ ZoteroPane_Local.clearItemsPaneMessage();
+ }
+
//Initialize collections view
- this.collectionsView = new Zotero.CollectionTreeView();
+ ZoteroPane_Local.collectionsView = new Zotero.CollectionTreeView();
var collectionsTree = document.getElementById('zotero-collections-tree');
- collectionsTree.view = this.collectionsView;
+ collectionsTree.view = ZoteroPane_Local.collectionsView;
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
@@ -147,8 +169,6 @@ var ZoteroPane = new function()
itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
itemsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
- this.buildItemTypeSubMenu();
-
var menu = document.getElementById("contentAreaContextMenu");
menu.addEventListener("popupshowing", ZoteroPane_Local.contextPopupShowing, false);
@@ -322,6 +342,8 @@ var ZoteroPane = new function()
this.collectionsView.unregister();
if (this.itemsView)
this.itemsView.unregister();
+
+ observerService.removeObserver(_reload, "zotero-reloaded", false);
}
/**
@@ -349,6 +371,7 @@ var ZoteroPane = new function()
return false;
}
+ this.buildItemTypeSubMenu();
this.unserializePersist();
this.updateToolbarPosition();
this.updateTagSelectorSize();
@@ -3644,6 +3667,22 @@ var ZoteroPane = new function()
this.openAboutDialog = function() {
window.openDialog('chrome://zotero/content/about.xul', 'about', 'chrome');
}
+
+ /**
+ * Adds or removes a function to be called when Zotero is reloaded by switching into or out of
+ * the connector
+ */
+ this.addReloadListener = function(/** @param {Function} **/func) {
+ if(_reloadFunctions.indexOf(func) === -1) _reloadFunctions.push(func);
+ }
+
+ /**
+ * Called when Zotero is reloaded (i.e., if it is switched into or out of connector mode)
+ */
+ function _reload() {
+ Zotero.debug("Reloading Zotero pane");
+ for each(var func in _reloadFunctions) func();
+ }
}
/**
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
@@ -730,4 +730,6 @@ locate.libraryLookup.label = Library Lookup
locate.libraryLookup.tooltip = Look up this item using the selected OpenURL resolver
locate.manageLocateEngines = Manage Lookup Engines...
-standalone.corruptInstallation = Your Zotero Standalone installation appears to be corrupted due to a failed auto-update. While Zotero may continue to function, to avoid potential bugs, please download the latest version of Zotero Standalone from http://zotero.org/support/standalone as soon as possible.
-\ No newline at end of file
+standalone.corruptInstallation = Your Zotero Standalone installation appears to be corrupted due to a failed auto-update. While Zotero may continue to function, to avoid potential bugs, please download the latest version of Zotero Standalone from http://zotero.org/support/standalone as soon as possible.
+
+connector.standaloneOpen = Your database cannot be accessed because Zotero Standalone is currently open. Please view your items in Zotero Standalone.
+\ No newline at end of file
diff --git a/components/zotero-command-line-handler.js b/components/zotero-command-line-handler.js
@@ -0,0 +1,120 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+
+ Based on nsChromeExtensionHandler example code by Ed Anuff at
+ http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol
+
+ ***** END LICENSE BLOCK *****
+*/
+
+/*
+ Based on nsICommandLineHandler example code at
+ https://developer.mozilla.org/en/Chrome/Command_Line
+*/
+
+const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero";
+const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
+const clh_category = "m-zotero";
+const clh_description = "Zotero Command Line Handler";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * The XPCOM component that implements nsICommandLineHandler.
+ */
+function ZoteroCommandLineHandler() {}
+ZoteroCommandLineHandler.prototype = {
+ /* nsISupports */
+ QueryInterface : XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler,
+ Components.interfaces.nsIFactory, Components.interfaces.nsISupports]),
+
+ /* nsICommandLineHandler */
+ handle : function(cmdLine) {
+ // handler for Zotero integration commands
+ // this is typically used on Windows only, via WM_COPYDATA rather than the command line
+ var agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false);
+ if(agent) {
+ // Don't open a new window
+ cmdLine.preventDefault = true;
+
+ var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
+ var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
+
+ // Not quite sure why this is necessary to get the appropriate scoping
+ var Zotero = this.Zotero;
+ var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback({notify:function() { Zotero.Integration.execCommand(agent, command, docId) }}, 0,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ // handler for Windows IPC commands
+ var param = cmdLine.handleFlagWithParam("ZoteroIPC", false);
+ if(param) {
+ // Don't open a new window
+ cmdLine.preventDefault = true;
+ this.Zotero.IPC.parsePipeInput(param);
+ }
+
+ // special handler for "zotero" URIs at the command line to prevent them from opening a new
+ // window
+ if(this.Zotero.isStandalone) {
+ var param = cmdLine.handleFlagWithParam("url", false);
+ if(param) {
+ var uri = cmdLine.resolveURI(param);
+ if(uri.schemeIs("zotero")) {
+ // Don't open a new window
+ cmdLine.preventDefault = true;
+
+ Components.classes["@mozilla.org/network/protocol;1?name=zotero"]
+ .createInstance(Components.interfaces.nsIProtocolHandler).newChannel(uri);
+ }
+ }
+ }
+ },
+
+ classDescription: clh_description,
+ classID: clh_CID,
+ contractID: clh_contractID,
+ service: true,
+ _xpcom_categories: [{category:"command-line-handler", entry:clh_category}],
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler,
+ Components.interfaces.nsISupports])
+};
+
+ZoteroCommandLineHandler.prototype.__defineGetter__("Zotero", function() {
+ if(!this._Zotero) {
+ this._Zotero = Components.classes["@zotero.org/Zotero;1"]
+ .getService(Components.interfaces.nsISupports).wrappedJSObject;
+ }
+ return this._Zotero;
+});
+
+/**
+* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
+* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
+*/
+if (XPCOMUtils.generateNSGetFactory) {
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroCommandLineHandler]);
+} else {
+ var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroCommandLineHandler]);
+}
+\ No newline at end of file
diff --git a/components/zotero-integration-service.js b/components/zotero-integration-service.js
@@ -1,99 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
-
- Based on nsChromeExtensionHandler example code by Ed Anuff at
- http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol
-
- ***** END LICENSE BLOCK *****
-*/
-
-/*
- Based on nsICommandLineHandler example code at
- https://developer.mozilla.org/en/Chrome/Command_Line
-*/
-
-const nsISupports = Components.interfaces.nsISupports;
-const nsICategoryManager = Components.interfaces.nsICategoryManager;
-const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
-const nsICommandLine = Components.interfaces.nsICommandLine;
-const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
-const nsIFactory = Components.interfaces.nsIFactory;
-const nsIModule = Components.interfaces.nsIModule;
-const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
-
-const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration";
-const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
-const clh_category = "m-zotero-integration";
-const clh_description = "Zotero Integration Command Line Handler";
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-/**
- * The XPCOM component that implements nsICommandLineHandler.
- */
-function ZoteroIntegrationCommandLineHandler() {}
-ZoteroIntegrationCommandLineHandler.prototype = {
- Zotero : null,
-
- /* nsISupports */
- QueryInterface : function(iid) {
- if(iid.equals(nsICommandLineHandler) ||
- iid.equals(nsIFactory) ||
- iid.equals(nsISupports)) return this;
- throw Components.results.NS_ERROR_NO_INTERFACE;
- },
-
- /* nsICommandLineHandler */
- handle : function(cmdLine) {
- var agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false);
- var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
- var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
- if(agent && command) {
- if(!this.Zotero) this.Zotero = Components.classes["@zotero.org/Zotero;1"]
- .getService(Components.interfaces.nsISupports).wrappedJSObject;
- var Zotero = this.Zotero;
- // Not quite sure why this is necessary to get the appropriate scoping
- var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
- timer.initWithCallback({notify:function() { Zotero.Integration.execCommand(agent, command, docId) }}, 0,
- Components.interfaces.nsITimer.TYPE_ONE_SHOT);
- }
- },
-
- classDescription: clh_description,
- classID: clh_CID,
- contractID: clh_contractID,
- service: true,
- _xpcom_categories: [{category:"command-line-handler", entry:clh_category}],
- QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler,
- Components.interfaces.nsISupports])
-};
-
-/**
-* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
-* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
-*/
-if (XPCOMUtils.generateNSGetFactory) {
- var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroIntegrationCommandLineHandler]);
-} else {
- var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroIntegrationCommandLineHandler]);
-}
-\ No newline at end of file
diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js
@@ -852,13 +852,21 @@ function ChromeExtensionHandler() {
var [path, queryString] = uri.path.substr(1).split('?');
var [type, id] = path.split('/');
- //currently only able to select one item
+ // currently only able to select one item
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
- var win = wm.getMostRecentWindow(null);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ // restore window if it's in the dock
+ if(win.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
+ win.restore();
+ }
+
+ // open Zotero pane
win.ZoteroPane.show();
+ if(!id) return;
+
var lkh = Zotero.Items.parseLibraryKeyHash(id);
if (lkh) {
var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key);
@@ -1026,10 +1034,10 @@ function ChromeExtensionHandler() {
try {
var originalURI = uri.path;
originalURI = decodeURIComponent(originalURI.substr(originalURI.indexOf("/")+1));
- if(!Zotero.Connector.Data[originalURI]) {
+ if(!Zotero.Server.Connector.Data[originalURI]) {
return null;
} else {
- return new ConnectorChannel(originalURI, Zotero.Connector.Data[originalURI]);
+ return new ConnectorChannel(originalURI, Zotero.Server.Connector.Data[originalURI]);
}
} catch(e) {
Zotero.debug(e);
diff --git a/components/zotero-service.js b/components/zotero-service.js
@@ -35,30 +35,31 @@ const ZOTERO_IID = Components.interfaces.chnmIZoteroService; //unused
const Cc = Components.classes;
const Ci = Components.interfaces;
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
- getService(Components.interfaces.nsIXULAppInfo);
-if(appInfo.platformVersion[0] >= 2) {
- Components.utils.import("resource://gre/modules/AddonManager.jsm");
-}
-
-// Assign the global scope to a variable to passed via wrappedJSObject
-var ZoteroWrapped = this;
-
-/********************************************************************
-* Include the core objects to be stored within XPCOM
-*********************************************************************/
-
-var xpcomFiles = [
+/** XPCOM files to be loaded for all modes **/
+const xpcomFilesAll = [
'zotero',
+ 'date',
+ 'debug',
+ 'error',
+ 'file',
+ 'http',
+ 'mimeTypeHandler',
+ 'openurl',
+ 'ipc',
+ 'progressWindow',
+ 'translation/translate',
+ 'translation/translate_firefox',
+ 'translation/tlds',
+ 'utilities'
+];
+
+/** XPCOM files to be loaded only for local translation and DB access **/
+const xpcomFilesLocal = [
+ 'collectionTreeView',
'annotate',
'attachments',
'cite',
- 'collectionTreeView',
'commons',
- 'connector',
- 'dataServer',
'data_access',
'data/dataObjects',
'data/cachedTypes',
@@ -79,28 +80,21 @@ var xpcomFiles = [
'data/tags',
'date',
'db',
- 'debug',
'duplicate',
'enstyle',
- 'error',
- 'file',
'fulltext',
- 'http',
'id',
'integration',
- 'integration_compat',
'itemTreeView',
'locateManager',
'mime',
- 'mimeTypeHandler',
'notifier',
- 'openurl',
- 'progressWindow',
'proxy',
'quickCopy',
'report',
'schema',
'search',
+ 'server',
'style',
'sync',
'storage',
@@ -108,117 +102,197 @@ var xpcomFiles = [
'storage/zfs',
'storage/webdav',
'timeline',
- 'translation/translator',
- 'translation/translate',
- 'translation/browser_firefox',
- 'translation/item_local',
'uri',
- 'utilities',
- 'zeroconf'
+ 'zeroconf',
+ 'translation/translate_item',
+ 'translation/translator',
+ 'server_connector'
];
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[0] + ".js");
+/** XPCOM files to be loaded only for connector translation and DB access **/
+const xpcomFilesConnector = [
+ 'connector/translate_item',
+ 'connector/translator',
+ 'connector/connector',
+ 'connector/cachedTypes'
+];
-// Load CiteProc into Zotero.CiteProc namespace
-Zotero.CiteProc = {"Zotero":Zotero};
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/xpcom/citeproc.js", Zotero.CiteProc);
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-for (var i=1; i<xpcomFiles.length; i++) {
- try {
+var instanceID = (new Date()).getTime();
+var isFirstLoadThisSession = true;
+var zContext = null;
+
+ZoteroContext = function() {}
+ZoteroContext.prototype = {
+ /**
+ * Convenience method to replicate window.alert()
+ **/
+ // TODO: is this still used? if so, move to zotero.js
+ "alert":function alert(msg){
+ this.Zotero.debug("alert() is deprecated from Zotero XPCOM");
+ Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .alert(null, "", msg);
+ },
+
+ /**
+ * Convenience method to replicate window.confirm()
+ **/
+ // TODO: is this still used? if so, move to zotero.js
+ "confirm":function confirm(msg){
+ this.Zotero.debug("confirm() is deprecated from Zotero XPCOM");
+ return Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .confirm(null, "", msg);
+ },
+
+ "Cc":Cc,
+ "Ci":Ci,
+
+ /**
+ * Convenience method to replicate window.setTimeout()
+ **/
+ "setTimeout":function setTimeout(func, ms){
+ this.Zotero.setTimeout(func, ms);
+ },
+
+ /**
+ * Switches in or out of connector mode
+ */
+ "switchConnectorMode":function(isConnector) {
+ if(isConnector !== this.isConnector) {
+ zContext.Zotero.shutdown();
+
+ // create a new zContext
+ makeZoteroContext(isConnector);
+ zContext.Zotero.init();
+ }
+
+ return zContext;
+ }
+};
+
+/**
+ * The class from which the Zotero global XPCOM context is constructed
+ *
+ * @constructor
+ * This runs when ZoteroService is first requested to load all applicable scripts and initialize
+ * Zotero. Calls to other XPCOM components must be in here rather than in top-level code, as other
+ * components may not have yet been initialized.
+ */
+function makeZoteroContext(isConnector) {
+ if(zContext) {
+ // Swap out old zContext
+ var oldzContext = zContext;
+ // Create new zContext
+ zContext = new ZoteroContext();
+ // Swap in old Zotero object, so that references don't break, but empty it
+ zContext.Zotero = oldzContext.Zotero;
+ for(var key in zContext.Zotero) delete zContext.Zotero[key];
+ } else {
+ zContext = new ZoteroContext();
+ zContext.Zotero = function() {};
+ }
+
+ // Load zotero.js first
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext);
+
+ // Load CiteProc into Zotero.CiteProc namespace
+ zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc);
+
+ // Load remaining xpcomFiles
+ for (var i=1; i<xpcomFilesAll.length; i++) {
+ try {
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext);
+ }
+ catch (e) {
+ Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext);
+ throw (e);
+ }
+ }
+
+ // Load xpcomFiles for specific mode
+ for each(var xpcomFile in (isConnector ? xpcomFilesConnector : xpcomFilesLocal)) {
+ try {
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/xpcom/" + xpcomFile + ".js", zContext);
+ }
+ catch (e) {
+ Components.utils.reportError("Error loading " + xpcomFile + ".js", zContext);
+ throw (e);
+ }
+ }
+
+ // Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
+ const rdfXpcomFiles = [
+ 'rdf/uri',
+ 'rdf/term',
+ 'rdf/identity',
+ 'rdf/match',
+ 'rdf/n3parser',
+ 'rdf/rdfparser',
+ 'rdf/serialize',
+ 'rdf'
+ ];
+ zContext.Zotero.RDF = {AJAW:{Zotero:zContext.Zotero}};
+ for (var i=0; i<rdfXpcomFiles.length; i++) {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[i] + ".js");
- }
- catch (e) {
- Components.utils.reportError("Error loading " + xpcomFiles[i] + ".js");
- throw (e);
+ .loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", zContext.Zotero.RDF.AJAW);
}
-}
-
-
-// Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
-var rdfXpcomFiles = [
- 'rdf/uri',
- 'rdf/term',
- 'rdf/identity',
- 'rdf/match',
- 'rdf/n3parser',
- 'rdf/rdfparser',
- 'rdf/serialize',
- 'rdf'
-];
-
-Zotero.RDF = {AJAW:{}};
-
-for (var i=0; i<rdfXpcomFiles.length; i++) {
+
+ // load nsTransferable (query: do we still use this?)
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", Zotero.RDF.AJAW);
-}
-
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://global/content/nsTransferable.js");
-
-/********************************************************************/
-
+ .loadSubScript("chrome://global/content/nsTransferable.js", zContext);
+
+ // add connector-related properties
+ zContext.Zotero.isConnector = isConnector;
+ zContext.Zotero.instanceID = instanceID;
+ zContext.Zotero.__defineGetter__("isFirstLoadThisSession", function() isFirstLoadThisSession);
+};
-// Initialize the Zotero service
-//
-// This runs when ZoteroService is first requested.
-// Calls to other XPCOM components must be in here rather than in top-level
-// code, as other components may not have yet been initialized.
-function setupService(){
+/**
+ * The class representing the Zotero service, and affiliated XPCOM goop
+ */
+function ZoteroService(){
try {
- Zotero.init();
- }
- catch (e) {
+ if(isFirstLoadThisSession) {
+ makeZoteroContext(false);
+ try {
+ zContext.Zotero.init();
+ } catch(e) {
+ if(e === "ZOTERO_SHOULD_START_AS_CONNECTOR") {
+ // if Zotero should start as a connector, reload it
+ zContext.Zotero.shutdown();
+ makeZoteroContext(true);
+ zContext.Zotero.init();
+ } else {
+ dump(e.toSource());
+ Components.utils.reportError(e);
+ throw e;
+ }
+ }
+ }
+ isFirstLoadThisSession = false; // no longer first load
+ this.wrappedJSObject = zContext.Zotero;
+ } catch(e) {
var msg = typeof e == 'string' ? e : e.name;
dump(e + "\n\n");
Components.utils.reportError(e);
- throw (e);
+ throw e;
}
}
-function ZoteroService(){
- this.wrappedJSObject = ZoteroWrapped.Zotero;
- setupService();
-}
-
-
-/**
-* Convenience method to replicate window.alert()
-**/
-// TODO: is this still used? if so, move to zotero.js
-function alert(msg){
- Cc["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Ci.nsIPromptService)
- .alert(null, "", msg);
-}
-
-/**
-* Convenience method to replicate window.confirm()
-**/
-// TODO: is this still used? if so, move to zotero.js
-function confirm(msg){
- return Cc["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Ci.nsIPromptService)
- .confirm(null, "", msg);
-}
-
-
-/**
-* Convenience method to replicate window.setTimeout()
-**/
-function setTimeout(func, ms) {
- Zotero.setTimeout(func, ms);
-}
-
-
//
// XPCOM goop
//
@@ -227,8 +301,8 @@ ZoteroService.prototype = {
contractID: ZOTERO_CONTRACTID,
classDescription: ZOTERO_CLASSNAME,
classID: ZOTERO_CID,
- service: true,
- QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, ZOTERO_IID])
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
+ Components.interfaces.nsIProtocolHandler])
}
/**
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
@@ -107,8 +107,8 @@ pref("extensions.zotero.integration.port", 50001);
pref("extensions.zotero.integration.autoRegenerate", -1); // -1 = ask; 0 = no; 1 = yes
// Connector settings
-pref("extensions.zotero.connector.enabled", false);
-pref("extensions.zotero.connector.port", 23119); // ascii "ZO"
+pref("extensions.zotero.httpServer.enabled", false); // TODO enabled for testing only
+pref("extensions.zotero.httpServer.port", 23119); // ascii "ZO"
// Zeroconf
pref("extensions.zotero.zeroconf.server.enabled", false);