diff --git a/chrome.manifest b/chrome.manifest
index 87a9631b8..99fba88f4 100644
--- 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
index 1bc6a17d1..e0f1b087d 100644
--- 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
index 553805da7..4ec88313d 100644
--- 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
index 7079b938b..256a95b9b 100644
--- 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
deleted file mode 100755
index 5ada86a06..000000000
--- a/chrome/content/zotero/xpcom/connector.js
+++ /dev/null
@@ -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 .
-
- ***** 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"]] = ""+this._parsedPostData["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
new file mode 100644
index 000000000..740c59f5b
--- /dev/null
+++ 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 .
+
+ ***** 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.
+
+ ***** 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/translation/item_connector.js b/chrome/content/zotero/xpcom/connector/translate_item.js
similarity index 67%
rename from chrome/content/zotero/xpcom/translation/item_connector.js
rename to chrome/content/zotero/xpcom/connector/translate_item.js
index 331e2094e..f233e4b99 100644
--- a/chrome/content/zotero/xpcom/translation/item_connector.js
+++ b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -23,8 +23,18 @@
***** END LICENSE BLOCK *****
*/
-Zotero.Translate.Item = {
- "saveItem":function (translate, item) {
-
+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
+};
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js
new file mode 100644
index 000000000..92d483775
--- /dev/null
+++ 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 .
+
+ ***** 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 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; i4 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;
- }
-
/**
* Calls the Integration applicatoon
*/
@@ -546,22 +305,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
*/
diff --git a/chrome/content/zotero/xpcom/ipc.js b/chrome/content/zotero/xpcom/ipc.js
new file mode 100755
index 000000000..f08f293fd
--- /dev/null
+++ 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 .
+
+ ***** 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
index c477e3d3e..c0b5819bd 100644
--- 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/integration_worker.js b/chrome/content/zotero/xpcom/pipe_worker.js
similarity index 80%
rename from chrome/content/zotero/xpcom/integration_worker.js
rename to chrome/content/zotero/xpcom/pipe_worker.js
index bdaee72cb..8b317e929 100644
--- a/chrome/content/zotero/xpcom/integration_worker.js
+++ b/chrome/content/zotero/xpcom/pipe_worker.js
@@ -54,19 +54,12 @@ onmessage = function(event) {
// 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"]);
+ if(string === "Zotero shutdown\n") {
+ postMessage(["Debug", "IPC: Worker closing "+event.data.path]);
lib.close();
return;
}
- postMessage([agent, cmd, document]);
+
+ 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
index b684362a5..075d4d695 100644
--- 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
new file mode 100755
index 000000000..ad1112bda
--- /dev/null
+++ 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 .
+
+ ***** 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
new file mode 100755
index 000000000..5b878de2c
--- /dev/null
+++ 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 .
+
+ ***** 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"]] = ""+this._parsedPostData["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_other.js b/chrome/content/zotero/xpcom/translation/browser_other.js
deleted file mode 100644
index c39d3531b..000000000
--- a/chrome/content/zotero/xpcom/translation/browser_other.js
+++ /dev/null
@@ -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 .
-
- ***** 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
+ *
* 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);
-
- 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.";
+ 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.";
+ }
}
+
+ 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 www.nature.com)
+ var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
+ if(m) {
+ var hostnames = m[2].split(".");
+ for(var i=1; i= 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
index 8e8093ef1..dea87d7f7 100644
--- 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
index 046f19857..578543034 100644
--- 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-integration-service.js b/components/zotero-command-line-handler.js
similarity index 58%
rename from components/zotero-integration-service.js
rename to components/zotero-command-line-handler.js
index 14f28e528..5939210f3 100644
--- a/components/zotero-integration-service.js
+++ b/components/zotero-command-line-handler.js
@@ -32,51 +32,64 @@
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_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero";
const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
-const clh_category = "m-zotero-integration";
-const clh_description = "Zotero Integration Command Line Handler";
+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 ZoteroIntegrationCommandLineHandler() {}
-ZoteroIntegrationCommandLineHandler.prototype = {
- Zotero : null,
-
+function ZoteroCommandLineHandler() {}
+ZoteroCommandLineHandler.prototype = {
/* nsISupports */
- QueryInterface : function(iid) {
- if(iid.equals(nsICommandLineHandler) ||
- iid.equals(nsIFactory) ||
- iid.equals(nsISupports)) return this;
- throw Components.results.NS_ERROR_NO_INTERFACE;
- },
+ 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);
- 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;
+ 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,
@@ -88,12 +101,20 @@ ZoteroIntegrationCommandLineHandler.prototype = {
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([ZoteroIntegrationCommandLineHandler]);
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroCommandLineHandler]);
} else {
- var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroIntegrationCommandLineHandler]);
+ var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroCommandLineHandler]);
}
\ No newline at end of file
diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js
index f1f7d5efb..bbd59eb4d 100644
--- 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
index 28b11be55..b8a5aa442 100644
--- 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");
-
-// 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);
-
-for (var i=1; i