Zotero Everywhere megacommit
- Implement connector for Firefox (should switch in/out of connector mode automatically when Standalone is launched or closed, although this has only been tested extensively on OS X) - Share core translation code between Zotero and connectors Still to be done: - Run translators in non-Fx connectors (this works in theory, but it's not currently enabled for any translators) - Show translation results in non-Fx connectors - Ability to translate to server when Zotero Standalone is not running
This commit is contained in:
parent
fd5d9496fd
commit
8268d1b01c
33 changed files with 3582 additions and 1645 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,780 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Connector = new function() {
|
||||
var _onlineObserverRegistered;
|
||||
var responseCodes = {
|
||||
200:"OK",
|
||||
201:"Created",
|
||||
300:"Multiple Choices",
|
||||
400:"Bad Request",
|
||||
404:"Not Found",
|
||||
500:"Internal Server Error",
|
||||
501:"Method Not Implemented"
|
||||
};
|
||||
|
||||
/**
|
||||
* initializes a very rudimentary web server
|
||||
*/
|
||||
this.init = function() {
|
||||
if (Zotero.HTTP.browserIsOffline()) {
|
||||
Zotero.debug('Browser is offline -- not initializing connector HTTP server');
|
||||
_registerOnlineObserver();
|
||||
return;
|
||||
}
|
||||
|
||||
// start listening on socket
|
||||
var serv = Components.classes["@mozilla.org/network/server-socket;1"]
|
||||
.createInstance(Components.interfaces.nsIServerSocket);
|
||||
try {
|
||||
// bind to a random port on loopback only
|
||||
serv.init(Zotero.Prefs.get('connector.port'), true, -1);
|
||||
serv.asyncListen(Zotero.Connector.SocketListener);
|
||||
|
||||
Zotero.debug("Connector HTTP server listening on 127.0.0.1:"+serv.port);
|
||||
} catch(e) {
|
||||
Zotero.debug("Not initializing connector HTTP server");
|
||||
}
|
||||
|
||||
_registerOnlineObserver()
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the response to an HTTP request
|
||||
*/
|
||||
this.generateResponse = function (status, contentType, body) {
|
||||
var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
|
||||
response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
|
||||
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
|
||||
|
||||
if(body) {
|
||||
if(contentType) {
|
||||
response += "Content-Type: "+contentType+"\r\n";
|
||||
}
|
||||
response += "\r\n"+body;
|
||||
} else {
|
||||
response += "Content-Length: 0\r\n\r\n"
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes application/x-www-form-urlencoded data
|
||||
*
|
||||
* @param {String} postData application/x-www-form-urlencoded data, as sent in a g request
|
||||
* @return {Object} data in object form
|
||||
*/
|
||||
this.decodeURLEncodedData = function(postData) {
|
||||
var splitData = postData.split("&");
|
||||
var variables = {};
|
||||
for each(var variable in splitData) {
|
||||
var splitIndex = variable.indexOf("=");
|
||||
variables[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
function _registerOnlineObserver() {
|
||||
if (_onlineObserverRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Observer to enable the integration when we go online
|
||||
var observer = {
|
||||
observe: function(subject, topic, data) {
|
||||
if (data == 'online') {
|
||||
Zotero.Connector.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var observerService =
|
||||
Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
observerService.addObserver(observer, "network:offline-status-changed", false);
|
||||
|
||||
_onlineObserverRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Connector.SocketListener = new function() {
|
||||
this.onSocketAccepted = onSocketAccepted;
|
||||
this.onStopListening = onStopListening;
|
||||
|
||||
/*
|
||||
* called when a socket is opened
|
||||
*/
|
||||
function onSocketAccepted(socket, transport) {
|
||||
// get an input stream
|
||||
var iStream = transport.openInputStream(0, 0, 0);
|
||||
var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
|
||||
|
||||
var dataListener = new Zotero.Connector.DataListener(iStream, oStream);
|
||||
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
|
||||
.createInstance(Components.interfaces.nsIInputStreamPump);
|
||||
pump.init(iStream, -1, -1, 0, 0, false);
|
||||
pump.asyncRead(dataListener, null);
|
||||
}
|
||||
|
||||
function onStopListening(serverSocket, status) {
|
||||
Zotero.debug("Connector HTTP server going offline");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* handles the actual acquisition of data
|
||||
*/
|
||||
Zotero.Connector.DataListener = function(iStream, oStream) {
|
||||
this.header = "";
|
||||
this.headerFinished = false;
|
||||
|
||||
this.body = "";
|
||||
this.bodyLength = 0;
|
||||
|
||||
this.iStream = iStream;
|
||||
this.oStream = oStream;
|
||||
this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this.sStream.init(iStream);
|
||||
|
||||
this.foundReturn = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* called when a request begins (although the request should have begun before
|
||||
* the DataListener was generated)
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype.onStartRequest = function(request, context) {}
|
||||
|
||||
/*
|
||||
* called when a request stops
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype.onStopRequest = function(request, context, status) {
|
||||
this.iStream.close();
|
||||
this.oStream.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* called when new data is available
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype.onDataAvailable = function(request, context,
|
||||
inputStream, offset, count) {
|
||||
var readData = this.sStream.read(count);
|
||||
|
||||
if(this.headerFinished) { // reading body
|
||||
this.body += readData;
|
||||
// check to see if data is done
|
||||
this._bodyData();
|
||||
} else { // reading header
|
||||
// see if there's a magic double return
|
||||
var lineBreakIndex = readData.indexOf("\r\n\r\n");
|
||||
if(lineBreakIndex != -1) {
|
||||
if(lineBreakIndex != 0) {
|
||||
this.header += readData.substr(0, lineBreakIndex+4);
|
||||
this.body = readData.substr(lineBreakIndex+4);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
var lineBreakIndex = readData.indexOf("\n\n");
|
||||
if(lineBreakIndex != -1) {
|
||||
if(lineBreakIndex != 0) {
|
||||
this.header += readData.substr(0, lineBreakIndex+2);
|
||||
this.body = readData.substr(lineBreakIndex+2);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
if(this.header && this.header[this.header.length-1] == "\n" &&
|
||||
(readData[0] == "\n" || readData[0] == "\r")) {
|
||||
if(readData.length > 1 && readData[1] == "\n") {
|
||||
this.header += readData.substr(0, 2);
|
||||
this.body = readData.substr(2);
|
||||
} else {
|
||||
this.header += readData[0];
|
||||
this.body = readData.substr(1);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
this.header += readData;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* processes an HTTP header and decides what to do
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype._headerFinished = function() {
|
||||
this.headerFinished = true;
|
||||
|
||||
Zotero.debug(this.header);
|
||||
|
||||
const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
|
||||
|
||||
// get first line of request (all we care about for now)
|
||||
var method = methodRe.exec(this.header);
|
||||
|
||||
if(!method) {
|
||||
this._requestFinished(Zotero.Connector.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
if(!Zotero.Connector.Endpoints[method[2]]) {
|
||||
this._requestFinished(Zotero.Connector.generateResponse(404));
|
||||
return;
|
||||
}
|
||||
this.endpoint = Zotero.Connector.Endpoints[method[2]];
|
||||
|
||||
if(method[1] == "HEAD" || method[1] == "OPTIONS") {
|
||||
this._requestFinished(Zotero.Connector.generateResponse(200));
|
||||
} else if(method[1] == "GET") {
|
||||
this._requestFinished(this._processEndpoint("GET", method[3]));
|
||||
} else if(method[1] == "POST") {
|
||||
const contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
|
||||
|
||||
// parse content length
|
||||
var m = contentLengthRe.exec(this.header);
|
||||
if(!m) {
|
||||
this._requestFinished(Zotero.Connector.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
|
||||
this.bodyLength = parseInt(m[1]);
|
||||
this._bodyData();
|
||||
} else {
|
||||
this._requestFinished(Zotero.Connector.generateResponse(501));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* checks to see if Content-Length bytes of body have been read and, if so, processes the body
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype._bodyData = function() {
|
||||
if(this.body.length >= this.bodyLength) {
|
||||
// convert to UTF-8
|
||||
var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIStringInputStream);
|
||||
dataStream.setData(this.body, this.bodyLength);
|
||||
|
||||
var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
utf8Stream.init(dataStream, "UTF-8", 4096, "?");
|
||||
|
||||
this.body = "";
|
||||
var string = {};
|
||||
while(utf8Stream.readString(this.bodyLength, string)) {
|
||||
this.body += string.value;
|
||||
}
|
||||
|
||||
// handle envelope
|
||||
this._processEndpoint("POST", this.body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a response based on calling the function associated with the endpoint
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype._processEndpoint = function(method, postData) {
|
||||
try {
|
||||
var endpoint = new this.endpoint;
|
||||
var me = this;
|
||||
var sendResponseCallback = function(code, contentType, arg) {
|
||||
me._requestFinished(Zotero.Connector.generateResponse(code, contentType, arg));
|
||||
}
|
||||
endpoint.init(method, postData ? postData : undefined, sendResponseCallback);
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
this._requestFinished(Zotero.Connector.generateResponse(500));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* returns HTTP data from a request
|
||||
*/
|
||||
Zotero.Connector.DataListener.prototype._requestFinished = function(response) {
|
||||
// close input stream
|
||||
this.iStream.close();
|
||||
|
||||
// open UTF-8 converter for output stream
|
||||
var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
||||
|
||||
// write
|
||||
try {
|
||||
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
|
||||
|
||||
// write response
|
||||
Zotero.debug(response);
|
||||
intlStream.writeString(response);
|
||||
} finally {
|
||||
intlStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage cookies in a sandboxed fashion
|
||||
*
|
||||
* @param {browser} browser Hidden browser object
|
||||
* @param {String} uri URI of page to manage cookies for (cookies for domains that are not
|
||||
* subdomains of this URI are ignored)
|
||||
* @param {String} cookieData Cookies with which to initiate the sandbox
|
||||
*/
|
||||
Zotero.Connector.CookieManager = function(browser, uri, cookieData) {
|
||||
this._webNav = browser.webNavigation;
|
||||
this._browser = browser;
|
||||
this._watchedBrowsers = [browser];
|
||||
this._observerService = Components.classes["@mozilla.org/observer-service;1"].
|
||||
getService(Components.interfaces.nsIObserverService);
|
||||
|
||||
this._uri = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService)
|
||||
.newURI(uri, null, null);
|
||||
|
||||
var splitCookies = cookieData.split(/; ?/);
|
||||
this._cookies = {};
|
||||
for each(var cookie in splitCookies) {
|
||||
var key = cookie.substr(0, cookie.indexOf("="));
|
||||
var value = cookie.substr(cookie.indexOf("=")+1);
|
||||
this._cookies[key] = value;
|
||||
}
|
||||
|
||||
[this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
|
||||
}
|
||||
|
||||
Zotero.Connector.CookieManager.prototype = {
|
||||
"_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
|
||||
"_watchedXHRs":[],
|
||||
|
||||
/**
|
||||
* nsIObserver implementation for adding, clearing, and slurping cookies
|
||||
*/
|
||||
"observe": function(channel, topic) {
|
||||
if(topic == "quit-application") {
|
||||
Zotero.debug("WARNING: A Zotero.Connector.CookieManager for "+this._uri.spec+" was still open on shutdown");
|
||||
} else {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
var isTracked = null;
|
||||
try {
|
||||
var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
|
||||
for each(var browser in this._watchedBrowsers) {
|
||||
isTracked = topDoc == browser.contentDocument;
|
||||
if(isTracked) break;
|
||||
}
|
||||
} catch(e) {}
|
||||
if(isTracked === null) {
|
||||
try {
|
||||
isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
|
||||
} catch(e) {}
|
||||
}
|
||||
if(isTracked === null) {
|
||||
try {
|
||||
isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// isTracked is now either true, false, or null
|
||||
// true => we should manage cookies for this request
|
||||
// false => we should not manage cookies for this request
|
||||
// null => this request is of a type we couldn't match to this request. one such type
|
||||
// is a link prefetch (nsPrefetchNode) but there might be others as well. for
|
||||
// now, we are paranoid and reject these.
|
||||
|
||||
if(isTracked === false) {
|
||||
Zotero.debug("Zotero.Connector.CookieManager: not touching channel for "+channel.URI.spec);
|
||||
return;
|
||||
} else if(isTracked) {
|
||||
Zotero.debug("Zotero.Connector.CookieManager: managing cookies for "+channel.URI.spec);
|
||||
} else {
|
||||
Zotero.debug("Zotero.Connector.CookieManager: being paranoid about channel for "+channel.URI.spec);
|
||||
}
|
||||
|
||||
if(topic == "http-on-modify-request") {
|
||||
// clear cookies to be sent to other domains
|
||||
if(isTracked === null || channel.URI.host != this._uri.host) {
|
||||
channel.setRequestHeader("Cookie", "", false);
|
||||
channel.setRequestHeader("Cookie2", "", false);
|
||||
Zotero.debug("Zotero.Connector.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// add cookies to be sent to this domain
|
||||
var cookies = [key+"="+this._cookies[key]
|
||||
for(key in this._cookies)].join("; ");
|
||||
channel.setRequestHeader("Cookie", cookies, false);
|
||||
Zotero.debug("Zotero.Connector.CookieManager: added cookies for request to "+channel.URI.spec);
|
||||
} else if(topic == "http-on-examine-response") {
|
||||
// clear cookies being received
|
||||
try {
|
||||
var cookieHeader = channel.getResponseHeader("Set-Cookie");
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
channel.setResponseHeader("Set-Cookie", "", false);
|
||||
channel.setResponseHeader("Set-Cookie2", "", false);
|
||||
|
||||
// don't process further if these cookies are for another set of domains
|
||||
if(isTracked === null || channel.URI.host != this._uri.host) {
|
||||
Zotero.debug("Zotero.Connector.CookieManager: rejected cookies from "+channel.URI.spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// put new cookies into our sandbox
|
||||
if(cookieHeader) {
|
||||
var cookies = cookieHeader.split(/; ?/);
|
||||
var newCookies = {};
|
||||
for each(var cookie in cookies) {
|
||||
var key = cookie.substr(0, cookie.indexOf("="));
|
||||
var value = cookie.substr(cookie.indexOf("=")+1);
|
||||
var lcCookie = key.toLowerCase();
|
||||
|
||||
if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
|
||||
// ignore cookie parameters; we are only holding cookies for a few minutes
|
||||
// with a single domain, and the path attribute doesn't allow any additional
|
||||
// security anyway
|
||||
continue;
|
||||
} else if(lcCookie == "secure") {
|
||||
// don't accept secure cookies
|
||||
newCookies = {};
|
||||
break;
|
||||
} else {
|
||||
newCookies[key] = value;
|
||||
}
|
||||
}
|
||||
[this._cookies[key] = newCookies[key] for(key in newCookies)];
|
||||
}
|
||||
|
||||
Zotero.debug("Zotero.Connector.CookieManager: slurped cookies from "+channel.URI.spec);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach CookieManager to a specific XMLHttpRequest
|
||||
* @param {XMLHttpRequest} xhr
|
||||
*/
|
||||
"attachToBrowser": function(browser) {
|
||||
this._watchedBrowsers.push(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach CookieManager to a specific XMLHttpRequest
|
||||
* @param {XMLHttpRequest} xhr
|
||||
*/
|
||||
"attachToXHR": function(xhr) {
|
||||
this._watchedXHRs.push(xhr);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys this CookieManager (intended to be executed when the browser is destroyed)
|
||||
*/
|
||||
"destroy": function() {
|
||||
[this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Connector.Data = {};
|
||||
|
||||
Zotero.Connector.Translate = function() {};
|
||||
Zotero.Connector.Translate._waitingForSelection = {};
|
||||
|
||||
|
||||
/**
|
||||
* Lists all available translators, including code for translators that should be run on every page
|
||||
*/
|
||||
Zotero.Connector.Translate.List = function() {};
|
||||
|
||||
Zotero.Connector.Translate.List.prototype = {
|
||||
/**
|
||||
* Gets available translator list
|
||||
* @param {String} method "GET" or "POST"
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(method, data, sendResponseCallback) {
|
||||
if(method != "POST") {
|
||||
sendResponseCallback(400);
|
||||
return;
|
||||
}
|
||||
|
||||
var translators = Zotero.Translators.getAllForType("web");
|
||||
var jsons = [];
|
||||
for each(var translator in translators) {
|
||||
let json = {};
|
||||
for each(var key in ["translatorID", "label", "creator", "target", "priority", "detectXPath"]) {
|
||||
json[key] = translator[key];
|
||||
}
|
||||
json["localExecution"] = translator.browserSupport.indexOf(data["browser"]) !== -1;
|
||||
|
||||
// Do not pass targetless translators that do not support this browser (since that
|
||||
// would mean passing each page back to Zotero)
|
||||
if(json["target"] || json["detectXPath"] || json["localExecution"]) {
|
||||
jsons.push(json);
|
||||
}
|
||||
}
|
||||
|
||||
sendResponseCallback(200, "application/json", JSON.stringify(jsons));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether there is an available translator to handle a given page
|
||||
*/
|
||||
Zotero.Connector.Translate.Detect = function() {};
|
||||
|
||||
Zotero.Connector.Translate.Detect.prototype = {
|
||||
/**
|
||||
* Loads HTML into a hidden browser and initiates translator detection
|
||||
* @param {String} method "GET" or "POST"
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(method, data, sendResponseCallback) {
|
||||
if(method != "POST") {
|
||||
sendResponseCallback(400);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendResponse = sendResponseCallback;
|
||||
this._parsedPostData = JSON.parse(data);
|
||||
|
||||
this._translate = new Zotero.Translate("web");
|
||||
this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
|
||||
|
||||
Zotero.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
|
||||
this._browser = Zotero.Browser.createHiddenBrowser();
|
||||
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
|
||||
|
||||
var pageShowCalled = false;
|
||||
var me = this;
|
||||
this._translate.setCookieManager(new Zotero.Connector.CookieManager(this._browser,
|
||||
this._parsedPostData["uri"], this._parsedPostData["cookie"]));
|
||||
this._browser.addEventListener("DOMContentLoaded", function() {
|
||||
try {
|
||||
if(me._browser.contentDocument.location.href == "about:blank") return;
|
||||
if(pageShowCalled) return;
|
||||
pageShowCalled = true;
|
||||
delete Zotero.Connector.Data[me._parsedPostData["uri"]];
|
||||
|
||||
// get translators
|
||||
me._translate.setDocument(me._browser.contentDocument);
|
||||
me._translate.getTranslators();
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
throw e;
|
||||
}
|
||||
}, false);
|
||||
|
||||
me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to be executed when list of translators becomes available. Sends response with
|
||||
* item types, translator IDs, labels, and icons for available translators.
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Zotero.Translator[]} translators
|
||||
*/
|
||||
"_translatorsAvailable":function(obj, translators) {
|
||||
var jsons = [];
|
||||
for each(var translator in translators) {
|
||||
if(translator.itemType == "multiple") {
|
||||
var icon = "treesource-collection.png"
|
||||
} else {
|
||||
var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
|
||||
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||
}
|
||||
var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
|
||||
"label":translator.label, "icon":icon}
|
||||
jsons.push(json);
|
||||
}
|
||||
this.sendResponse(200, "application/json", JSON.stringify(jsons));
|
||||
|
||||
this._translate.cookieManager.destroy();
|
||||
Zotero.Browser.deleteHiddenBrowser(this._browser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs translation of a given page
|
||||
*/
|
||||
Zotero.Connector.Translate.Save = function() {};
|
||||
Zotero.Connector.Translate.Save.prototype = {
|
||||
/**
|
||||
* Init method inherited from Zotero.Connector.Translate.Detect
|
||||
* @borrows Zotero.Connector.Translate.Detect as this.init
|
||||
*/
|
||||
"init":Zotero.Connector.Translate.Detect.prototype.init,
|
||||
|
||||
/**
|
||||
* Callback to be executed when items must be selected
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Object} itemList ID=>text pairs representing available items
|
||||
*/
|
||||
"_selectItems":function(translate, itemList) {
|
||||
var instanceID = Zotero.randomString();
|
||||
Zotero.Connector.Translate._waitingForSelection[instanceID] = this;
|
||||
|
||||
// Fix for translators that don't create item lists as objects
|
||||
if(itemList.push && typeof itemList.push === "function") {
|
||||
var newItemList = {};
|
||||
for(var item in itemList) {
|
||||
Zotero.debug(item);
|
||||
newItemList[item] = itemList[item];
|
||||
}
|
||||
itemList = newItemList;
|
||||
}
|
||||
|
||||
// Send "Multiple Choices" HTTP response
|
||||
this.sendResponse(300, "application/json", JSON.stringify({"items":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
|
||||
|
||||
// We need this to make sure that we won't stop Firefox from quitting, even if the user
|
||||
// didn't close the selectItems window
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
var me = this;
|
||||
var quitObserver = {observe:function() { me.selectedItems = false; }};
|
||||
observerService.addObserver(quitObserver, "quit-application", false);
|
||||
|
||||
this.selectedItems = null;
|
||||
var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
|
||||
// permanently slow Firefox with this loop
|
||||
while(this.selectedItems === null && Date.now() < endTime) {
|
||||
Zotero.mainThread.processNextEvent(true);
|
||||
}
|
||||
|
||||
observerService.removeObserver(quitObserver, "quit-application");
|
||||
if(!this.selectedItems) this._progressWindow.close();
|
||||
return this.selectedItems;
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to be executed when list of translators becomes available. Opens progress window,
|
||||
* selects specified translator, and initiates translation.
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Zotero.Translator[]} translators
|
||||
*/
|
||||
"_translatorsAvailable":function(translate, translators) {
|
||||
// make sure translatorsAvailable succeded
|
||||
if(!translators.length) {
|
||||
Zotero.Browser.deleteHiddenBrowser(this._browser);
|
||||
this.sendResponse(500);
|
||||
return;
|
||||
}
|
||||
|
||||
// set up progress window
|
||||
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator)
|
||||
.getMostRecentWindow("navigator:browser");
|
||||
|
||||
this._progressWindow = win.Zotero_Browser.progress;
|
||||
if(Zotero.locked) {
|
||||
this._progressWindow.changeHeadline(Zotero.getString("ingester.scrapeError"));
|
||||
var desc = Zotero.localeJoin([
|
||||
Zotero.getString('general.operationInProgress'), Zotero.getString('general.operationInProgress.waitUntilFinishedAndTryAgain')
|
||||
]);
|
||||
this._progressWindow.addDescription(desc);
|
||||
this._progressWindow.show();
|
||||
this._progressWindow.startCloseTimer(8000);
|
||||
return;
|
||||
}
|
||||
|
||||
this._progressWindow.show();
|
||||
|
||||
// set save callbacks
|
||||
this._libraryID = null;
|
||||
var collection = null;
|
||||
try {
|
||||
this._libraryID = win.ZoteroPane.getSelectedLibraryID();
|
||||
collection = win.ZoteroPane.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
var me = this;
|
||||
translate.setHandler("select", function(obj, item) { return me._selectItems(obj, item) });
|
||||
translate.setHandler("itemDone", function(obj, item) { win.Zotero_Browser.itemDone(obj, item, collection) });
|
||||
translate.setHandler("done", function(obj, item) {
|
||||
win.Zotero_Browser.finishScraping(obj, item, collection);
|
||||
me._translate.cookieManager.destroy();
|
||||
Zotero.Browser.deleteHiddenBrowser(me._browser);
|
||||
me.sendResponse(201);
|
||||
});
|
||||
|
||||
// set translator and translate
|
||||
translate.setTranslator(this._parsedPostData.translatorID);
|
||||
translate.translate(this._libraryID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle item selection
|
||||
*/
|
||||
Zotero.Connector.Translate.Select = function() {};
|
||||
Zotero.Connector.Translate.Select.prototype = {
|
||||
/**
|
||||
* Finishes up translation when item selection is complete
|
||||
* @param {String} method "GET" or "POST"
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(method, postData, sendResponseCallback) {
|
||||
if(method != "POST") {
|
||||
sendResponseCallback(400);
|
||||
return;
|
||||
}
|
||||
|
||||
var postData = JSON.parse(postData);
|
||||
var saveInstance = Zotero.Connector.Translate._waitingForSelection[postData.instanceID];
|
||||
saveInstance.sendResponse = sendResponseCallback;
|
||||
|
||||
saveInstance.selectedItems = false;
|
||||
for(var i in postData.items) {
|
||||
saveInstance.selectedItems = postData.items;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoints for the Connector HTTP server
|
||||
*
|
||||
* Each endpoint should take the form of an object. The init() method of this object will be passed:
|
||||
* method - the method of the request ("GET" or "POST")
|
||||
* data - the query string (for a "GET" request) or POST data (for a "POST" request)
|
||||
* sendResponseCallback - a function to send a response to the HTTP request. This can be passed
|
||||
* a response code alone (e.g., sendResponseCallback(404)) or a response
|
||||
* code, MIME type, and response body
|
||||
* (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
|
||||
*/
|
||||
Zotero.Connector.Endpoints = {
|
||||
"/translate/list":Zotero.Connector.Translate.List,
|
||||
"/translate/detect":Zotero.Connector.Translate.Detect,
|
||||
"/translate/save":Zotero.Connector.Translate.Save,
|
||||
"/translate/select":Zotero.Connector.Translate.Select
|
||||
}
|
113
chrome/content/zotero/xpcom/connector/cachedTypes.js
Normal file
113
chrome/content/zotero/xpcom/connector/cachedTypes.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emulates very small parts of cachedTypes.js and itemFields.js APIs for use with connector
|
||||
*/
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.Connector.Types = new function() {
|
||||
/**
|
||||
* Initializes types
|
||||
* @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
|
||||
*/
|
||||
this.init = function(typeSchema) {
|
||||
const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
|
||||
|
||||
// attach IDs and make referenceable by either ID or name
|
||||
for(var i=0; i<schemaTypes.length; i++) {
|
||||
var schemaType = schemaTypes[i];
|
||||
this[schemaType] = typeSchema[schemaType];
|
||||
for(var id in this[schemaType]) {
|
||||
var entry = this[schemaType][id];
|
||||
entry.id = id;
|
||||
this[schemaType][entry.name] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.CachedTypes = function() {
|
||||
this.getID = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].id;
|
||||
}
|
||||
|
||||
this.getName = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].name;
|
||||
}
|
||||
|
||||
this.getLocalizedString = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].localizedString;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemTypes = new function() {
|
||||
this.schemaType = "itemTypes";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getImageSrc = function(idOrName) {
|
||||
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
|
||||
|
||||
if(Zotero.isFx) {
|
||||
return "chrome://zotero/skin/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
|
||||
} else {
|
||||
return "images/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.CreatorTypes = new function() {
|
||||
this.schemaType = "creatorTypes";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getTypesForItemType = function(idOrName) {
|
||||
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
|
||||
var itemType = Zotero.Connector.Types["itemTypes"][idOrName];
|
||||
var creatorTypes = [];
|
||||
for(var i=0; i<itemType.creatorTypes.length; i++) {
|
||||
creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]);
|
||||
}
|
||||
return creatorTypes;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemFields = new function() {
|
||||
this.schemaType = "fields";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.isValidForType = function(fieldIdOrName, typeIdOrName) {
|
||||
// mimics itemFields.js
|
||||
if(!Zotero.Connector.Types["fields"][fieldIdOrName]
|
||||
|| !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
|
||||
|
||||
return Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf(
|
||||
Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1;
|
||||
}
|
||||
}
|
176
chrome/content/zotero/xpcom/connector/connector.js
Normal file
176
chrome/content/zotero/xpcom/connector/connector.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2011 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
Zotero.Connector = new function() {
|
||||
const CONNECTOR_URI = "http://127.0.0.1:23119/";
|
||||
|
||||
this.isOnline = true;
|
||||
this.haveRefreshedData = false;
|
||||
this.data = null;
|
||||
|
||||
/**
|
||||
* Called to initialize Zotero
|
||||
*/
|
||||
this.init = function() {
|
||||
Zotero.Connector.getData();
|
||||
}
|
||||
|
||||
function _getDataFile() {
|
||||
var dataFile = Zotero.getZoteroDirectory();
|
||||
dataFile.append("connector.json");
|
||||
return dataFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Zotero.Connector.data object to localStorage/preferences
|
||||
* @param {String} [json] The
|
||||
*/
|
||||
this.serializeData = function(json) {
|
||||
if(!json) json = JSON.stringify(Zotero.Connector.data);
|
||||
|
||||
if(Zotero.isFx) {
|
||||
Zotero.File.putContents(_getDataFile(), json);
|
||||
} else {
|
||||
localStorage.data = json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes the Zotero.Connector.data object from localStorage/preferences
|
||||
*/
|
||||
this.unserializeData = function() {
|
||||
var data = null;
|
||||
|
||||
if(Zotero.isFx) {
|
||||
var dataFile = _getDataFile();
|
||||
if(dataFile.exists()) data = Zotero.File.getContents(dataFile);
|
||||
} else {
|
||||
if(localStorage.data) data = localStorage.data;
|
||||
}
|
||||
|
||||
if(data) Zotero.Connector.data = JSON.parse(data);
|
||||
}
|
||||
|
||||
// saner descriptions of some HTTP error codes
|
||||
this.EXCEPTION_NOT_AVAILABLE = 0;
|
||||
this.EXCEPTION_BAD_REQUEST = 400;
|
||||
this.EXCEPTION_NO_ENDPOINT = 404;
|
||||
this.EXCEPTION_CONNECTOR_INTERNAL = 500;
|
||||
this.EXCEPTION_METHOD_NOT_IMPLEMENTED = 501;
|
||||
this.EXCEPTION_CODES = [0, 400, 404, 500, 501];
|
||||
|
||||
/**
|
||||
* Updates Zotero's status depending on the success or failure of a request
|
||||
*
|
||||
* @param {Boolean} isOnline Whether or not Zotero was online
|
||||
* @param {Function} successCallback Function to be called after loading new data if
|
||||
* Zotero is online
|
||||
* @param {Function} failureCallback Function to be called if Zotero is offline
|
||||
*
|
||||
* Calls Zotero.Connector.Browser.onStateChange(isOnline, method, context) if status has changed
|
||||
*/
|
||||
function _checkState(isOnline, callback) {
|
||||
if(isOnline) {
|
||||
if(Zotero.Connector.haveRefreshedData) {
|
||||
if(callback) callback(true);
|
||||
} else {
|
||||
Zotero.Connector.getData(callback);
|
||||
}
|
||||
} else {
|
||||
if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
if(Zotero.Connector.isOnline !== isOnline) {
|
||||
Zotero.Connector.isOnline = isOnline;
|
||||
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) {
|
||||
Zotero.Connector_Browser.onStateChange(isOnline);
|
||||
}
|
||||
}
|
||||
|
||||
return isOnline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads list of translators and other relevant data from local Zotero instance
|
||||
*
|
||||
* @param {Function} successCallback Function to be called after loading new data if
|
||||
* Zotero is online
|
||||
* @param {Function} failureCallback Function to be called if Zotero is offline
|
||||
*/
|
||||
this.getData = function(callback) {
|
||||
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/getData",
|
||||
JSON.stringify({"browser":Zotero.Connector_Browser}),
|
||||
function(req) {
|
||||
var isOnline = req.status !== 0;
|
||||
|
||||
if(isOnline) {
|
||||
// if request succeded, update data
|
||||
Zotero.Connector.haveRefreshedData = true;
|
||||
Zotero.Connector.serializeData(req.responseText);
|
||||
Zotero.Connector.data = JSON.parse(req.responseText);
|
||||
} else {
|
||||
// if request failed, unserialize saved data
|
||||
Zotero.Connector.unserializeData();
|
||||
}
|
||||
Zotero.Connector.Types.init(Zotero.Connector.data.schema);
|
||||
|
||||
// update online state. this shouldn't loop, since haveRefreshedData should
|
||||
// be true if isOnline is true.
|
||||
_checkState(isOnline, callback);
|
||||
}, {"Content-Type":"application/json"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the XHR to execute an RPC call.
|
||||
*
|
||||
* @param {String} method RPC method. See documentation above.
|
||||
* @param {Object} data RPC data. See documentation above.
|
||||
* @param {Function} successCallback Function to be called if request succeeded.
|
||||
* @param {Function} failureCallback Function to be called if request failed.
|
||||
*/
|
||||
this.callMethod = function(method, data, callback) {
|
||||
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/"+method, JSON.stringify(data),
|
||||
function(req) {
|
||||
_checkState(req.status != 0, function() {
|
||||
if(!callback) callback(false);
|
||||
|
||||
if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) {
|
||||
if(callback) callback(false, req.status);
|
||||
} else {
|
||||
if(callback) {
|
||||
var val = undefined;
|
||||
if(req.responseText) {
|
||||
if(req.getResponseHeader("Content-Type") === "application/json") {
|
||||
val = JSON.parse(req.responseText);
|
||||
} else {
|
||||
val = req.responseText;
|
||||
}
|
||||
}
|
||||
callback(val, req.status);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, {"Content-Type":"application/json"});
|
||||
}
|
||||
}
|
|
@ -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]});
|
||||
}
|
||||
}
|
||||
};
|
341
chrome/content/zotero/xpcom/connector/translator.js
Normal file
341
chrome/content/zotero/xpcom/connector/translator.js
Normal file
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
// Enumeration of types of translators
|
||||
const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
|
||||
|
||||
/**
|
||||
* Singleton to handle loading and caching of translators
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.Translators = new function() {
|
||||
var _cache, _translators;
|
||||
var _initialized = false;
|
||||
|
||||
/**
|
||||
* Initializes translator cache, loading all relevant translators into memory
|
||||
*/
|
||||
this.init = function() {
|
||||
_cache = {"import":[], "export":[], "web":[], "search":[]};
|
||||
_translators = {};
|
||||
_initialized = true;
|
||||
|
||||
// Build caches
|
||||
var translators = Zotero.Connector.data.translators;
|
||||
for(var i=0; i<translators.length; i++) {
|
||||
var translator = new Zotero.Translator(translators[i]);
|
||||
_translators[translator.translatorID] = translator;
|
||||
|
||||
for(var type in TRANSLATOR_TYPES) {
|
||||
if(translator.translatorType & TRANSLATOR_TYPES[type]) {
|
||||
_cache[type].push(translator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by priority
|
||||
var cmp = function (a, b) {
|
||||
if (a.priority > b.priority) {
|
||||
return 1;
|
||||
}
|
||||
else if (a.priority < b.priority) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for(var type in _cache) {
|
||||
_cache[type].sort(cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translator that corresponds to a given ID
|
||||
* @param {String} id The ID of the translator
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned.
|
||||
*/
|
||||
this.get = function(id, callback) {
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
var translator = _translators[id];
|
||||
if(!translator) {
|
||||
callback(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// only need to get code if it is of some use
|
||||
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
translator.getCode(function() { callback(translator) });
|
||||
} else {
|
||||
callback(translator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all translators for a specific type of translation
|
||||
* @param {String} type The type of translators to get (import, export, web, or search)
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned.
|
||||
*/
|
||||
this.getAllForType = function(type, callback) {
|
||||
if(!_initialized) Zotero.Translators.init()
|
||||
var translators = _cache[type].slice(0);
|
||||
new Zotero.Translators.CodeGetter(translators, callback, translators);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets web translators for a specific location
|
||||
* @param {String} uri The URI for which to look for translators
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned. The callback is passed a set of functions for
|
||||
* converting URLs from proper to proxied forms as the second
|
||||
* argument.
|
||||
*/
|
||||
this.getWebTranslatorsForLocation = function(uri, callback) {
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
var allTranslators = _cache["web"];
|
||||
var potentialTranslators = [];
|
||||
var searchURIs = [uri];
|
||||
|
||||
Zotero.debug("Translators: Looking for translators for "+uri);
|
||||
|
||||
// if there is a subdomain that is also a TLD, also test against URI with the domain
|
||||
// dropped after the TLD
|
||||
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
|
||||
var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
|
||||
var properHosts = [];
|
||||
var proxyHosts = [];
|
||||
if(m) {
|
||||
var hostnames = m[2].split(".");
|
||||
for(var i=1; i<hostnames.length-2; i++) {
|
||||
if(TLDS[hostnames[i].toLowerCase()]) {
|
||||
var properHost = hostnames.slice(0, i+1).join(".");
|
||||
searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
|
||||
properHosts.push(properHost);
|
||||
proxyHosts.push(hostnames.slice(i+1).join("."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var converterFunctions = [];
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
for(var j=0; j<searchURIs.length; j++) {
|
||||
// don't attempt to use translators with no target that can't be run in this browser
|
||||
// since that would require transmitting every page to Zotero host
|
||||
if(!allTranslators[i].webRegexp
|
||||
&& allTranslators[i].runMode !== Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!allTranslators[i].webRegexp
|
||||
|| (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
|
||||
// add translator to list
|
||||
potentialTranslators.push(allTranslators[i]);
|
||||
|
||||
if(j === 0) {
|
||||
converterFunctions.push(null);
|
||||
} else if(Zotero.isFx) {
|
||||
// in Firefox, push the converterFunction
|
||||
converterFunctions.push(new function() {
|
||||
var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
|
||||
var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
|
||||
return function(uri) { return uri.replace(re, "$&."+proxyHost) };
|
||||
});
|
||||
} else {
|
||||
// in Chrome/Safari, the converterFunction needs to be passed as JSON, so
|
||||
// just push an array with the proper and proxyHosts
|
||||
converterFunctions.push([properHosts[j-1], proxyHosts[j-1]]);
|
||||
}
|
||||
|
||||
// don't add translator more than once
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Zotero.Translators.CodeGetter(potentialTranslators, callback,
|
||||
[potentialTranslators, converterFunctions]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts translators to JSON-serializable objects
|
||||
*/
|
||||
this.serialize = function(translator) {
|
||||
// handle translator arrays
|
||||
if(translator.length !== undefined) {
|
||||
var newTranslators = new Array(translator.length);
|
||||
for(var i in translator) {
|
||||
newTranslators[i] = Zotero.Translators.serialize(translator[i]);
|
||||
}
|
||||
return newTranslators;
|
||||
}
|
||||
|
||||
// handle individual translator
|
||||
var newTranslator = {};
|
||||
for(var i in PRESERVE_PROPERTIES) {
|
||||
var property = PRESERVE_PROPERTIES[i];
|
||||
newTranslator[property] = translator[property];
|
||||
}
|
||||
return newTranslator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to get the code for a set of translators at once
|
||||
*/
|
||||
Zotero.Translators.CodeGetter = function(translators, callback, callbackArgs) {
|
||||
this._translators = translators;
|
||||
this._callbackArgs = callbackArgs;
|
||||
this._callback = callback;
|
||||
this.getCodeFor(0);
|
||||
}
|
||||
|
||||
Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
|
||||
var me = this;
|
||||
while(true) {
|
||||
if(i === this._translators.length) {
|
||||
// all done; run callback
|
||||
this._callback(this._callbackArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this._translators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
// get next translator
|
||||
this._translators[i].getCode(function() { me.getCodeFor(i+1) });
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are not at end of list and there is no reason to retrieve the code, keep going
|
||||
// through the list of potential translators
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const TRANSLATOR_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
|
||||
"priority", "browserSupport"];
|
||||
var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions",
|
||||
"code", "runMode"]);
|
||||
/**
|
||||
* @class Represents an individual translator
|
||||
* @constructor
|
||||
* @property {String} translatorID Unique GUID of the translator
|
||||
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
|
||||
* @property {String} label Human-readable name of the translator
|
||||
* @property {String} creator Author(s) of the translator
|
||||
* @property {String} target Location that the translator processes
|
||||
* @property {String} minVersion Minimum Zotero version
|
||||
* @property {String} maxVersion Minimum Zotero version
|
||||
* @property {Integer} priority Lower-priority translators will be selected first
|
||||
* @property {String} browserSupport String indicating browser supported by the translator
|
||||
* g = Gecko (Firefox)
|
||||
* c = Google Chrome (WebKit & V8)
|
||||
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
|
||||
* i = Internet Explorer
|
||||
* @property {Object} configOptions Configuration options for import/export
|
||||
* @property {Object} displayOptions Display options for export
|
||||
* @property {Boolean} inRepository Whether the translator may be found in the repository
|
||||
* @property {String} lastUpdated SQL-style date and time of translator's last update
|
||||
* @property {String} code The executable JavaScript for the translator
|
||||
*/
|
||||
Zotero.Translator = function(info) {
|
||||
// make sure we have all the properties
|
||||
for(var i in TRANSLATOR_PROPERTIES) {
|
||||
var property = TRANSLATOR_PROPERTIES[i];
|
||||
if(info[property] === undefined) {
|
||||
this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label);
|
||||
haveMetadata = false;
|
||||
break;
|
||||
} else {
|
||||
this[property] = info[property];
|
||||
}
|
||||
}
|
||||
|
||||
if(info["browserSupport"].indexOf(Zotero.browser) !== -1) {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
} else {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
|
||||
}
|
||||
|
||||
this._configOptions = info["configOptions"] ? info["configOptions"] : {};
|
||||
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
|
||||
|
||||
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
||||
// compile import regexp to match only file extension
|
||||
this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
|
||||
}
|
||||
|
||||
if(this.translatorType & TRANSLATOR_TYPES["web"]) {
|
||||
// compile web regexp
|
||||
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
|
||||
}
|
||||
|
||||
if(info.code) {
|
||||
this.code = info.code;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Translator.prototype.getCode = function(callback) {
|
||||
if(this.code) {
|
||||
callback(true);
|
||||
} else {
|
||||
var me = this;
|
||||
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID},
|
||||
function(code) {
|
||||
if(!code) {
|
||||
callback(false);
|
||||
} else {
|
||||
me.code = code;
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Translator.prototype.__defineGetter__("displayOptions", function() {
|
||||
return Zotero.Utilities.deepCopy(this._displayOptions);
|
||||
});
|
||||
Zotero.Translator.prototype.__defineGetter__("configOptions", function() {
|
||||
return Zotero.Utilities.deepCopy(this._configOptions);
|
||||
});
|
||||
|
||||
/**
|
||||
* Log a translator-related error
|
||||
* @param {String} message The error message
|
||||
* @param {String} [type] The error type ("error", "warning", "exception", or "strict")
|
||||
* @param {String} [line] The text of the line on which the error occurred
|
||||
* @param {Integer} lineNumber
|
||||
* @param {Integer} colNumber
|
||||
*/
|
||||
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
|
||||
Zotero.log(message, type ? type : "error", this.label);
|
||||
}
|
||||
|
||||
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
|
|
@ -61,7 +61,7 @@ Zotero.Date = new function(){
|
|||
.getService(Components.interfaces.nsIStringBundleService).createBundle(src, appLocale);
|
||||
|
||||
_months = {"short":[], "long":[]};
|
||||
for(let i=1; i<=12; i++) {
|
||||
for(var i=1; i<=12; i++) {
|
||||
_months.short.push(bundle.GetStringFromName("month."+i+".Mmm"));
|
||||
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
|
|||
}
|
||||
dataset.push(row);
|
||||
}
|
||||
statement.reset();
|
||||
statement.finalize();
|
||||
|
||||
return dataset.length ? dataset : false;
|
||||
}
|
||||
|
@ -170,12 +170,12 @@ Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
|
|||
|
||||
// No rows
|
||||
if (!statement.executeStep()) {
|
||||
statement.reset();
|
||||
statement.finalize();
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = this._getTypedValue(statement, 0);
|
||||
statement.reset();
|
||||
statement.finalize();
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
|
|||
while (statement.executeStep()) {
|
||||
column.push(this._getTypedValue(statement, 0));
|
||||
}
|
||||
statement.reset();
|
||||
statement.finalize();
|
||||
return column.length ? column : false;
|
||||
}
|
||||
return false;
|
||||
|
@ -630,7 +630,7 @@ Zotero.DBConnection.prototype.getColumns = function (table) {
|
|||
for (var i=0,len=statement.columnCount; i<len; i++) {
|
||||
cols.push(statement.getColumnName(i));
|
||||
}
|
||||
statement.reset();
|
||||
statement.finalize();
|
||||
return cols;
|
||||
}
|
||||
catch (e) {
|
||||
|
@ -771,8 +771,11 @@ Zotero.DBConnection.prototype.checkException = function (e) {
|
|||
|
||||
|
||||
Zotero.DBConnection.prototype.closeDatabase = function () {
|
||||
var db = this._getDBConnection();
|
||||
db.close();
|
||||
if(this._connection) {
|
||||
this.stopDummyStatement();
|
||||
this._connection.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -855,10 +858,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
|
|||
var hadDummyStatement = !!this._dummyStatement;
|
||||
try {
|
||||
if (dbLockExclusive) {
|
||||
Zotero.DB.query("PRAGMA locking_mode=NORMAL");
|
||||
this.query("PRAGMA locking_mode=NORMAL");
|
||||
}
|
||||
if (hadDummyStatement) {
|
||||
Zotero.DB.stopDummyStatement();
|
||||
this.stopDummyStatement();
|
||||
}
|
||||
|
||||
var store = Components.classes["@mozilla.org/storage/service;1"].
|
||||
|
@ -872,10 +875,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
|
|||
}
|
||||
finally {
|
||||
if (dbLockExclusive) {
|
||||
Zotero.DB.query("PRAGMA locking_mode=EXCLUSIVE");
|
||||
this.query("PRAGMA locking_mode=EXCLUSIVE");
|
||||
}
|
||||
if (hadDummyStatement) {
|
||||
Zotero.DB.startDummyStatement();
|
||||
this.startDummyStatement();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1003,8 +1006,10 @@ Zotero.DBConnection.prototype.stopDummyStatement = function () {
|
|||
}
|
||||
|
||||
Zotero.debug("Stopping dummy statement for '" + this._dbName + "'");
|
||||
this._dummyStatement.reset();
|
||||
this._dummyStatement = null;
|
||||
this._dummyStatement.finalize();
|
||||
this._dummyConnection.close();
|
||||
delete this._dummyConnection;
|
||||
delete this._dummyStatement;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
|
||||
|
||||
Zotero.Debug = new function () {
|
||||
this.__defineGetter__('storing', function () _store);
|
||||
this.__defineGetter__('enabled', function () _console || _store);
|
||||
this.__defineGetter__('storing', function () { return _store; });
|
||||
this.__defineGetter__('enabled', function () { return _console || _store; });
|
||||
|
||||
var _console;
|
||||
var _store;
|
||||
|
@ -81,7 +81,12 @@ Zotero.Debug = new function () {
|
|||
}
|
||||
|
||||
if (_console) {
|
||||
dump('zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message + "\n\n");
|
||||
var output = 'zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message;
|
||||
if(Zotero.isFx) {
|
||||
dump(output+"\n\n");
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
}
|
||||
if (_store) {
|
||||
if (Math.random() < 1/1000) {
|
||||
|
|
|
@ -31,7 +31,6 @@ const DATA_VERSION = 3;
|
|||
// this is used only for update checking
|
||||
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
|
||||
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
|
||||
const INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
|
||||
|
||||
Zotero.Integration = new function() {
|
||||
var _fifoFile = null;
|
||||
|
@ -39,8 +38,7 @@ Zotero.Integration = new function() {
|
|||
var _osascriptFile;
|
||||
var _inProgress = false;
|
||||
var _integrationVersionsOK = null;
|
||||
var _pipeMode = false;
|
||||
var _winUser32;
|
||||
var INTEGRATION_MIN_VERSIONS;
|
||||
|
||||
// these need to be global because of GC
|
||||
var _timer;
|
||||
|
@ -52,29 +50,32 @@ Zotero.Integration = new function() {
|
|||
* Initializes the pipe used for integration on non-Windows platforms.
|
||||
*/
|
||||
this.init = function() {
|
||||
// initialize SOAP server just to throw version errors
|
||||
Zotero.Integration.Compat.init();
|
||||
if(Zotero.isMac || Zotero.isWin) { // on Mac or Windows, we don't have pipe issues
|
||||
INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
|
||||
} else { // on *NIX, there's no point in supporting 3.1b1
|
||||
INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.5a1", "3.1b1"];
|
||||
}
|
||||
|
||||
// Windows uses a command line handler for integration. See
|
||||
// We only use an integration pipe on OS X.
|
||||
// On Linux, we use the alternative communication method in the OOo plug-in
|
||||
// On Windows, we use a command line handler for integration. See
|
||||
// components/zotero-integration-service.js for this implementation.
|
||||
if(Zotero.isWin) return;
|
||||
if(!Zotero.isMac) return;
|
||||
|
||||
// Determine where to put the pipe
|
||||
if(Zotero.isMac) {
|
||||
// on OS X, first try /Users/Shared for those who can't put pipes in their home
|
||||
// directories
|
||||
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
_fifoFile.initWithPath("/Users/Shared");
|
||||
|
||||
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
|
||||
var logname = Components.classes["@mozilla.org/process/environment;1"].
|
||||
getService(Components.interfaces.nsIEnvironment).
|
||||
get("LOGNAME");
|
||||
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
|
||||
} else {
|
||||
_fifoFile = null;
|
||||
}
|
||||
// on OS X, first try /Users/Shared for those who can't put pipes in their home
|
||||
// directories
|
||||
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
_fifoFile.initWithPath("/Users/Shared");
|
||||
|
||||
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
|
||||
var logname = Components.classes["@mozilla.org/process/environment;1"].
|
||||
getService(Components.interfaces.nsIEnvironment).
|
||||
get("LOGNAME");
|
||||
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
|
||||
} else {
|
||||
_fifoFile = null;
|
||||
}
|
||||
|
||||
if(!_fifoFile) {
|
||||
|
@ -85,8 +86,6 @@ Zotero.Integration = new function() {
|
|||
_fifoFile.append(".zoteroIntegrationPipe");
|
||||
}
|
||||
|
||||
Zotero.debug("Initializing Zotero integration pipe at "+_fifoFile.path);
|
||||
|
||||
// destroy old pipe, if one exists
|
||||
try {
|
||||
if(_fifoFile.exists()) {
|
||||
|
@ -100,35 +99,27 @@ Zotero.Integration = new function() {
|
|||
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
|
||||
+ "for instructions on correcting this problem."
|
||||
);
|
||||
if(Zotero.isMac) {
|
||||
// can attempt to delete on OS X
|
||||
try {
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
|
||||
if(!deletePipe) return;
|
||||
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
|
||||
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
|
||||
if(_fifoFile.exists()) return;
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// can attempt to delete on OS X
|
||||
try {
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
|
||||
if(!deletePipe) return;
|
||||
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
|
||||
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
|
||||
if(_fifoFile.exists()) return;
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// try to initialize pipe
|
||||
try {
|
||||
var pipeInitialized = _initializeIntegrationPipe();
|
||||
Zotero.IPC.Pipe.initPipeListener(_fifoFile, _parseIntegrationPipeCommand);
|
||||
} catch(e) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
|
||||
if(pipeInitialized) {
|
||||
// if initialization succeeded, add an observer so that we don't hang shutdown
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
observerService.addObserver({ observe: Zotero.Integration.destroy }, "quit-application", false);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
_updateTimer = Components.classes["@mozilla.org/timer;1"].
|
||||
|
@ -210,246 +201,14 @@ Zotero.Integration = new function() {
|
|||
if(parts) {
|
||||
var agent = parts[1].toString();
|
||||
var cmd = parts[2].toString();
|
||||
|
||||
// return if we were told to shutdown
|
||||
if(agent === "Zotero" && cmd === "shutdown") return;
|
||||
|
||||
_initializePipeStreamPump();
|
||||
|
||||
var document = parts[3] ? parts[3].toString() : null;
|
||||
Zotero.Integration.execCommand(agent, cmd, document);
|
||||
} else {
|
||||
_initializePipeStreamPump();
|
||||
Components.utils.reportError("Zotero: Invalid integration input received: "+string);
|
||||
}
|
||||
} else {
|
||||
_initializePipeStreamPump();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens asynchronously for data on the integration pipe and reads it when available
|
||||
*
|
||||
* Used to read from the integration pipe on Fx 4.2
|
||||
*/
|
||||
var _integrationPipeListenerFx42 = {
|
||||
"onStartRequest":function() {},
|
||||
"onStopRequest":function() {},
|
||||
|
||||
"onDataAvailable":function(request, context, inputStream, offset, count) {
|
||||
// read from pipe
|
||||
var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
converterInputStream.init(inputStream, "UTF-8", 4096,
|
||||
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
var out = {};
|
||||
converterInputStream.readString(count, out);
|
||||
inputStream.close();
|
||||
|
||||
_parseIntegrationPipeCommand(out.value);
|
||||
}};
|
||||
|
||||
/**
|
||||
* Polling mechanism for file
|
||||
*/
|
||||
var _integrationPipeObserverFx36 = {"notify":function() {
|
||||
if(_fifoFile.fileSize === 0) return;
|
||||
|
||||
// read from pipe (file, actually)
|
||||
var string = Zotero.File.getContents(_fifoFile);
|
||||
|
||||
// clear file
|
||||
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
|
||||
foStream.close();
|
||||
|
||||
// run command
|
||||
_parseIntegrationPipeCommand(string);
|
||||
}};
|
||||
|
||||
/**
|
||||
* Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
|
||||
*/
|
||||
function _initializePipeStreamPump() {
|
||||
// Fx >4 supports deferred open; no need to use sh
|
||||
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
|
||||
// 16 = open as deferred so that we don't block on open
|
||||
fifoStream.init(_fifoFile, -1, 0, 16);
|
||||
|
||||
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
|
||||
createInstance(Components.interfaces.nsIInputStreamPump);
|
||||
pump.init(fifoStream, -1, -1, 4096, 1, true);
|
||||
pump.asyncRead(_integrationPipeListenerFx42, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Zotero Integration Pipe
|
||||
*/
|
||||
function _initializeIntegrationPipe() {
|
||||
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
|
||||
.getService(Components.interfaces.nsIVersionComparator);
|
||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
|
||||
getService(Components.interfaces.nsIXULAppInfo);
|
||||
if(Zotero.isFx4) {
|
||||
if(verComp.compare("2.0b9pre", appInfo.platformVersion) > 0) {
|
||||
Components.utils.reportError("Zotero word processor integration requires "+
|
||||
"Firefox 4.0b9 or later. Please update to the latest Firefox 4.0 beta.");
|
||||
return;
|
||||
} else if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) {
|
||||
_pipeMode = "deferredOpen";
|
||||
} else {
|
||||
_pipeMode = "fx4thread";
|
||||
}
|
||||
} else {
|
||||
if(Zotero.isMac) {
|
||||
_pipeMode = "poll";
|
||||
} else {
|
||||
_pipeMode = "fx36thread";
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Using integration pipe mode "+_pipeMode);
|
||||
|
||||
if(_pipeMode === "poll") {
|
||||
// create empty file
|
||||
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
|
||||
foStream.close();
|
||||
|
||||
// no deferred open capability, so we need to poll
|
||||
// has to be global so that we don't get garbage collected
|
||||
_timer = Components.classes["@mozilla.org/timer;1"].
|
||||
createInstance(Components.interfaces.nsITimer);
|
||||
_timer.initWithCallback(_integrationPipeObserverFx36, 1000,
|
||||
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
||||
} else {
|
||||
// make a new pipe
|
||||
var mkfifo = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
mkfifo.initWithPath("/usr/bin/mkfifo");
|
||||
if(!mkfifo.exists()) mkfifo.initWithPath("/bin/mkfifo");
|
||||
if(!mkfifo.exists()) mkfifo.initWithPath("/usr/local/bin/mkfifo");
|
||||
|
||||
if(mkfifo.exists()) {
|
||||
// create named pipe
|
||||
var proc = Components.classes["@mozilla.org/process/util;1"].
|
||||
createInstance(Components.interfaces.nsIProcess);
|
||||
proc.init(mkfifo);
|
||||
proc.run(true, [_fifoFile.path], 1);
|
||||
|
||||
if(_fifoFile.exists()) {
|
||||
if(_pipeMode === "deferredOpen") {
|
||||
_initializePipeStreamPump();
|
||||
} else if(_pipeMode === "fx36thread") {
|
||||
var main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
|
||||
var background = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);
|
||||
|
||||
function mainThread(agent, cmd, doc) {
|
||||
this.agent = agent;
|
||||
this.cmd = cmd;
|
||||
this.document = doc;
|
||||
}
|
||||
mainThread.prototype.run = function() {
|
||||
Zotero.Integration.execCommand(this.agent, this.cmd, this.document);
|
||||
}
|
||||
|
||||
function fifoThread() {}
|
||||
fifoThread.prototype.run = function() {
|
||||
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var line = {};
|
||||
while(true) {
|
||||
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
|
||||
fifoStream.init(_fifoFile, -1, 0, 0);
|
||||
fifoStream.QueryInterface(Components.interfaces.nsILineInputStream);
|
||||
fifoStream.readLine(line);
|
||||
fifoStream.close();
|
||||
|
||||
var parts = line.value.split(" ");
|
||||
var agent = parts[0];
|
||||
var cmd = parts[1];
|
||||
var document = parts.length >= 3 ? line.value.substr(agent.length+cmd.length+2) : null;
|
||||
if(agent == "Zotero" && cmd == "shutdown") return;
|
||||
main.dispatch(new mainThread(agent, cmd, document), background.DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
fifoThread.prototype.QueryInterface = mainThread.prototype.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIRunnable) ||
|
||||
iid.equals(Components.interfaces.nsISupports)) return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
background.dispatch(new fifoThread(), background.DISPATCH_NORMAL);
|
||||
} else if(_pipeMode === "fx4thread") {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
// get possible names for libc
|
||||
if(Zotero.isMac) {
|
||||
var possibleLibcs = ["/usr/lib/libc.dylib"];
|
||||
} else {
|
||||
var possibleLibcs = [
|
||||
"libc.so.6",
|
||||
"libc.so.6.1",
|
||||
"libc.so"
|
||||
];
|
||||
}
|
||||
|
||||
// try all possibilities
|
||||
while(possibleLibcs.length) {
|
||||
var libc = possibleLibcs.shift();
|
||||
try {
|
||||
var lib = ctypes.open(libc);
|
||||
break;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// throw appropriate error on failure
|
||||
if(!lib) {
|
||||
throw "libc could not be loaded. Please post on the Zotero Forums so we can add "+
|
||||
"support for your operating system.";
|
||||
}
|
||||
|
||||
// int mkfifo(const char *path, mode_t mode);
|
||||
var mkfifo = lib.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
|
||||
|
||||
// make pipe
|
||||
var ret = mkfifo(_fifoFile.path, 0600);
|
||||
if(!_fifoFile.exists()) return false;
|
||||
lib.close();
|
||||
|
||||
// set up worker
|
||||
var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
|
||||
.createInstance(Components.interfaces.nsIWorkerFactory)
|
||||
.newChromeWorker("chrome://zotero/content/xpcom/integration_worker.js");
|
||||
worker.onmessage = function(event) {
|
||||
if(event.data[0] == "Exception") {
|
||||
throw event.data[1];
|
||||
} else if(event.data[0] == "Debug") {
|
||||
Zotero.debug(event.data[1]);
|
||||
} else {
|
||||
Zotero.Integration.execCommand(event.data[0], event.data[1], event.data[2]);
|
||||
}
|
||||
}
|
||||
worker.postMessage({"path":_fifoFile.path, "libc":libc});
|
||||
}
|
||||
} else {
|
||||
Components.utils.reportError("Zotero: mkfifo failed -- not initializing integration pipe");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Components.utils.reportError("Zotero: mkfifo or sh not found -- not initializing integration pipe");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
484
chrome/content/zotero/xpcom/ipc.js
Executable file
484
chrome/content/zotero/xpcom/ipc.js
Executable file
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.IPC = new function() {
|
||||
var _libc, _libcPath, _instancePipe, _user32;
|
||||
|
||||
/**
|
||||
* Initialize pipe for communication with connector
|
||||
*/
|
||||
this.init = function() {
|
||||
if(!Zotero.isWin && (Zotero.isFx4 || Zotero.isMac)) { // no pipe support on Fx 3.6
|
||||
_instancePipe = _getPipeDirectory();
|
||||
if(!_instancePipe.exists()) {
|
||||
_instancePipe.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
|
||||
}
|
||||
_instancePipe.append(Zotero.instanceID);
|
||||
|
||||
Zotero.IPC.Pipe.initPipeListener(_instancePipe, this.parsePipeInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses input received via instance pipe
|
||||
*/
|
||||
this.parsePipeInput = function(msg) {
|
||||
// remove a newline if there is one
|
||||
if(msg[msg.length-1] === "\n") msg = msg.substr(0, msg.length-1);
|
||||
|
||||
Zotero.debug('IPC: Received "'+msg+'"');
|
||||
|
||||
if(msg === "releaseLock" && !Zotero.isConnector) {
|
||||
switchConnectorMode(true);
|
||||
} else if(msg === "lockReleased") {
|
||||
Zotero.onDBLockReleased();
|
||||
} else if(msg === "initComplete") {
|
||||
Zotero.onInitComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message to all other Zotero instances
|
||||
*/
|
||||
this.broadcast = function(msg) {
|
||||
if(Zotero.isWin) { // communicate via WM_COPYDATA method
|
||||
// there is no ctypes struct support in Fx 3.6
|
||||
// while we could mimic it, it's easier just to require users to upgrade if they
|
||||
// want connector sharing
|
||||
if(!Zotero.isFx4) return false;
|
||||
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
// communicate via message window
|
||||
var user32 = ctypes.open("user32.dll");
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms633499%28v=vs.85%29.aspx
|
||||
* HWND WINAPI FindWindow(
|
||||
* __in_opt LPCTSTR lpClassName,
|
||||
* __in_opt LPCTSTR lpWindowName
|
||||
* );
|
||||
*/
|
||||
var FindWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.int32_t,
|
||||
ctypes.jschar.ptr, ctypes.jschar.ptr);
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms633539%28v=vs.85%29.aspx
|
||||
* BOOL WINAPI SetForegroundWindow(
|
||||
* __in HWND hWnd
|
||||
* );
|
||||
*/
|
||||
var SetForegroundWindow = user32.declare("SetForegroundWindow", ctypes.winapi_abi,
|
||||
ctypes.bool, ctypes.int32_t);
|
||||
|
||||
/*
|
||||
* LRESULT WINAPI SendMessage(
|
||||
* __in HWND hWnd,
|
||||
* __in UINT Msg,
|
||||
* __in WPARAM wParam,
|
||||
* __in LPARAM lParam
|
||||
* );
|
||||
*/
|
||||
var SendMessage = user32.declare("SendMessageW", ctypes.winapi_abi, ctypes.uintptr_t,
|
||||
ctypes.int32_t, ctypes.unsigned_int, ctypes.voidptr_t, ctypes.voidptr_t);
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/ms649010%28v=vs.85%29.aspx
|
||||
* typedef struct tagCOPYDATASTRUCT {
|
||||
* ULONG_PTR dwData;
|
||||
* DWORD cbData;
|
||||
* PVOID lpData;
|
||||
* } COPYDATASTRUCT, *PCOPYDATASTRUCT;
|
||||
*/
|
||||
var COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT", [
|
||||
{"dwData":ctypes.voidptr_t},
|
||||
{"cbData":ctypes.uint32_t},
|
||||
{"lpData":ctypes.voidptr_t}
|
||||
]);
|
||||
|
||||
const appNames = ["Firefox", "Zotero", "Nightly", "Aurora", "Minefield"];
|
||||
for each(var appName in appNames) {
|
||||
// don't send messages to ourself
|
||||
if(appName === Zotero.appName) continue;
|
||||
|
||||
var thWnd = FindWindow(appName+"MessageWindow", null);
|
||||
if(thWnd) {
|
||||
Zotero.debug('IPC: Broadcasting "'+msg+'" to window "'+appName+'MessageWindow"');
|
||||
|
||||
// allocate message
|
||||
var data = ctypes.char.array()('firefox.exe -ZoteroIPC "'+msg.replace('"', '""', "g")+'"\x00C:\\');
|
||||
var dataSize = data.length*data.constructor.size;
|
||||
|
||||
// create new COPYDATASTRUCT
|
||||
var cds = new COPYDATASTRUCT();
|
||||
cds.dwData = null;
|
||||
cds.cbData = dataSize;
|
||||
cds.lpData = data.address();
|
||||
|
||||
// send COPYDATASTRUCT
|
||||
var success = SendMessage(thWnd, 0x004A /** WM_COPYDATA **/, null, cds.address());
|
||||
|
||||
user32.close();
|
||||
return !!success;
|
||||
}
|
||||
}
|
||||
|
||||
user32.close();
|
||||
return false;
|
||||
} else { // communicate via pipes
|
||||
|
||||
// look for other Zotero instances
|
||||
var pipes = [];
|
||||
var pipeDir = _getPipeDirectory();
|
||||
if(pipeDir.exists()) {
|
||||
var dirEntries = pipeDir.directoryEntries;
|
||||
while (dirEntries.hasMoreElements()) {
|
||||
var pipe = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) {
|
||||
pipes.push(pipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!pipes.length) return false;
|
||||
|
||||
// safely write to instance pipes
|
||||
var lib = this.getLibc();
|
||||
if(!lib) return false;
|
||||
|
||||
// int open(const char *path, int oflag);
|
||||
if(Zotero.isFx36) {
|
||||
var open = lib.declare("open", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.int32_t);
|
||||
} else {
|
||||
var open = lib.declare("open", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int);
|
||||
}
|
||||
// ssize_t write(int fildes, const void *buf, size_t nbyte);
|
||||
if(Zotero.isFx36) {
|
||||
} else {
|
||||
var write = lib.declare("write", ctypes.default_abi, ctypes.ssize_t, ctypes.int, ctypes.char.ptr, ctypes.size_t);
|
||||
}
|
||||
// int close(int filedes);
|
||||
if(Zotero.isFx36) {
|
||||
} else {
|
||||
var close = lib.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
|
||||
}
|
||||
|
||||
var success = false;
|
||||
for each(var pipe in pipes) {
|
||||
var fd = open(pipe.path, 0x0004 | 0x0001); // O_NONBLOCK | O_WRONLY
|
||||
if(fd !== -1) {
|
||||
Zotero.debug('IPC: Broadcasting "'+msg+'" to instance '+pipe.leafName);
|
||||
success = true;
|
||||
write(fd, msg+"\n", msg.length);
|
||||
close(fd);
|
||||
} else {
|
||||
try {
|
||||
pipe.remove(true);
|
||||
} catch(e) {};
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directory containing Zotero pipes
|
||||
*/
|
||||
function _getPipeDirectory() {
|
||||
var dir = Zotero.getZoteroDirectory();
|
||||
dir.append("pipes");
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to libc as a string
|
||||
*/
|
||||
this.getLibcPath = function() {
|
||||
if(_libcPath) return _libcPath;
|
||||
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
// get possible names for libc
|
||||
if(Zotero.isMac) {
|
||||
var possibleLibcs = ["/usr/lib/libc.dylib"];
|
||||
} else {
|
||||
var possibleLibcs = [
|
||||
"libc.so.6",
|
||||
"libc.so.6.1",
|
||||
"libc.so"
|
||||
];
|
||||
}
|
||||
|
||||
// try all possibilities
|
||||
while(possibleLibcs.length) {
|
||||
var libPath = possibleLibcs.shift();
|
||||
try {
|
||||
var lib = ctypes.open(libPath);
|
||||
break;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// throw appropriate error on failure
|
||||
if(!lib) {
|
||||
Components.utils.reportError("Zotero: libc could not be loaded. Word processor integration "+
|
||||
"and other functionality will not be available. Please post on the Zotero Forums so we "+
|
||||
"can add support for your operating system.");
|
||||
return;
|
||||
}
|
||||
|
||||
_libc = lib;
|
||||
_libcPath = libPath;
|
||||
return libPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets standard C library via ctypes
|
||||
*/
|
||||
this.getLibc = function() {
|
||||
if(!_libc) this.getLibcPath();
|
||||
return _libc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods for reading from and writing to a pipe
|
||||
*/
|
||||
Zotero.IPC.Pipe = new function() {
|
||||
var _mkfifo, _pipeClass;
|
||||
|
||||
/**
|
||||
* Creates and listens on a pipe
|
||||
*
|
||||
* @param {nsIFile} file The location where the pipe should be created
|
||||
* @param {Function} callback A function to be passed any data recevied on the pipe
|
||||
*/
|
||||
this.initPipeListener = function(file, callback) {
|
||||
Zotero.debug("IPC: Initializing pipe at "+file.path);
|
||||
|
||||
// determine type of pipe
|
||||
if(!_pipeClass) {
|
||||
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
|
||||
.getService(Components.interfaces.nsIVersionComparator);
|
||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
|
||||
getService(Components.interfaces.nsIXULAppInfo);
|
||||
if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) { // Gecko 5
|
||||
_pipeClass = Zotero.IPC.Pipe.DeferredOpen;
|
||||
} else if(verComp.compare("2.0b9pre", appInfo.platformVersion) <= 0) { // Gecko 2.0b9+
|
||||
_pipeClass = Zotero.IPC.Pipe.WorkerThread;
|
||||
} else { // Gecko 1.9.2
|
||||
_pipeClass = Zotero.IPC.Pipe.Poll;
|
||||
}
|
||||
}
|
||||
|
||||
// make new pipe
|
||||
new _pipeClass(file, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a fifo
|
||||
* @param {nsIFile} file Location to create the fifo
|
||||
*/
|
||||
this.mkfifo = function(file) {
|
||||
// int mkfifo(const char *path, mode_t mode);
|
||||
if(!_mkfifo) {
|
||||
var libc = Zotero.IPC.getLibc();
|
||||
if(!libc) return false;
|
||||
if(Zotero.isFx36) {
|
||||
_mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.uint32_t);
|
||||
} else {
|
||||
_mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
|
||||
}
|
||||
}
|
||||
|
||||
// make pipe
|
||||
var ret = _mkfifo(file.path, 0600);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then
|
||||
* deletes it
|
||||
*/
|
||||
this.writeShutdownMessage = function(file) {
|
||||
var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
|
||||
getService(Components.interfaces.nsIFileOutputStream);
|
||||
oStream.init(file, 0x02 | 0x10, 0, 0);
|
||||
const cmd = "Zotero shutdown\n";
|
||||
oStream.write(cmd, cmd.length);
|
||||
oStream.close();
|
||||
file.remove(false);
|
||||
Zotero.debug("IPC: Closing pipe "+file.path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens asynchronously for data on the integration pipe and reads it when available
|
||||
*
|
||||
* Used to read from pipe on Gecko 5+
|
||||
*/
|
||||
Zotero.IPC.Pipe.DeferredOpen = function(file, callback) {
|
||||
this._file = file;
|
||||
this._callback = callback;
|
||||
|
||||
if(!Zotero.IPC.Pipe.mkfifo(file)) return;
|
||||
|
||||
this._initPump();
|
||||
|
||||
// add shutdown listener
|
||||
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
|
||||
}
|
||||
|
||||
Zotero.IPC.Pipe.DeferredOpen.prototype = {
|
||||
"onStartRequest":function() {},
|
||||
"onStopRequest":function() {},
|
||||
"onDataAvailable":function(request, context, inputStream, offset, count) {
|
||||
// read from pipe
|
||||
var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
converterInputStream.init(inputStream, "UTF-8", 4096,
|
||||
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
var out = {};
|
||||
converterInputStream.readString(count, out);
|
||||
inputStream.close();
|
||||
|
||||
if(out.value === "Zotero shutdown\n") return
|
||||
|
||||
this._initPump();
|
||||
this._callback(out.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
|
||||
*
|
||||
* Used after reading from file on Gecko 5+
|
||||
*/
|
||||
"_initPump":function() {
|
||||
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
|
||||
// 16 = open as deferred so that we don't block on open
|
||||
fifoStream.init(this._file, -1, 0, 16);
|
||||
|
||||
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
|
||||
createInstance(Components.interfaces.nsIInputStreamPump);
|
||||
pump.init(fifoStream, -1, -1, 4096, 1, true);
|
||||
pump.asyncRead(this, null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listens synchronously for data on the integration pipe on a separate JS thread and reads it
|
||||
* when available
|
||||
*
|
||||
* Used to read from pipe on Gecko 2
|
||||
*/
|
||||
Zotero.IPC.Pipe.WorkerThread = function(file, callback) {
|
||||
this._callback = callback;
|
||||
|
||||
if(!Zotero.IPC.Pipe.mkfifo(file)) return;
|
||||
|
||||
// set up worker
|
||||
var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
|
||||
.createInstance(Components.interfaces.nsIWorkerFactory)
|
||||
.newChromeWorker("chrome://zotero/content/xpcom/pipe_worker.js");
|
||||
worker.onmessage = this.onmessage.bind(this);
|
||||
worker.postMessage({"path":file.path, "libc":Zotero.IPC.getLibcPath()});
|
||||
|
||||
// add shutdown listener
|
||||
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
|
||||
}
|
||||
|
||||
Zotero.IPC.Pipe.WorkerThread.prototype = {
|
||||
/**
|
||||
* onmessage call for worker thread, to get data from it
|
||||
*/
|
||||
"onmessage":function(event) {
|
||||
if(event.data[0] === "Exception") {
|
||||
throw event.data[1];
|
||||
} else if(event.data[0] === "Debug") {
|
||||
Zotero.debug(event.data[1]);
|
||||
} else if(event.data[0] === "Read") {
|
||||
this._callback(event.data[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling mechanism for file
|
||||
*
|
||||
* Used to read from integration "pipe" on Gecko 1.9.2/Firefox 3.6
|
||||
*/
|
||||
Zotero.IPC.Pipe.Poll = function(file, callback) {
|
||||
this._file = file;
|
||||
this._callback = callback;
|
||||
|
||||
// create empty file
|
||||
this._clearFile();
|
||||
|
||||
// no deferred open capability, so we need to poll
|
||||
this._timer = Components.classes["@mozilla.org/timer;1"].
|
||||
createInstance(Components.interfaces.nsITimer);
|
||||
this._timer.initWithCallback(this, 1000,
|
||||
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
||||
|
||||
// this has to be in global scope so we don't get garbage collected
|
||||
Zotero.IPC.Pipe.Poll._activePipes.push(this);
|
||||
|
||||
// add shutdown listener
|
||||
Zotero.addShutdownListener(this);
|
||||
}
|
||||
Zotero.IPC.Pipe.Poll._activePipes = [];
|
||||
|
||||
Zotero.IPC.Pipe.Poll.prototype = {
|
||||
/**
|
||||
* Called every second to check if there is new data to be read
|
||||
*/
|
||||
"notify":function() {
|
||||
if(this._file.fileSize === 0) return;
|
||||
|
||||
// read from pipe (file, actually)
|
||||
var string = Zotero.File.getContents(this._file);
|
||||
this._clearFile();
|
||||
|
||||
// run command
|
||||
this._callback(string);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on quit to remove the file
|
||||
*/
|
||||
"observe":function() {
|
||||
this._file.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the old contents of the fifo file
|
||||
*/
|
||||
"_clearFile":function() {
|
||||
// clear file
|
||||
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
|
||||
foStream.close();
|
||||
}
|
||||
};
|
|
@ -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()];
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
|
|
379
chrome/content/zotero/xpcom/server.js
Executable file
379
chrome/content/zotero/xpcom/server.js
Executable file
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Server = new function() {
|
||||
var _onlineObserverRegistered;
|
||||
var responseCodes = {
|
||||
200:"OK",
|
||||
201:"Created",
|
||||
300:"Multiple Choices",
|
||||
400:"Bad Request",
|
||||
404:"Not Found",
|
||||
500:"Internal Server Error",
|
||||
501:"Method Not Implemented"
|
||||
};
|
||||
|
||||
/**
|
||||
* initializes a very rudimentary web server
|
||||
*/
|
||||
this.init = function() {
|
||||
if (Zotero.HTTP.browserIsOffline()) {
|
||||
Zotero.debug('Browser is offline -- not initializing HTTP server');
|
||||
_registerOnlineObserver();
|
||||
return;
|
||||
}
|
||||
|
||||
// start listening on socket
|
||||
var serv = Components.classes["@mozilla.org/network/server-socket;1"]
|
||||
.createInstance(Components.interfaces.nsIServerSocket);
|
||||
try {
|
||||
// bind to a random port on loopback only
|
||||
serv.init(Zotero.Prefs.get('httpServer.port'), true, -1);
|
||||
serv.asyncListen(Zotero.Server.SocketListener);
|
||||
|
||||
Zotero.debug("HTTP server listening on 127.0.0.1:"+serv.port);
|
||||
} catch(e) {
|
||||
Zotero.debug("Not initializing HTTP server");
|
||||
}
|
||||
|
||||
_registerOnlineObserver()
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the response to an HTTP request
|
||||
*/
|
||||
this.generateResponse = function (status, contentType, body) {
|
||||
var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
|
||||
response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
|
||||
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
|
||||
|
||||
if(body) {
|
||||
if(contentType) {
|
||||
response += "Content-Type: "+contentType+"\r\n";
|
||||
}
|
||||
response += "\r\n"+body;
|
||||
} else {
|
||||
response += "Content-Length: 0\r\n\r\n";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function _registerOnlineObserver() {
|
||||
if (_onlineObserverRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Observer to enable the integration when we go online
|
||||
var observer = {
|
||||
observe: function(subject, topic, data) {
|
||||
if (data == 'online') {
|
||||
Zotero.Server.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var observerService =
|
||||
Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
observerService.addObserver(observer, "network:offline-status-changed", false);
|
||||
|
||||
_onlineObserverRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Server.SocketListener = new function() {
|
||||
this.onSocketAccepted = onSocketAccepted;
|
||||
this.onStopListening = onStopListening;
|
||||
|
||||
/*
|
||||
* called when a socket is opened
|
||||
*/
|
||||
function onSocketAccepted(socket, transport) {
|
||||
// get an input stream
|
||||
var iStream = transport.openInputStream(0, 0, 0);
|
||||
var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
|
||||
|
||||
var dataListener = new Zotero.Server.DataListener(iStream, oStream);
|
||||
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
|
||||
.createInstance(Components.interfaces.nsIInputStreamPump);
|
||||
pump.init(iStream, -1, -1, 0, 0, false);
|
||||
pump.asyncRead(dataListener, null);
|
||||
}
|
||||
|
||||
function onStopListening(serverSocket, status) {
|
||||
Zotero.debug("HTTP server going offline");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* handles the actual acquisition of data
|
||||
*/
|
||||
Zotero.Server.DataListener = function(iStream, oStream) {
|
||||
this.header = "";
|
||||
this.headerFinished = false;
|
||||
|
||||
this.body = "";
|
||||
this.bodyLength = 0;
|
||||
|
||||
this.iStream = iStream;
|
||||
this.oStream = oStream;
|
||||
this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this.sStream.init(iStream);
|
||||
|
||||
this.foundReturn = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* called when a request begins (although the request should have begun before
|
||||
* the DataListener was generated)
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype.onStartRequest = function(request, context) {}
|
||||
|
||||
/*
|
||||
* called when a request stops
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype.onStopRequest = function(request, context, status) {
|
||||
this.iStream.close();
|
||||
this.oStream.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* called when new data is available
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype.onDataAvailable = function(request, context,
|
||||
inputStream, offset, count) {
|
||||
var readData = this.sStream.read(count);
|
||||
|
||||
if(this.headerFinished) { // reading body
|
||||
this.body += readData;
|
||||
// check to see if data is done
|
||||
this._bodyData();
|
||||
} else { // reading header
|
||||
// see if there's a magic double return
|
||||
var lineBreakIndex = readData.indexOf("\r\n\r\n");
|
||||
if(lineBreakIndex != -1) {
|
||||
if(lineBreakIndex != 0) {
|
||||
this.header += readData.substr(0, lineBreakIndex+4);
|
||||
this.body = readData.substr(lineBreakIndex+4);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
var lineBreakIndex = readData.indexOf("\n\n");
|
||||
if(lineBreakIndex != -1) {
|
||||
if(lineBreakIndex != 0) {
|
||||
this.header += readData.substr(0, lineBreakIndex+2);
|
||||
this.body = readData.substr(lineBreakIndex+2);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
if(this.header && this.header[this.header.length-1] == "\n" &&
|
||||
(readData[0] == "\n" || readData[0] == "\r")) {
|
||||
if(readData.length > 1 && readData[1] == "\n") {
|
||||
this.header += readData.substr(0, 2);
|
||||
this.body = readData.substr(2);
|
||||
} else {
|
||||
this.header += readData[0];
|
||||
this.body = readData.substr(1);
|
||||
}
|
||||
|
||||
this._headerFinished();
|
||||
return;
|
||||
}
|
||||
this.header += readData;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* processes an HTTP header and decides what to do
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._headerFinished = function() {
|
||||
this.headerFinished = true;
|
||||
|
||||
Zotero.debug(this.header);
|
||||
|
||||
const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
|
||||
const contentTypeRe = /[\r\n]Content-Type: +([^ \r\n]+)/i;
|
||||
|
||||
// get first line of request
|
||||
var method = methodRe.exec(this.header);
|
||||
// get content-type
|
||||
var contentType = contentTypeRe.exec(this.header);
|
||||
if(contentType) {
|
||||
var splitContentType = contentType[1].split(/\s*;/);
|
||||
this.contentType = splitContentType[0];
|
||||
}
|
||||
|
||||
if(!method) {
|
||||
this._requestFinished(Zotero.Server.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
if(!Zotero.Server.Endpoints[method[2]]) {
|
||||
this._requestFinished(Zotero.Server.generateResponse(404));
|
||||
return;
|
||||
}
|
||||
this.endpoint = Zotero.Server.Endpoints[method[2]];
|
||||
|
||||
if(method[1] == "HEAD" || method[1] == "OPTIONS") {
|
||||
this._requestFinished(Zotero.Server.generateResponse(200));
|
||||
} else if(method[1] == "GET") {
|
||||
this._requestFinished(this._processEndpoint("GET", method[3]));
|
||||
} else if(method[1] == "POST") {
|
||||
const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i;
|
||||
|
||||
// parse content length
|
||||
var m = contentLengthRe.exec(this.header);
|
||||
if(!m) {
|
||||
this._requestFinished(Zotero.Server.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
|
||||
this.bodyLength = parseInt(m[1]);
|
||||
this._bodyData();
|
||||
} else {
|
||||
this._requestFinished(Zotero.Server.generateResponse(501));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* checks to see if Content-Length bytes of body have been read and, if so, processes the body
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._bodyData = function() {
|
||||
if(this.body.length >= this.bodyLength) {
|
||||
// convert to UTF-8
|
||||
var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIStringInputStream);
|
||||
dataStream.setData(this.body, this.bodyLength);
|
||||
|
||||
var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
utf8Stream.init(dataStream, "UTF-8", 4096, "?");
|
||||
|
||||
this.body = "";
|
||||
var string = {};
|
||||
while(utf8Stream.readString(this.bodyLength, string)) {
|
||||
this.body += string.value;
|
||||
}
|
||||
|
||||
// handle envelope
|
||||
this._processEndpoint("POST", this.body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a response based on calling the function associated with the endpoint
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._processEndpoint = function(method, postData) {
|
||||
try {
|
||||
var endpoint = new this.endpoint;
|
||||
|
||||
// check that endpoint supports method
|
||||
if(endpoint.supportedMethods.indexOf(method) === -1) {
|
||||
this._requestFinished(Zotero.Server.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
|
||||
var decodedData = null;
|
||||
if(postData && this.contentType) {
|
||||
// check that endpoint supports contentType
|
||||
if(endpoint.supportedDataTypes.indexOf(this.contentType) === -1) {
|
||||
this._requestFinished(Zotero.Server.generateResponse(400));
|
||||
return;
|
||||
}
|
||||
|
||||
// decode JSON or urlencoded post data, and pass through anything else
|
||||
if(this.contentType === "application/json") {
|
||||
decodedData = JSON.parse(postData);
|
||||
} else if(this.contentType === "application/x-www-urlencoded") {
|
||||
var splitData = postData.split("&");
|
||||
decodedData = {};
|
||||
for each(var variable in splitData) {
|
||||
var splitIndex = variable.indexOf("=");
|
||||
data[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
|
||||
}
|
||||
} else {
|
||||
decodedData = postData;
|
||||
}
|
||||
}
|
||||
|
||||
// set up response callback
|
||||
var me = this;
|
||||
var sendResponseCallback = function(code, contentType, arg) {
|
||||
me._requestFinished(Zotero.Server.generateResponse(code, contentType, arg));
|
||||
}
|
||||
|
||||
// pass to endpoint
|
||||
endpoint.init(decodedData, sendResponseCallback);
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
this._requestFinished(Zotero.Server.generateResponse(500));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* returns HTTP data from a request
|
||||
*/
|
||||
Zotero.Server.DataListener.prototype._requestFinished = function(response) {
|
||||
// close input stream
|
||||
this.iStream.close();
|
||||
|
||||
// open UTF-8 converter for output stream
|
||||
var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
||||
|
||||
// write
|
||||
try {
|
||||
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
|
||||
|
||||
// write response
|
||||
Zotero.debug(response);
|
||||
intlStream.writeString(response);
|
||||
} finally {
|
||||
intlStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Endpoints for the HTTP server
|
||||
*
|
||||
* Each endpoint should take the form of an object. The init() method of this object will be passed:
|
||||
* method - the method of the request ("GET" or "POST")
|
||||
* data - the query string (for a "GET" request) or POST data (for a "POST" request)
|
||||
* sendResponseCallback - a function to send a response to the HTTP request. This can be passed
|
||||
* a response code alone (e.g., sendResponseCallback(404)) or a response
|
||||
* code, MIME type, and response body
|
||||
* (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
|
||||
*
|
||||
* See connector/server_connector.js for examples
|
||||
*/
|
||||
Zotero.Server.Endpoints = {}
|
607
chrome/content/zotero/xpcom/server_connector.js
Executable file
607
chrome/content/zotero/xpcom/server_connector.js
Executable file
|
@ -0,0 +1,607 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Server.Connector = function() {};
|
||||
Zotero.Server.Connector._waitingForSelection = {};
|
||||
Zotero.Server.Connector.Data = {};
|
||||
|
||||
/**
|
||||
* Manage cookies in a sandboxed fashion
|
||||
*
|
||||
* @param {browser} browser Hidden browser object
|
||||
* @param {String} uri URI of page to manage cookies for (cookies for domains that are not
|
||||
* subdomains of this URI are ignored)
|
||||
* @param {String} cookieData Cookies with which to initiate the sandbox
|
||||
*/
|
||||
Zotero.Server.Connector.CookieManager = function(browser, uri, cookieData) {
|
||||
this._webNav = browser.webNavigation;
|
||||
this._browser = browser;
|
||||
this._watchedBrowsers = [browser];
|
||||
this._observerService = Components.classes["@mozilla.org/observer-service;1"].
|
||||
getService(Components.interfaces.nsIObserverService);
|
||||
|
||||
this._uri = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService)
|
||||
.newURI(uri, null, null);
|
||||
|
||||
var splitCookies = cookieData.split(/; ?/);
|
||||
this._cookies = {};
|
||||
for each(var cookie in splitCookies) {
|
||||
var key = cookie.substr(0, cookie.indexOf("="));
|
||||
var value = cookie.substr(cookie.indexOf("=")+1);
|
||||
this._cookies[key] = value;
|
||||
}
|
||||
|
||||
[this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
|
||||
}
|
||||
|
||||
Zotero.Server.Connector.CookieManager.prototype = {
|
||||
"_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
|
||||
"_watchedXHRs":[],
|
||||
|
||||
/**
|
||||
* nsIObserver implementation for adding, clearing, and slurping cookies
|
||||
*/
|
||||
"observe": function(channel, topic) {
|
||||
if(topic == "quit-application") {
|
||||
Zotero.debug("WARNING: A Zotero.Server.CookieManager for "+this._uri.spec+" was still open on shutdown");
|
||||
} else {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
var isTracked = null;
|
||||
try {
|
||||
var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
|
||||
for each(var browser in this._watchedBrowsers) {
|
||||
isTracked = topDoc == browser.contentDocument;
|
||||
if(isTracked) break;
|
||||
}
|
||||
} catch(e) {}
|
||||
if(isTracked === null) {
|
||||
try {
|
||||
isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
|
||||
} catch(e) {}
|
||||
}
|
||||
if(isTracked === null) {
|
||||
try {
|
||||
isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// isTracked is now either true, false, or null
|
||||
// true => we should manage cookies for this request
|
||||
// false => we should not manage cookies for this request
|
||||
// null => this request is of a type we couldn't match to this request. one such type
|
||||
// is a link prefetch (nsPrefetchNode) but there might be others as well. for
|
||||
// now, we are paranoid and reject these.
|
||||
|
||||
if(isTracked === false) {
|
||||
Zotero.debug("Zotero.Server.CookieManager: not touching channel for "+channel.URI.spec);
|
||||
return;
|
||||
} else if(isTracked) {
|
||||
Zotero.debug("Zotero.Server.CookieManager: managing cookies for "+channel.URI.spec);
|
||||
} else {
|
||||
Zotero.debug("Zotero.Server.CookieManager: being paranoid about channel for "+channel.URI.spec);
|
||||
}
|
||||
|
||||
if(topic == "http-on-modify-request") {
|
||||
// clear cookies to be sent to other domains
|
||||
if(isTracked === null || channel.URI.host != this._uri.host) {
|
||||
channel.setRequestHeader("Cookie", "", false);
|
||||
channel.setRequestHeader("Cookie2", "", false);
|
||||
Zotero.debug("Zotero.Server.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// add cookies to be sent to this domain
|
||||
var cookies = [key+"="+this._cookies[key]
|
||||
for(key in this._cookies)].join("; ");
|
||||
channel.setRequestHeader("Cookie", cookies, false);
|
||||
Zotero.debug("Zotero.Server.CookieManager: added cookies for request to "+channel.URI.spec);
|
||||
} else if(topic == "http-on-examine-response") {
|
||||
// clear cookies being received
|
||||
try {
|
||||
var cookieHeader = channel.getResponseHeader("Set-Cookie");
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
channel.setResponseHeader("Set-Cookie", "", false);
|
||||
channel.setResponseHeader("Set-Cookie2", "", false);
|
||||
|
||||
// don't process further if these cookies are for another set of domains
|
||||
if(isTracked === null || channel.URI.host != this._uri.host) {
|
||||
Zotero.debug("Zotero.Server.CookieManager: rejected cookies from "+channel.URI.spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// put new cookies into our sandbox
|
||||
if(cookieHeader) {
|
||||
var cookies = cookieHeader.split(/; ?/);
|
||||
var newCookies = {};
|
||||
for each(var cookie in cookies) {
|
||||
var key = cookie.substr(0, cookie.indexOf("="));
|
||||
var value = cookie.substr(cookie.indexOf("=")+1);
|
||||
var lcCookie = key.toLowerCase();
|
||||
|
||||
if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
|
||||
// ignore cookie parameters; we are only holding cookies for a few minutes
|
||||
// with a single domain, and the path attribute doesn't allow any additional
|
||||
// security.
|
||||
// DEBUG: does ignoring the path attribute break any sites?
|
||||
continue;
|
||||
} else if(lcCookie == "secure") {
|
||||
// don't accept secure cookies
|
||||
newCookies = {};
|
||||
break;
|
||||
} else {
|
||||
newCookies[key] = value;
|
||||
}
|
||||
}
|
||||
[this._cookies[key] = newCookies[key] for(key in newCookies)];
|
||||
}
|
||||
|
||||
Zotero.debug("Zotero.Server.CookieManager: slurped cookies from "+channel.URI.spec);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach CookieManager to a specific XMLHttpRequest
|
||||
* @param {XMLHttpRequest} xhr
|
||||
*/
|
||||
"attachToBrowser": function(browser) {
|
||||
this._watchedBrowsers.push(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach CookieManager to a specific XMLHttpRequest
|
||||
* @param {XMLHttpRequest} xhr
|
||||
*/
|
||||
"attachToXHR": function(xhr) {
|
||||
this._watchedXHRs.push(xhr);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys this CookieManager (intended to be executed when the browser is destroyed)
|
||||
*/
|
||||
"destroy": function() {
|
||||
[this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lists all available translators, including code for translators that should be run on every page
|
||||
*
|
||||
* Accepts:
|
||||
* browser - one-letter code of the current browser
|
||||
* g = Gecko (Firefox)
|
||||
* c = Google Chrome (WebKit & V8)
|
||||
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
|
||||
* i = Internet Explorer
|
||||
* Returns:
|
||||
* translators - Zotero.Translator objects
|
||||
* schema - Some information about the database. Currently includes:
|
||||
* itemTypes
|
||||
* name
|
||||
* localizedString
|
||||
* creatorTypes
|
||||
* fields
|
||||
* baseFields
|
||||
* creatorTypes
|
||||
* name
|
||||
* localizedString
|
||||
* fields
|
||||
* name
|
||||
* localizedString
|
||||
*/
|
||||
Zotero.Server.Connector.GetData = function() {};
|
||||
Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData;
|
||||
Zotero.Server.Connector.GetData.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Gets available translator list and other important data
|
||||
* @param {Object} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
// Translator data
|
||||
var responseData = {"preferences":{}, "translators":[]};
|
||||
|
||||
// TODO only send necessary translators
|
||||
var translators = Zotero.Translators.getAll();
|
||||
for each(var translator in translators) {
|
||||
let serializableTranslator = {};
|
||||
for each(var key in ["translatorID", "translatorType", "label", "creator", "target",
|
||||
"priority", "browserSupport"]) {
|
||||
serializableTranslator[key] = translator[key];
|
||||
}
|
||||
|
||||
// Do not pass targetless translators that do not support this browser (since that
|
||||
// would mean passing each page back to Zotero)
|
||||
responseData.translators.push(serializableTranslator);
|
||||
}
|
||||
|
||||
// Various DB data (only sending what is required at the moment)
|
||||
var systemVersion = Zotero.Schema.getDBVersion("system");
|
||||
if(systemVersion != data.systemVersion) {
|
||||
responseData.schema = this._generateTypeSchema();
|
||||
}
|
||||
|
||||
// Preferences
|
||||
var prefs = Zotero.Prefs.prefBranch.getChildList("", {}, {});
|
||||
for each(var pref in prefs) {
|
||||
responseData.preferences[pref] = Zotero.Prefs.get(pref);
|
||||
}
|
||||
|
||||
sendResponseCallback(200, "application/json", JSON.stringify(responseData));
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a type schema. This is used by connector/type.js to handle types without DB access.
|
||||
*/
|
||||
"_generateTypeSchema":function() {
|
||||
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
|
||||
var types = Zotero.ItemTypes.getTypes();
|
||||
|
||||
var fieldIDs = Zotero.DB.columnQuery("SELECT fieldID FROM fieldsCombined");
|
||||
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
||||
for each(var fieldID in fieldIDs) {
|
||||
var fieldObj = {"name":Zotero.ItemFields.getName(fieldID)};
|
||||
try {
|
||||
fieldObj.localizedString = Zotero.getString("itemFields." + fieldObj.name)
|
||||
} catch(e) {}
|
||||
schema.fields[fieldID] = fieldObj;
|
||||
}
|
||||
|
||||
// names, localizedStrings, creatorTypes, and fields for each item type
|
||||
for each(var type in types) {
|
||||
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
|
||||
var baseFields = {};
|
||||
for each(var fieldID in fieldIDs) {
|
||||
if(baseMappedFields.indexOf(fieldID) !== -1) {
|
||||
baseFields[fieldID] = Zotero.ItemFields.getFieldIDFromTypeAndBase(type.id, fieldID);
|
||||
}
|
||||
}
|
||||
|
||||
var icon = Zotero.ItemTypes.getImageSrc(type.name);
|
||||
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||
|
||||
schema.itemTypes[type.id] = {"name":type.name,
|
||||
"localizedString":Zotero.ItemTypes.getLocalizedString(type.name),
|
||||
"creatorTypes":[creatorType.id for each(creatorType in Zotero.CreatorTypes.getTypesForItemType(type.id))],
|
||||
"fields":fieldIDs, "baseFields":baseFields, "icon":icon};
|
||||
|
||||
}
|
||||
|
||||
var types = Zotero.CreatorTypes.getTypes();
|
||||
for each(var type in types) {
|
||||
schema.creatorTypes[type.id] = {"name":type.name,
|
||||
"localizedString":Zotero.CreatorTypes.getLocalizedString(type.name)};
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether there is an available translator to handle a given page
|
||||
*
|
||||
* Accepts:
|
||||
* uri - The URI of the page to be saved
|
||||
* html - document.innerHTML or equivalent
|
||||
* cookie - document.cookie or equivalent
|
||||
*
|
||||
* Returns a list of available translators as an array
|
||||
*/
|
||||
Zotero.Server.Connector.Detect = function() {};
|
||||
Zotero.Server.Endpoints["/connector/detect"] = Zotero.Server.Connector.Detect;
|
||||
Zotero.Server.Connector.Data = {};
|
||||
Zotero.Server.Connector.Detect.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Loads HTML into a hidden browser and initiates translator detection
|
||||
* @param {Object} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
this._sendResponse = sendResponseCallback;
|
||||
this._parsedPostData = data;
|
||||
|
||||
this._translate = new Zotero.Translate("web");
|
||||
this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
|
||||
|
||||
Zotero.Server.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
|
||||
this._browser = Zotero.Browser.createHiddenBrowser();
|
||||
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
|
||||
|
||||
var pageShowCalled = false;
|
||||
var me = this;
|
||||
this._translate.setCookieManager(new Zotero.Server.Connector.CookieManager(this._browser,
|
||||
this._parsedPostData["uri"], this._parsedPostData["cookie"]));
|
||||
this._browser.addEventListener("DOMContentLoaded", function() {
|
||||
try {
|
||||
if(me._browser.contentDocument.location.href == "about:blank") return;
|
||||
if(pageShowCalled) return;
|
||||
pageShowCalled = true;
|
||||
delete Zotero.Server.Connector.Data[me._parsedPostData["uri"]];
|
||||
|
||||
// get translators
|
||||
me._translate.setDocument(me._browser.contentDocument);
|
||||
me._translate.getTranslators();
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
throw e;
|
||||
}
|
||||
}, false);
|
||||
|
||||
me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to be executed when list of translators becomes available. Sends response with
|
||||
* item types, translator IDs, labels, and icons for available translators.
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Zotero.Translator[]} translators
|
||||
*/
|
||||
"_translatorsAvailable":function(obj, translators) {
|
||||
var jsons = [];
|
||||
for each(var translator in translators) {
|
||||
if(translator.itemType == "multiple") {
|
||||
var icon = "treesource-collection.png"
|
||||
} else {
|
||||
var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
|
||||
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||
}
|
||||
var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
|
||||
"label":translator.label, "priority":translator.priority}
|
||||
jsons.push(json);
|
||||
}
|
||||
this._sendResponse(200, "application/json", JSON.stringify(jsons));
|
||||
|
||||
this._translate.cookieManager.destroy();
|
||||
Zotero.Browser.deleteHiddenBrowser(this._browser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs translation of a given page
|
||||
*
|
||||
* Accepts:
|
||||
* uri - The URI of the page to be saved
|
||||
* html - document.innerHTML or equivalent
|
||||
* cookie - document.cookie or equivalent
|
||||
*
|
||||
* Returns:
|
||||
* If a single item, sends response code 201 with no body.
|
||||
* If multiple items, sends response code 300 with the following content:
|
||||
* items - list of items in the format typically passed to the selectItems handler
|
||||
* instanceID - an ID that must be maintained for the subsequent Zotero.Connector.Select call
|
||||
* uri - the URI of the page for which multiple items are available
|
||||
*/
|
||||
Zotero.Server.Connector.SavePage = function() {};
|
||||
Zotero.Server.Endpoints["/connector/savePage"] = Zotero.Server.Connector.SavePage;
|
||||
Zotero.Server.Connector.SavePage.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
|
||||
* to the database
|
||||
* @param {Object} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
this._sendResponse = sendResponseCallback;
|
||||
Zotero.Server.Connector.Detect.prototype.init.apply(this, [data, sendResponseCallback])
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to be executed when items must be selected
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Object} itemList ID=>text pairs representing available items
|
||||
*/
|
||||
"_selectItems":function(translate, itemList, callback) {
|
||||
var instanceID = Zotero.randomString();
|
||||
Zotero.Server.Connector._waitingForSelection[instanceID] = this;
|
||||
|
||||
// Fix for translators that don't create item lists as objects
|
||||
if(itemList.push && typeof itemList.push === "function") {
|
||||
var newItemList = {};
|
||||
for(var item in itemList) {
|
||||
newItemList[item] = itemList[item];
|
||||
}
|
||||
itemList = newItemList;
|
||||
}
|
||||
|
||||
// Send "Multiple Choices" HTTP response
|
||||
this._sendResponse(300, "application/json", JSON.stringify({"selectItems":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
|
||||
|
||||
// We need this to make sure that we won't stop Firefox from quitting, even if the user
|
||||
// didn't close the selectItems window
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
var me = this;
|
||||
var quitObserver = {observe:function() { me.selectedItems = false; }};
|
||||
observerService.addObserver(quitObserver, "quit-application", false);
|
||||
|
||||
this.selectedItems = null;
|
||||
var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
|
||||
// permanently slow Firefox with this loop
|
||||
while(this.selectedItems === null && Date.now() < endTime) {
|
||||
Zotero.mainThread.processNextEvent(true);
|
||||
}
|
||||
|
||||
observerService.removeObserver(quitObserver, "quit-application");
|
||||
callback(this.selectedItems);
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to be executed when list of translators becomes available. Opens progress window,
|
||||
* selects specified translator, and initiates translation.
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {Zotero.Translator[]} translators
|
||||
*/
|
||||
"_translatorsAvailable":function(translate, translators) {
|
||||
// make sure translatorsAvailable succeded
|
||||
if(!translators.length) {
|
||||
Zotero.Browser.deleteHiddenBrowser(this._browser);
|
||||
this._sendResponse(500);
|
||||
return;
|
||||
}
|
||||
|
||||
// figure out where to save
|
||||
var libraryID = null;
|
||||
var collectionID = null;
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
try {
|
||||
var libraryID = zp.getSelectedLibraryID();
|
||||
var collection = zp.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
|
||||
// set handlers for translation
|
||||
var me = this;
|
||||
var jsonItems = [];
|
||||
translate.setHandler("select", function(obj, item, callback) { return me._selectItems(obj, item, callback) });
|
||||
translate.setHandler("itemDone", function(obj, item, jsonItem) {
|
||||
if(collection) {
|
||||
collection.addItem(item.id);
|
||||
}
|
||||
jsonItems.push(jsonItem);
|
||||
});
|
||||
translate.setHandler("done", function(obj, item) {
|
||||
me._translate.cookieManager.destroy();
|
||||
Zotero.Browser.deleteHiddenBrowser(me._browser);
|
||||
me._sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
|
||||
});
|
||||
|
||||
// set translator and translate
|
||||
translate.setTranslator(this._parsedPostData.translatorID);
|
||||
translate.translate(libraryID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs translation of a given page, or, alternatively, saves items directly
|
||||
*
|
||||
* Accepts:
|
||||
* items - an array of JSON format items
|
||||
* Returns:
|
||||
* 201 response code with empty body
|
||||
*/
|
||||
Zotero.Server.Connector.SaveItem = function() {};
|
||||
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
|
||||
Zotero.Server.Connector.SaveItem.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
|
||||
* to the database
|
||||
* @param {Object} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
// figure out where to save
|
||||
var libraryID = null;
|
||||
var collectionID = null;
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
try {
|
||||
var libraryID = zp.getSelectedLibraryID();
|
||||
var collection = zp.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
|
||||
// save items
|
||||
var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1);
|
||||
for each(var item in data.items) {
|
||||
var savedItem = itemSaver.saveItem(item);
|
||||
if(collection) collection.addItem(savedItem.id);
|
||||
}
|
||||
sendResponseCallback(201);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle item selection
|
||||
*
|
||||
* Accepts:
|
||||
* selectedItems - a list of items to translate in ID => text format as returned by a selectItems handler
|
||||
* instanceID - as returned by savePage call
|
||||
* Returns:
|
||||
* 201 response code with empty body
|
||||
*/
|
||||
Zotero.Server.Connector.SelectItems = function() {};
|
||||
Zotero.Server.Endpoints["/connector/selectItems"] = Zotero.Server.Connector.SelectItems;
|
||||
Zotero.Server.Connector.SelectItems.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Finishes up translation when item selection is complete
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
var saveInstance = Zotero.Server.Connector._waitingForSelection[data.instanceID];
|
||||
saveInstance._sendResponse = sendResponseCallback;
|
||||
|
||||
saveInstance.selectedItems = false;
|
||||
for(var i in data.selectedItems) {
|
||||
saveInstance.selectedItems = data.selectedItems;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get code for a translator
|
||||
*
|
||||
* Accepts:
|
||||
* translatorID
|
||||
* Returns:
|
||||
* code - translator code
|
||||
*/
|
||||
Zotero.Server.Connector.GetTranslatorCode = function() {};
|
||||
Zotero.Server.Endpoints["/connector/getTranslatorCode"] = Zotero.Server.Connector.GetTranslatorCode;
|
||||
Zotero.Server.Connector.GetTranslatorCode.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Finishes up translation when item selection is complete
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(postData, sendResponseCallback) {
|
||||
var translator = Zotero.Translators.get(postData.translatorID);
|
||||
sendResponseCallback(200, "application/javascript", translator.code);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Manages the translator sandbox
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {String|window} sandboxLocation
|
||||
*/
|
||||
Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
|
||||
this.sandbox = {};
|
||||
this._translate = translate;
|
||||
}
|
||||
|
||||
Zotero.Translate.SandboxManager.prototype = {
|
||||
/**
|
||||
* Evaluates code in the sandbox
|
||||
*/
|
||||
"eval":function(code) {
|
||||
// eval in sandbox scope
|
||||
(new Function("with(this) { " + code + " }")).call(this.sandbox);
|
||||
},
|
||||
|
||||
/**
|
||||
* Imports an object into the sandbox
|
||||
*
|
||||
* @param {Object} object Object to be imported (under Zotero)
|
||||
* @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
|
||||
* as the first argument to the function.
|
||||
*/
|
||||
"importObject":function(object, passAsFirstArgument) {
|
||||
var translate = this._translate;
|
||||
|
||||
for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) {
|
||||
var fn = (function(object, key) { return object[key] })();
|
||||
|
||||
// magic "this"-preserving wrapping closure
|
||||
this.sandbox[key] = function() {
|
||||
var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
|
||||
for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
|
||||
fn.apply(object, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
271
chrome/content/zotero/xpcom/translation/tlds.js
Normal file
271
chrome/content/zotero/xpcom/translation/tlds.js
Normal file
|
@ -0,0 +1,271 @@
|
|||
const TLDS = {
|
||||
"ac":true,
|
||||
"ad":true,
|
||||
"ae":true,
|
||||
"aero":true,
|
||||
"af":true,
|
||||
"ag":true,
|
||||
"ai":true,
|
||||
"al":true,
|
||||
"am":true,
|
||||
"an":true,
|
||||
"ao":true,
|
||||
"aq":true,
|
||||
"ar":true,
|
||||
"arpa":true,
|
||||
"as":true,
|
||||
"asia":true,
|
||||
"at":true,
|
||||
"au":true,
|
||||
"aw":true,
|
||||
"ax":true,
|
||||
"az":true,
|
||||
"ba":true,
|
||||
"bb":true,
|
||||
"bd":true,
|
||||
"be":true,
|
||||
"bf":true,
|
||||
"bg":true,
|
||||
"bh":true,
|
||||
"bi":true,
|
||||
"biz":true,
|
||||
"bj":true,
|
||||
"bm":true,
|
||||
"bn":true,
|
||||
"bo":true,
|
||||
"br":true,
|
||||
"bs":true,
|
||||
"bt":true,
|
||||
"bv":true,
|
||||
"bw":true,
|
||||
"by":true,
|
||||
"bz":true,
|
||||
"ca":true,
|
||||
"cat":true,
|
||||
"cc":true,
|
||||
"cd":true,
|
||||
"cf":true,
|
||||
"cg":true,
|
||||
"ch":true,
|
||||
"ci":true,
|
||||
"ck":true,
|
||||
"cl":true,
|
||||
"cm":true,
|
||||
"cn":true,
|
||||
"co":true,
|
||||
"com":true,
|
||||
"coop":true,
|
||||
"cr":true,
|
||||
"cu":true,
|
||||
"cv":true,
|
||||
"cx":true,
|
||||
"cy":true,
|
||||
"cz":true,
|
||||
"de":true,
|
||||
"dj":true,
|
||||
"dk":true,
|
||||
"dm":true,
|
||||
"do":true,
|
||||
"dz":true,
|
||||
"ec":true,
|
||||
"edu":true,
|
||||
"ee":true,
|
||||
"eg":true,
|
||||
"er":true,
|
||||
"es":true,
|
||||
"et":true,
|
||||
"eu":true,
|
||||
"fi":true,
|
||||
"fj":true,
|
||||
"fk":true,
|
||||
"fm":true,
|
||||
"fo":true,
|
||||
"fr":true,
|
||||
"ga":true,
|
||||
"gb":true,
|
||||
"gd":true,
|
||||
"ge":true,
|
||||
"gf":true,
|
||||
"gg":true,
|
||||
"gh":true,
|
||||
"gi":true,
|
||||
"gl":true,
|
||||
"gm":true,
|
||||
"gn":true,
|
||||
"gov":true,
|
||||
"gp":true,
|
||||
"gq":true,
|
||||
"gr":true,
|
||||
"gs":true,
|
||||
"gt":true,
|
||||
"gu":true,
|
||||
"gw":true,
|
||||
"gy":true,
|
||||
"hk":true,
|
||||
"hm":true,
|
||||
"hn":true,
|
||||
"hr":true,
|
||||
"ht":true,
|
||||
"hu":true,
|
||||
"id":true,
|
||||
"ie":true,
|
||||
"il":true,
|
||||
"im":true,
|
||||
"in":true,
|
||||
"info":true,
|
||||
"int":true,
|
||||
"io":true,
|
||||
"iq":true,
|
||||
"ir":true,
|
||||
"is":true,
|
||||
"it":true,
|
||||
"je":true,
|
||||
"jm":true,
|
||||
"jo":true,
|
||||
"jobs":true,
|
||||
"jp":true,
|
||||
"ke":true,
|
||||
"kg":true,
|
||||
"kh":true,
|
||||
"ki":true,
|
||||
"km":true,
|
||||
"kn":true,
|
||||
"kp":true,
|
||||
"kr":true,
|
||||
"kw":true,
|
||||
"ky":true,
|
||||
"kz":true,
|
||||
"la":true,
|
||||
"lb":true,
|
||||
"lc":true,
|
||||
"li":true,
|
||||
"lk":true,
|
||||
"lr":true,
|
||||
"ls":true,
|
||||
"lt":true,
|
||||
"lu":true,
|
||||
"lv":true,
|
||||
"ly":true,
|
||||
"ma":true,
|
||||
"mc":true,
|
||||
"md":true,
|
||||
"me":true,
|
||||
"mg":true,
|
||||
"mh":true,
|
||||
"mil":true,
|
||||
"mk":true,
|
||||
"ml":true,
|
||||
"mm":true,
|
||||
"mn":true,
|
||||
"mo":true,
|
||||
"mobi":true,
|
||||
"mp":true,
|
||||
"mq":true,
|
||||
"mr":true,
|
||||
"ms":true,
|
||||
"mt":true,
|
||||
"mu":true,
|
||||
"museum":true,
|
||||
"mv":true,
|
||||
"mw":true,
|
||||
"mx":true,
|
||||
"my":true,
|
||||
"mz":true,
|
||||
"na":true,
|
||||
"name":true,
|
||||
"nc":true,
|
||||
"ne":true,
|
||||
"net":true,
|
||||
"nf":true,
|
||||
"ng":true,
|
||||
"ni":true,
|
||||
"nl":true,
|
||||
"no":true,
|
||||
"np":true,
|
||||
"nr":true,
|
||||
"nu":true,
|
||||
"nz":true,
|
||||
"om":true,
|
||||
"org":true,
|
||||
"pa":true,
|
||||
"pe":true,
|
||||
"pf":true,
|
||||
"pg":true,
|
||||
"ph":true,
|
||||
"pk":true,
|
||||
"pl":true,
|
||||
"pm":true,
|
||||
"pn":true,
|
||||
"pr":true,
|
||||
"pro":true,
|
||||
"ps":true,
|
||||
"pt":true,
|
||||
"pw":true,
|
||||
"py":true,
|
||||
"qa":true,
|
||||
"re":true,
|
||||
"ro":true,
|
||||
"rs":true,
|
||||
"ru":true,
|
||||
"rw":true,
|
||||
"sa":true,
|
||||
"sb":true,
|
||||
"sc":true,
|
||||
"sd":true,
|
||||
"se":true,
|
||||
"sg":true,
|
||||
"sh":true,
|
||||
"si":true,
|
||||
"sj":true,
|
||||
"sk":true,
|
||||
"sl":true,
|
||||
"sm":true,
|
||||
"sn":true,
|
||||
"so":true,
|
||||
"sr":true,
|
||||
"st":true,
|
||||
"su":true,
|
||||
"sv":true,
|
||||
"sy":true,
|
||||
"sz":true,
|
||||
"tc":true,
|
||||
"td":true,
|
||||
"tel":true,
|
||||
"tf":true,
|
||||
"tg":true,
|
||||
"th":true,
|
||||
"tj":true,
|
||||
"tk":true,
|
||||
"tl":true,
|
||||
"tm":true,
|
||||
"tn":true,
|
||||
"to":true,
|
||||
"tp":true,
|
||||
"tr":true,
|
||||
"travel":true,
|
||||
"tt":true,
|
||||
"tv":true,
|
||||
"tw":true,
|
||||
"tz":true,
|
||||
"ua":true,
|
||||
"ug":true,
|
||||
"uk":true,
|
||||
"us":true,
|
||||
"uy":true,
|
||||
"uz":true,
|
||||
"va":true,
|
||||
"vc":true,
|
||||
"ve":true,
|
||||
"vg":true,
|
||||
"vi":true,
|
||||
"vn":true,
|
||||
"vu":true,
|
||||
"wf":true,
|
||||
"ws":true,
|
||||
"xxx":true,
|
||||
"ye":true,
|
||||
"yt":true,
|
||||
"za":true,
|
||||
"zm":true,
|
||||
"zw":true
|
||||
};
|
|
@ -25,14 +25,15 @@
|
|||
|
||||
/**
|
||||
* @class
|
||||
* Deprecated class for creating new Zotero.Translate instances
|
||||
* Deprecated class for creating new Zotero.Translate instances<br/>
|
||||
* <br/>
|
||||
* New code should use Zotero.Translate.Web, Zotero.Translate.Import, Zotero.Translate.Export, or
|
||||
* Zotero.Translate.Search
|
||||
*/
|
||||
Zotero.Translate = function(type) {
|
||||
Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to");
|
||||
// hack
|
||||
var translate = Zotero.Translate.new(type);
|
||||
var translate = Zotero.Translate.newInstance(type);
|
||||
for(var i in translate) {
|
||||
this[i] = translate[i];
|
||||
}
|
||||
|
@ -43,10 +44,14 @@ Zotero.Translate = function(type) {
|
|||
/**
|
||||
* Create a new translator by a string type
|
||||
*/
|
||||
Zotero.Translate.new = function(type) {
|
||||
Zotero.Translate.newInstance = function(type) {
|
||||
return new Zotero.Translate[type[0].toUpperCase()+type.substr(1).toLowerCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Namespace for Zotero sandboxes
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.Translate.Sandbox = {
|
||||
/**
|
||||
* Combines a sandbox with the base sandbox
|
||||
|
@ -67,10 +72,11 @@ Zotero.Translate.Sandbox = {
|
|||
|
||||
/**
|
||||
* Base sandbox. These methods are available to all translators.
|
||||
* @namespace
|
||||
*/
|
||||
"Base": {
|
||||
/**
|
||||
* Called as Zotero.Item#complete() from translators to save items to the database.
|
||||
* Called as {@link Zotero.Item#complete} from translators to save items to the database.
|
||||
* @param {Zotero.Translate} translate
|
||||
* @param {SandboxItem} An item created using the Zotero.Item class from the sandbox
|
||||
*/
|
||||
|
@ -86,7 +92,7 @@ Zotero.Translate.Sandbox = {
|
|||
// just return the item array
|
||||
if(translate._libraryID === false || translate._parentTranslator) {
|
||||
translate.newItems.push(item);
|
||||
translate._runHandler("itemDone", item);
|
||||
translate._runHandler("itemDone", item, item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -99,7 +105,8 @@ Zotero.Translate.Sandbox = {
|
|||
Zotero.wait();
|
||||
}
|
||||
|
||||
translate._runHandler("itemDone", newItem);
|
||||
// pass both the saved item and the original JS array item
|
||||
translate._runHandler("itemDone", newItem, item);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -142,16 +149,19 @@ Zotero.Translate.Sandbox = {
|
|||
}
|
||||
|
||||
Zotero.debug("Translate: creating translate instance of type "+type+" in sandbox");
|
||||
var translation = Zotero.Translate.new(type);
|
||||
var translation = Zotero.Translate.newInstance(type);
|
||||
translation._parentTranslator = translate;
|
||||
|
||||
if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) {
|
||||
throw("Translate: only export translators may call other export translators");
|
||||
}
|
||||
|
||||
// for security reasons, safeTranslator wraps the translator object.
|
||||
// note that setLocation() is not allowed
|
||||
var safeTranslator = new Object();
|
||||
/**
|
||||
* @class Wrapper for {@link Zotero.Translate} for safely calling another translator
|
||||
* from inside an existing translator
|
||||
* @inner
|
||||
*/
|
||||
var safeTranslator = {};
|
||||
safeTranslator.__exposedProps__ = {
|
||||
"setSearch":"r",
|
||||
"setDocument":"r",
|
||||
|
@ -185,43 +195,61 @@ Zotero.Translate.Sandbox = {
|
|||
);
|
||||
};
|
||||
safeTranslator.setString = function(arg) { translation.setString(arg) };
|
||||
safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) };
|
||||
safeTranslator.setTranslator = function(arg) {
|
||||
var success = translation.setTranslator(arg);
|
||||
if(!success) {
|
||||
throw "Translator "+translate.translator[0].translatorID+" attempted to call invalid translatorID "+arg;
|
||||
}
|
||||
};
|
||||
safeTranslator.getTranslators = function() { return translation.getTranslators() };
|
||||
safeTranslator.translate = function() {
|
||||
setDefaultHandlers(translate, translation);
|
||||
return translation.translate(false);
|
||||
};
|
||||
// TODO
|
||||
safeTranslator.getTranslatorObject = function(callback) {
|
||||
translation._loadTranslator(translation.translator[0]);
|
||||
|
||||
if(Zotero.isFx) {
|
||||
// do same origin check
|
||||
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Components.interfaces.nsIScriptSecurityManager);
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var haveTranslatorFunction = function(translator) {
|
||||
translation.translator[0] = translator;
|
||||
if(!Zotero._loadTranslator(translator)) throw "Translator could not be loaded";
|
||||
|
||||
var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
|
||||
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
|
||||
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
|
||||
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
|
||||
Zotero.debug(outerSandboxURI.spec);
|
||||
Zotero.debug(innerSandboxURI.spec);
|
||||
|
||||
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<arguments.length; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
|
||||
for(var i in this._handlers[type]) {
|
||||
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
|
||||
try {
|
||||
if(this._parentTranslator) {
|
||||
returnValue = this._handlers[type][i](null, argument);
|
||||
} else {
|
||||
returnValue = this._handlers[type][i](this, argument);
|
||||
}
|
||||
returnValue = this._handlers[type][i].apply(null, args);
|
||||
} catch(e) {
|
||||
if(this._parentTranslator) {
|
||||
// throw handler errors if they occur when a translator is
|
||||
|
@ -635,16 +706,73 @@ Zotero.Translate.Base.prototype = {
|
|||
if(this._currentState == "detect") throw "Translate: getTranslators: detection is already running";
|
||||
this._currentState = "detect";
|
||||
this._getAllTranslators = getAllTranslators;
|
||||
this._potentialTranslators = this._getPotentialTranslators();
|
||||
this._foundTranslators = [];
|
||||
|
||||
Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3);
|
||||
|
||||
this._detect();
|
||||
this._getTranslatorsGetPotentialTranslators();
|
||||
|
||||
// if detection returns immediately, return found translators
|
||||
if(!this._currentState) return this._foundTranslators;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all potential translators
|
||||
* @return {Zotero.Translator[]}
|
||||
*/
|
||||
"_getTranslatorsGetPotentialTranslators":function() {
|
||||
var me = this;
|
||||
Zotero.Translators.getAllForType(this.type,
|
||||
function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on completion of {@link #_getTranslatorsGetPotentialTranslators} call
|
||||
*/
|
||||
"_getTranslatorsTranslatorsReceived":function(allPotentialTranslators, properToProxyFunctions) {
|
||||
this._potentialTranslators = [];
|
||||
this._foundTranslators = [];
|
||||
|
||||
// this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is
|
||||
// specific for each translator, but we want to avoid making a copy of a translator whenever
|
||||
// possible.
|
||||
this._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null;
|
||||
this._waitingForRPC = false;
|
||||
|
||||
for(var i in allPotentialTranslators) {
|
||||
var translator = allPotentialTranslators[i];
|
||||
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
this._potentialTranslators.push(translator);
|
||||
} else {
|
||||
this._waitingForRPC = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO maybe this should only be in the web translator
|
||||
if(this._waitingForRPC) {
|
||||
var me = this;
|
||||
Zotero.Connector.callMethod("detect", {"uri":this.location.toString(),
|
||||
"cookie":this.document.cookie,
|
||||
"html":this.document.documentElement.innerHTML},
|
||||
function(returnValue) { me._getTranslatorsRPCComplete(returnValue) });
|
||||
}
|
||||
|
||||
this._detect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on completion of detect RPC for
|
||||
* {@link Zotero.Translate.Base#_getTranslatorsTranslatorsReceived}
|
||||
*/
|
||||
"_getTranslatorsRPCComplete":function(rpcTranslators) {
|
||||
this._waitingForRPC = false;
|
||||
|
||||
// if there are translators, add them to the list of found translators
|
||||
if(rpcTranslators) {
|
||||
this._foundTranslators = this._foundTranslators.concat(rpcTranslators);
|
||||
}
|
||||
|
||||
// call _detectTranslatorsCollected to return detected translators
|
||||
if(this._currentState === null) {
|
||||
this._detectTranslatorsCollected();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins the actual translation. At present, this returns immediately for import/export
|
||||
|
@ -656,14 +784,34 @@ Zotero.Translate.Base.prototype = {
|
|||
* if FALSE, don't save items
|
||||
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
|
||||
*/
|
||||
"translate":function(libraryID, saveAttachments) {
|
||||
// initialize properties specific to each translation
|
||||
"translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
|
||||
this._currentState = "translate";
|
||||
|
||||
if(!this.translator || !this.translator.length) {
|
||||
throw("Translate: Failed: no translator specified");
|
||||
}
|
||||
|
||||
this._libraryID = libraryID;
|
||||
this._saveAttachments = saveAttachments === undefined || saveAttachments;
|
||||
|
||||
if(typeof this.translator[0] === "object") {
|
||||
// already have a translator object, so use it
|
||||
this._translateHaveTranslator();
|
||||
} else {
|
||||
// need to get translator first
|
||||
var me = this;
|
||||
Zotero.Translators.get(this.translator[0],
|
||||
function(translator) {
|
||||
me.translator[0] = translator;
|
||||
me._translateHaveTranslator();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when translator has been retrieved
|
||||
*/
|
||||
"_translateHaveTranslator":function() {
|
||||
// load translators
|
||||
if(!this._loadTranslator(this.translator[0])) return;
|
||||
|
||||
|
@ -671,15 +819,13 @@ Zotero.Translate.Base.prototype = {
|
|||
if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions;
|
||||
|
||||
// prepare translation
|
||||
this._libraryID = libraryID;
|
||||
this._saveAttachments = typeof saveAttachments === "undefined" ? true : saveAttachments;
|
||||
this._prepareTranslation();
|
||||
|
||||
Zotero.debug("Translate: Beginning translation with "+this.translator[0].label);
|
||||
|
||||
// translate
|
||||
try {
|
||||
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(this.null, this._getParameters());
|
||||
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(null, this._getParameters());
|
||||
} catch(e) {
|
||||
if(this._parentTranslator) {
|
||||
throw(e);
|
||||
|
@ -715,9 +861,10 @@ Zotero.Translate.Base.prototype = {
|
|||
if(oldState === "detect") {
|
||||
if(this._potentialTranslators.length) {
|
||||
var lastTranslator = this._potentialTranslators.shift();
|
||||
var lastProperToProxyFunction = this._properToProxyFunctions ? this._properToProxyFunctions.shift() : null;
|
||||
|
||||
if(returnValue) {
|
||||
var dupeTranslator = {"itemType":returnValue};
|
||||
var dupeTranslator = {"itemType":returnValue, "properToProxy":lastProperToProxyFunction};
|
||||
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
|
||||
this._foundTranslators.push(dupeTranslator);
|
||||
} else if(error) {
|
||||
|
@ -730,7 +877,7 @@ Zotero.Translate.Base.prototype = {
|
|||
this._detect();
|
||||
} else {
|
||||
this._currentState = null;
|
||||
this._runHandler("translators", this._foundTranslators ? this._foundTranslators : false);
|
||||
if(!this._waitingForRPC) this._detectTranslatorsCollected();
|
||||
}
|
||||
} else {
|
||||
this._currentState = null;
|
||||
|
@ -762,6 +909,12 @@ Zotero.Translate.Base.prototype = {
|
|||
* Runs detect code for a translator
|
||||
*/
|
||||
"_detect":function() {
|
||||
// there won't be any translators if we need an RPC call
|
||||
if(!this._potentialTranslators.length) {
|
||||
this.complete(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this._loadTranslator(this._potentialTranslators[0])) {
|
||||
this.complete(false, "Error loading translator into sandbox");
|
||||
return;
|
||||
|
@ -778,6 +931,15 @@ Zotero.Translate.Base.prototype = {
|
|||
if(!this._waitForCompletion) this.complete(returnValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when all translators have been collected for detection
|
||||
*/
|
||||
"_detectTranslatorsCollected":function() {
|
||||
Zotero.debug("Translate: All translator detect calls and RPC calls complete");
|
||||
this._foundTranslators.sort(function(a, b) { return a.priority-b.priority });
|
||||
this._runHandler("translators", this._foundTranslators);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the translator into its sandbox
|
||||
* @param {Zotero.Translator} translator
|
||||
|
@ -795,7 +957,6 @@ Zotero.Translate.Base.prototype = {
|
|||
|
||||
try {
|
||||
this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox);
|
||||
return true;
|
||||
} catch(e) {
|
||||
if(translator.logError) {
|
||||
translator.logError(e.toString());
|
||||
|
@ -815,13 +976,14 @@ Zotero.Translate.Base.prototype = {
|
|||
*/
|
||||
"_generateSandbox":function() {
|
||||
Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4);
|
||||
this._sandboxManager = new Zotero.Translate.SandboxManager(this, this._sandboxLocation);
|
||||
this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation);
|
||||
const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']";
|
||||
var src = "var Zotero = {};"+
|
||||
"Zotero.Item = function (itemType) {"+
|
||||
"const createArrays = "+createArrays+";"+
|
||||
"this.itemType = itemType;"+
|
||||
"for each(var array in "+createArrays+") {"+
|
||||
"this[array] = [];"+
|
||||
"for(var i in createArrays) {"+
|
||||
"this[createArrays[i]] = [];"+
|
||||
"}"+
|
||||
"};"+
|
||||
"Zotero.Collection = function () {};"+
|
||||
|
@ -927,17 +1089,11 @@ Zotero.Translate.Base.prototype = {
|
|||
* No-op for preparing translation
|
||||
*/
|
||||
"_prepareTranslation":function() {},
|
||||
|
||||
/**
|
||||
* Get all potential translators
|
||||
* @return {Zotero.Translator[]}
|
||||
*/
|
||||
"_getPotentialTranslators":function() {
|
||||
return Zotero.Translators.getAllForType(this.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Web translation
|
||||
*
|
||||
* @property {Document} document The document object to be used for web scraping (set with setDocument)
|
||||
* @property {Zotero.Connector.CookieManager} cookieManager A CookieManager to manage cookies for
|
||||
* this Translate instance.
|
||||
|
@ -975,30 +1131,21 @@ Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) {
|
|||
* @param {String} location The URL of the page to translate
|
||||
*/
|
||||
Zotero.Translate.Web.prototype.setLocation = function(location) {
|
||||
// account for proxies
|
||||
this.location = Zotero.Proxies.proxyToProper(location);
|
||||
if(this.location != location) {
|
||||
// figure out if this URL is being proxies
|
||||
this.locationIsProxied = true;
|
||||
}
|
||||
this.location = location;
|
||||
this.path = this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all potential translators
|
||||
* Get potential web translators
|
||||
*/
|
||||
Zotero.Translate.Web.prototype._getPotentialTranslators = function() {
|
||||
var allTranslators = Zotero.Translators.getAllForType("web");
|
||||
var potentialTranslators = [];
|
||||
|
||||
Zotero.debug("Translate: Running regular expressions");
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
if(!allTranslators[i].webRegexp || (this.location.length < 8192 && allTranslators[i].webRegexp.test(this.location))) {
|
||||
potentialTranslators.push(allTranslators[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return potentialTranslators;
|
||||
Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() {
|
||||
var me = this;
|
||||
Zotero.Translators.getWebTranslatorsForLocation(this.location,
|
||||
function(data) {
|
||||
// data[0] = list of translators
|
||||
// data[1] = list of functions to convert proper URIs to proxied URIs
|
||||
me._getTranslatorsTranslatorsReceived(data[0], data[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1023,14 +1170,67 @@ Zotero.Translate.Web.prototype._prepareTranslation = function() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Overload detect to test regexp first
|
||||
* Overload translate to set selectedItems
|
||||
*/
|
||||
Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments, selectedItems) {
|
||||
this._selectedItems = selectedItems;
|
||||
Zotero.Translate.Base.prototype.translate.apply(this, libraryID, saveAttachments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload _translateHaveTranslator to send an RPC call if necessary
|
||||
*/
|
||||
Zotero.Translate.Web.prototype._translateHaveTranslator = function() {
|
||||
if(this.translator[0].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
// begin process to run translator in browser
|
||||
Zotero.Translate.Base.prototype._translateHaveTranslator.apply(this);
|
||||
} else {
|
||||
// otherwise, ferry translator load to RPC
|
||||
var me = this;
|
||||
Zotero.Connector.callMethod("savePage", {
|
||||
"uri":this.location.toString(),
|
||||
"translatorID":(typeof this.translator[0] === "object"
|
||||
? this.translator[0].translatorID : this.translator[0]),
|
||||
"cookie":this.document.cookie,
|
||||
"html":this.document.documentElement.innerHTML
|
||||
}, function(obj) { me._translateRPCComplete(obj) });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an RPC call for remote translation completes
|
||||
*/
|
||||
Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode) {
|
||||
if(!obj) this.complete(false, failureCode);
|
||||
|
||||
if(obj.selectItems) {
|
||||
// if we have to select items, call the selectItems handler and do it
|
||||
var me = this;
|
||||
var items = this._runHandler("select", obj.selectItems,
|
||||
function(selectedItems) {
|
||||
Zotero.Connector.callMethod("selectItems",
|
||||
{"instanceID":obj.instanceID, "selectedItems":selectedItems},
|
||||
function(obj) { me._translateRPCComplete(obj) })
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// if we don't have to select items, continue
|
||||
for(var i in obj.items) {
|
||||
this._runHandler("itemDone", null, obj.items[i]);
|
||||
}
|
||||
this.complete(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload complete to report translation failure
|
||||
*/
|
||||
Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
|
||||
// call super
|
||||
var oldState = this._currentState;
|
||||
var errorString = Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]);
|
||||
|
||||
// Report translaton failure if we failed
|
||||
// Report translation failure if we failed
|
||||
if(oldState == "translate" && errorString && this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) {
|
||||
// Don't report failure if in private browsing mode
|
||||
if(Zotero.isFx && !Zotero.isStandalone) {
|
||||
|
@ -1049,6 +1249,9 @@ Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Import translation
|
||||
*/
|
||||
Zotero.Translate.Import = function() {
|
||||
this.init();
|
||||
}
|
||||
|
@ -1081,35 +1284,26 @@ Zotero.Translate.Import.prototype.complete = function(returnValue, error) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all potential translators, ordering translators with the right file extension first
|
||||
* Get all potential import translators, ordering translators with the right file extension first
|
||||
*/
|
||||
Zotero.Translate.Import.prototype._getPotentialTranslators = function() {
|
||||
var allTranslators = Zotero.Translators.getAllForType("import");
|
||||
var tier1Translators = [];
|
||||
var tier2Translators = [];
|
||||
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
if(allTranslators[i].importRegexp.test(this.location)) {
|
||||
tier1Translators.push(allTranslators[i]);
|
||||
} else {
|
||||
tier2Translators.push(allTranslators[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return tier1Translators.concat(tier2Translators);
|
||||
Zotero.Translate.Import.prototype._getTranslatorsGetPotentialTranslators = function() {
|
||||
var me = this;
|
||||
Zotero.Translators.getImportTranslatorsForLocation(this.location,
|
||||
function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload Zotero.Translate.Base#_detect to return all translators immediately only if no string
|
||||
* or location is set
|
||||
* Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately only
|
||||
* if no string or location is set
|
||||
*/
|
||||
Zotero.Translate.Import.prototype._detect = function() {
|
||||
Zotero.Translate.Import.prototype.getTranslators = function() {
|
||||
if(!this._string && !this.location) {
|
||||
this._foundTranslators = this._potentialTranslators;
|
||||
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
|
||||
this._potentialTranslators = [];
|
||||
this.complete(true);
|
||||
return this._foundTranslators;
|
||||
} else {
|
||||
Zotero.Translate.Base.prototype._detect.call(this);
|
||||
Zotero.Translate.Base.prototype.getTranslators.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1156,7 +1350,7 @@ Zotero.Translate.Import.prototype._loadTranslator = function(translator) {
|
|||
this._sandboxManager.importObject(this._io);
|
||||
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare translation
|
||||
|
@ -1182,6 +1376,9 @@ function() {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* @class Export translation
|
||||
*/
|
||||
Zotero.Translate.Export = function() {
|
||||
this.init();
|
||||
}
|
||||
|
@ -1235,12 +1432,13 @@ Zotero.Translate.Export.prototype.setDisplayOptions = function(displayOptions) {
|
|||
Zotero.Translate.Export.prototype.complete = Zotero.Translate.Import.prototype.complete;
|
||||
|
||||
/**
|
||||
* Overload Zotero.Translate.Base#_detect to return all translators immediately
|
||||
* Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately
|
||||
*/
|
||||
Zotero.Translate.Export.prototype._detect = function() {
|
||||
this._foundTranslators = this._potentialTranslators;
|
||||
Zotero.Translate.Export.prototype.getTranslators = function() {
|
||||
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
|
||||
this._potentialTranslators = [];
|
||||
this.complete(true);
|
||||
return this._foundTranslators;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1295,6 +1493,7 @@ function() {
|
|||
});
|
||||
|
||||
/**
|
||||
* @class Search translation
|
||||
* @property {Array[]} search Item (in {@link Zotero.Item#serialize} format) to extrapolate data
|
||||
* (set with setSearch)
|
||||
*/
|
||||
|
@ -1307,7 +1506,7 @@ Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
|
|||
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
|
||||
|
||||
/**
|
||||
* @borrows Zotero.Translate.Web#setCookieManager as Zotero.Translate.Search#setCookieManager
|
||||
* @borrows Zotero.Translate.Web#setCookieManager
|
||||
*/
|
||||
Zotero.Translate.Search.prototype.setCookieManager = Zotero.Translate.Web.prototype.setCookieManager;
|
||||
|
||||
|
@ -1339,11 +1538,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) {
|
|||
// accept a list of objects
|
||||
this.translator = [];
|
||||
for(var i in translator) {
|
||||
if(typeof(translator[i]) == "object") {
|
||||
this.translator.push(translator[i]);
|
||||
} else {
|
||||
this.translator.push(Zotero.Translators.get(translator[i]));
|
||||
}
|
||||
this.translator.push(translator[i]);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
|
@ -1436,6 +1631,9 @@ Zotero.Translate.IO = {
|
|||
|
||||
/******* String support *******/
|
||||
|
||||
/**
|
||||
* @class Translate backend for translating from a string
|
||||
*/
|
||||
Zotero.Translate.IO.String = function(string, uri, mode) {
|
||||
if(string && typeof string === "string") {
|
||||
this._string = string;
|
||||
|
|
|
@ -38,10 +38,9 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
|||
* @param {Zotero.Translate} translate
|
||||
* @param {String|window} sandboxLocation
|
||||
*/
|
||||
Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
|
||||
Zotero.Translate.SandboxManager = function(sandboxLocation) {
|
||||
this.sandbox = new Components.utils.Sandbox(sandboxLocation);
|
||||
this.sandbox.Zotero = {};
|
||||
this._translate = translate;
|
||||
|
||||
// import functions missing from global scope into Fx sandbox
|
||||
this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
|
||||
|
@ -111,6 +110,7 @@ Zotero.Translate.SandboxManager.prototype = {
|
|||
|
||||
return object[localKey].apply(object, args);
|
||||
};
|
||||
attachTo[localKey].name = localKey;
|
||||
|
||||
// attach members
|
||||
if(!(object instanceof Components.interfaces.nsISupports)) {
|
|
@ -149,18 +149,153 @@ Zotero.Translators = new function() {
|
|||
|
||||
/**
|
||||
* Gets the translator that corresponds to a given ID
|
||||
* @param {String} id The ID of the translator
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned.
|
||||
*/
|
||||
this.get = function(id) {
|
||||
this.get = function(id, callback) {
|
||||
if(!_initialized) this.init();
|
||||
return _translators[id] ? _translators[id] : false;
|
||||
var translator = _translators[id] ? _translators[id] : false;
|
||||
|
||||
if(callback) {
|
||||
callback(translator);
|
||||
return true;
|
||||
}
|
||||
return translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all translators for a specific type of translation
|
||||
* @param {String} type The type of translators to get (import, export, web, or search)
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned.
|
||||
*/
|
||||
this.getAllForType = function(type, callback) {
|
||||
if(!_initialized) this.init()
|
||||
|
||||
var translators = _cache[type].slice(0);
|
||||
if(callback) {
|
||||
callback(translators);
|
||||
return true;
|
||||
}
|
||||
return translators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all translators for a specific type of translation
|
||||
*/
|
||||
this.getAllForType = function(type) {
|
||||
this.getAll = function() {
|
||||
if(!_initialized) this.init();
|
||||
return _cache[type].slice(0);
|
||||
return [translator for each(translator in _translators)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets web translators for a specific location
|
||||
* @param {String} uri The URI for which to look for translators
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned. The callback is passed a set of functions for
|
||||
* converting URLs from proper to proxied forms as the second
|
||||
* argument.
|
||||
*/
|
||||
this.getWebTranslatorsForLocation = function(uri, callback) {
|
||||
var allTranslators = this.getAllForType("web");
|
||||
var potentialTranslators = [];
|
||||
|
||||
var properHosts = [];
|
||||
var proxyHosts = [];
|
||||
|
||||
var properURI = Zotero.Proxies.proxyToProper(uri);
|
||||
var knownProxy = properURI !== uri;
|
||||
if(knownProxy) {
|
||||
// if we know this proxy, just use the proper URI for detection
|
||||
var searchURIs = [properURI];
|
||||
} else {
|
||||
var searchURIs = [uri];
|
||||
|
||||
// if there is a subdomain that is also a TLD, also test against URI with the domain
|
||||
// dropped after the TLD
|
||||
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
|
||||
var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
|
||||
if(m) {
|
||||
var hostnames = m[2].split(".");
|
||||
for(var i=1; i<hostnames.length-2; i++) {
|
||||
if(TLDS[hostnames[i].toLowerCase()]) {
|
||||
var properHost = hostnames.slice(0, i+1).join(".");
|
||||
searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
|
||||
properHosts.push(properHost);
|
||||
proxyHosts.push(hostnames.slice(i+1).join("."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Translators: Looking for translators for "+searchURIs.join(", "));
|
||||
|
||||
var converterFunctions = [];
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
for(var j=0; j<searchURIs.length; j++) {
|
||||
if((!allTranslators[i].webRegexp
|
||||
&& allTranslators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER)
|
||||
|| (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
|
||||
// add translator to list
|
||||
potentialTranslators.push(allTranslators[i]);
|
||||
|
||||
if(j === 0) {
|
||||
if(knownProxy) {
|
||||
converterFunctions.push(Zotero.Proxies.properToProxy);
|
||||
} else {
|
||||
converterFunctions.push(null);
|
||||
}
|
||||
} else {
|
||||
converterFunctions.push(new function() {
|
||||
var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
|
||||
var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
|
||||
return function(uri) { return uri.replace(re, "$&."+proxyHost) };
|
||||
});
|
||||
}
|
||||
|
||||
// don't add translator more than once
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(callback) {
|
||||
callback([potentialTranslators, converterFunctions]);
|
||||
return true;
|
||||
}
|
||||
return potentialTranslators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets import translators for a specific location
|
||||
* @param {String} location The location for which to look for translators
|
||||
* @param {Function} [callback] An optional callback to be executed when translators have been
|
||||
* retrieved. If no callback is specified, translators are
|
||||
* returned.
|
||||
*/
|
||||
this.getImportTranslatorsForLocation = function(location, callback) {
|
||||
var allTranslators = Zotero.Translators.getAllForType("import");
|
||||
var tier1Translators = [];
|
||||
var tier2Translators = [];
|
||||
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
if(allTranslators[i].importRegexp.test(location)) {
|
||||
tier1Translators.push(allTranslators[i]);
|
||||
} else {
|
||||
tier2Translators.push(allTranslators[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var translators = tier1Translators.concat(tier2Translators);
|
||||
if(callback) {
|
||||
callback(translators);
|
||||
return true;
|
||||
}
|
||||
return translators;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +306,6 @@ Zotero.Translators = new function() {
|
|||
return Zotero.File.getValidFileName(label) + ".js";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} metadata
|
||||
* @param {String} metadata.translatorID Translator GUID
|
||||
|
@ -262,29 +396,6 @@ Zotero.Translators = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Represents an individual translator
|
||||
* @constructor
|
||||
* @param {nsIFile} file File from which to generate a translator object
|
||||
* @property {String} translatorID Unique GUID of the translator
|
||||
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
|
||||
* @property {String} label Human-readable name of the translator
|
||||
* @property {String} creator Author(s) of the translator
|
||||
* @property {String} target Location that the translator processes
|
||||
* @property {String} minVersion Minimum Zotero version
|
||||
* @property {String} maxVersion Minimum Zotero version
|
||||
* @property {Integer} priority Lower-priority translators will be selected first
|
||||
* @property {String} browserSupport String indicating browser supported by the translator
|
||||
* g = Gecko (Firefox)
|
||||
* c = Google Chrome (WebKit & V8)
|
||||
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
|
||||
* i = Internet Explorer
|
||||
* @property {Object} configOptions Configuration options for import/export
|
||||
* @property {Object} displayOptions Display options for export
|
||||
* @property {Boolean} inRepository Whether the translator may be found in the repository
|
||||
* @property {String} lastUpdated SQL-style date and time of translator's last update
|
||||
* @property {String} code The executable JavaScript for the translator
|
||||
*/
|
||||
Zotero.Translator = function(file, json, code) {
|
||||
const codeGetterFunction = function() { return Zotero.File.getContents(this.file); }
|
||||
// Maximum length for the info JSON in a translator
|
||||
|
@ -355,6 +466,7 @@ Zotero.Translator = function(file, json, code) {
|
|||
this._configOptions = info["configOptions"] ? info["configOptions"] : {};
|
||||
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
|
||||
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
||||
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
|
||||
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
||||
// compile import regexp to match only file extension
|
||||
|
@ -374,7 +486,6 @@ Zotero.Translator = function(file, json, code) {
|
|||
try {
|
||||
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
|
||||
} catch(e) {
|
||||
if(fStream) fStream.close();
|
||||
this.logError("Invalid target in " + file.leafName);
|
||||
this.webRegexp = null;
|
||||
if(fStream) fStream.close();
|
||||
|
@ -420,4 +531,8 @@ Zotero.Translator.prototype.logError = function(message, type, line, lineNumber,
|
|||
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService);
|
||||
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
|
|
@ -595,6 +595,17 @@ Zotero.Utilities = {
|
|||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Escapes metacharacters in a literal so that it may be used in a regular expression
|
||||
*/
|
||||
"quotemeta":function(literal) {
|
||||
if(typeof literal !== "string") {
|
||||
throw "Argument "+literal+" must be a string in Zotero.Utilities.quotemeta()";
|
||||
}
|
||||
const metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g;
|
||||
return literal.replace(metaRegexp, "\\$&");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -746,13 +757,11 @@ Zotero.Utilities.Translate.prototype.loadDocument = function(url, succeeded, fai
|
|||
* @ignore
|
||||
*/
|
||||
Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) {
|
||||
if(this._translate.locationIsProxied) {
|
||||
if(typeof(urls) == "string") {
|
||||
urls = [this._convertURL(urls)];
|
||||
} else {
|
||||
for(var i in urls) {
|
||||
urls[i] = this._convertURL(urls[i]);
|
||||
}
|
||||
if(typeof(urls) == "string") {
|
||||
urls = [this._convertURL(urls)];
|
||||
} else {
|
||||
for(var i in urls) {
|
||||
urls[i] = this._convertURL(urls[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,7 +785,7 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
|
|||
* @return {Document} DOM document object
|
||||
*/
|
||||
Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
|
||||
if(this._translate.locationIsProxied) url = this._convertURL(url);
|
||||
url = this._convertURL(url);
|
||||
|
||||
var mainThread = Zotero.mainThread;
|
||||
var loaded = false;
|
||||
|
@ -822,7 +831,7 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, header
|
|||
/* Apparently, a synchronous XMLHttpRequest would have the behavior of this routine in FF3, but
|
||||
* in FF3.5, synchronous XHR blocks all JavaScript on the thread. See
|
||||
* http://hacks.mozilla.org/2009/07/synchronous-xhr/. */
|
||||
if(this._translate.locationIsProxied) url = this._convertURL(url);
|
||||
url = this._convertURL(url);
|
||||
if(!headers) headers = null;
|
||||
if(!responseCharset) responseCharset = null;
|
||||
|
||||
|
@ -911,15 +920,25 @@ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, header
|
|||
*/
|
||||
Zotero.Utilities.Translate.prototype._convertURL = function(url) {
|
||||
const protocolRe = /^(?:(?:http|https|ftp):)/i;
|
||||
const fileRe = /^[^:]*/;
|
||||
|
||||
if(this._translate.locationIsProxied) {
|
||||
url = Zotero.Proxies.properToProxy(url);
|
||||
// convert proxy to proper if applicable
|
||||
if(this._translate.translator && this._translate.translator[0]
|
||||
&& this._translate.translator[0].properToProxy) {
|
||||
url = this._translate.translator[0].properToProxy(url);
|
||||
}
|
||||
if(protocolRe.test(url)) return url;
|
||||
if(!fileRe.test(url)) {
|
||||
throw "Invalid URL supplied for HTTP request";
|
||||
|
||||
if(Zotero.isChrome || Zotero.isSafari) {
|
||||
// this code is sandboxed, so we don't worry
|
||||
return url;
|
||||
} else {
|
||||
if(protocolRe.test(url)) return url;
|
||||
|
||||
if(uri.indexOf(":") !== -1) {
|
||||
// don't allow protocol switches
|
||||
throw "Invalid URL supplied for HTTP request";
|
||||
}
|
||||
|
||||
// resolve relative URIs
|
||||
return Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService).
|
||||
newURI(this._translate.location, "", null).resolve(url);
|
||||
|
|
|
@ -36,6 +36,8 @@ const ZOTERO_CONFIG = {
|
|||
PREF_BRANCH: 'extensions.zotero.'
|
||||
};
|
||||
|
||||
const ZOTERO_METAREGEXP = /[-[\]{}()*+?.\\^$|,#\s]/g;
|
||||
|
||||
// Fx4.0b8+ use implicit SJOWs and get rid of explicit XPCSafeJSObjectWrapper constructor
|
||||
// Ugly hack to get around this until we can just kill the XPCSafeJSObjectWrapper calls (when we
|
||||
// drop Fx3.6 support)
|
||||
|
@ -45,10 +47,17 @@ try {
|
|||
eval("var XPCSafeJSObjectWrapper = function(arg) { return arg }");
|
||||
}
|
||||
|
||||
// Load AddonManager for Firefox 4
|
||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
|
||||
getService(Components.interfaces.nsIXULAppInfo);
|
||||
if(appInfo.platformVersion[0] >= 2) {
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
}
|
||||
|
||||
/*
|
||||
* Core functions
|
||||
*/
|
||||
var Zotero = new function(){
|
||||
(function(){
|
||||
// Privileged (public) methods
|
||||
this.init = init;
|
||||
this.stateCheck = stateCheck;
|
||||
|
@ -173,34 +182,39 @@ var Zotero = new function(){
|
|||
|
||||
var _locked;
|
||||
var _unlockCallbacks = [];
|
||||
var _shutdownListeners = [];
|
||||
var _progressMeters;
|
||||
var _lastPercentage;
|
||||
|
||||
// whether we are waiting for another Zotero process to release its DB lock
|
||||
var _waitingForDBLock = false;
|
||||
// whether we are waiting for another Zotero process to initialize so we can use connector
|
||||
var _waitingForInitComplete = false;
|
||||
|
||||
// whether we should broadcast an initComplete message when initialization finishes (we should
|
||||
// do this if we forced another Zotero process to release its lock)
|
||||
var _broadcastInitComplete = false;
|
||||
|
||||
/**
|
||||
* A set of nsITimerCallbacks to be executed when Zotero.wait() completes
|
||||
*/
|
||||
var _waitTimerCallbacks = [];
|
||||
|
||||
/*
|
||||
/**
|
||||
* Initialize the extension
|
||||
*/
|
||||
function init(){
|
||||
function init() {
|
||||
if (this.initialized || this.skipLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var start = (new Date()).getTime()
|
||||
var start = (new Date()).getTime();
|
||||
|
||||
// Register shutdown handler to call Zotero.shutdown()
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
observerService.addObserver({
|
||||
observe: Zotero.shutdown
|
||||
}, "quit-application", false);
|
||||
|
||||
// Load in the preferences branch for the extension
|
||||
Zotero.Prefs.init();
|
||||
|
||||
Zotero.Debug.init();
|
||||
|
||||
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
|
||||
|
@ -214,6 +228,7 @@ var Zotero = new function(){
|
|||
this.isFx31 = this.isFx35;
|
||||
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
|
||||
this.isFx4 = appInfo.platformVersion[0] >= 2;
|
||||
this.isFx5 = appInfo.platformVersion[0] >= 5;
|
||||
|
||||
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
|
||||
if(this.isStandalone) {
|
||||
|
@ -242,6 +257,9 @@ var Zotero = new function(){
|
|||
this.isLinux = (this.platform.substr(0, 5) == "Linux");
|
||||
this.oscpu = win.navigator.oscpu;
|
||||
|
||||
// Browser
|
||||
Zotero.browser = "g";
|
||||
|
||||
// Locale
|
||||
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService);
|
||||
|
@ -275,19 +293,19 @@ var Zotero = new function(){
|
|||
xmlhttp.send(null);
|
||||
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
|
||||
if (matches && matches[0] == 'rtl') {
|
||||
this.dir = 'rtl';
|
||||
Zotero.dir = 'rtl';
|
||||
}
|
||||
else {
|
||||
this.dir = 'ltr';
|
||||
Zotero.dir = 'ltr';
|
||||
}
|
||||
|
||||
try {
|
||||
var dataDir = this.getZoteroDirectory();
|
||||
var dataDir = Zotero.getZoteroDirectory();
|
||||
}
|
||||
catch (e) {
|
||||
// Zotero dir not found
|
||||
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
|
||||
this.startupError = Zotero.getString('dataDir.notFound');
|
||||
Zotero.startupError = Zotero.getString('dataDir.notFound');
|
||||
_startupErrorHandler = function() {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
|
@ -300,7 +318,7 @@ var Zotero = new function(){
|
|||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||||
var index = ps.confirmEx(win,
|
||||
Zotero.getString('general.error'),
|
||||
this.startupError + '\n\n' +
|
||||
Zotero.startupError + '\n\n' +
|
||||
Zotero.getString('dataDir.previousDir') + ' '
|
||||
+ Zotero.Prefs.get('lastDataDir'),
|
||||
buttonFlags, null,
|
||||
|
@ -382,7 +400,6 @@ var Zotero = new function(){
|
|||
else if (index == 2) {
|
||||
Zotero.chooseZoteroDirectory(true);
|
||||
}
|
||||
var dataDir = this.getZoteroDirectory();
|
||||
}
|
||||
// DEBUG: handle more startup errors
|
||||
else {
|
||||
|
@ -391,6 +408,54 @@ var Zotero = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
Zotero.IPC.init();
|
||||
|
||||
// Load additional info for connector or not
|
||||
if(Zotero.isConnector) {
|
||||
Zotero.debug("Loading in connector mode");
|
||||
Zotero.Connector.init();
|
||||
} else {
|
||||
Zotero.debug("Loading in full mode");
|
||||
_initFull();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Register shutdown handler to call Zotero.shutdown()
|
||||
var _shutdownObserver = {observe:Zotero.shutdown};
|
||||
observerService.addObserver(_shutdownObserver, "quit-application", false);
|
||||
|
||||
// Add shutdown listerner to remove observer
|
||||
this.addShutdownListener(function() {
|
||||
observerService.removeObserver(_shutdownObserver, "quit-application", false);
|
||||
});
|
||||
|
||||
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
|
||||
|
||||
if(!Zotero.isFirstLoadThisSession) {
|
||||
if(Zotero.isConnector) {
|
||||
// wait for initComplete message if we switched to connector because standalone was
|
||||
// started
|
||||
_waitingForInitComplete = true;
|
||||
while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
|
||||
}
|
||||
|
||||
// trigger zotero-reloaded event
|
||||
Zotero.debug('Triggering "zotero-reloaded" event');
|
||||
observerService.notifyObservers(Zotero, "zotero-reloaded", null);
|
||||
}
|
||||
|
||||
// Broadcast initComplete message if desired
|
||||
if(_broadcastInitComplete) Zotero.IPC.broadcast("initComplete");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization function to be called only if Zotero is in full mode
|
||||
*/
|
||||
function _initFull() {
|
||||
var dataDir = Zotero.getZoteroDirectory();
|
||||
Zotero.VersionHeader.init();
|
||||
|
||||
// Check for DB restore
|
||||
|
@ -412,7 +477,7 @@ var Zotero = new function(){
|
|||
Zotero.Schema.skipDefaultData = true;
|
||||
Zotero.Schema.updateSchema();
|
||||
|
||||
this.restoreFromServer = true;
|
||||
Zotero.restoreFromServer = true;
|
||||
}
|
||||
catch (e) {
|
||||
// Restore from backup?
|
||||
|
@ -420,54 +485,7 @@ var Zotero = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Test read access
|
||||
Zotero.DB.test();
|
||||
|
||||
var dbfile = Zotero.getZoteroDatabase();
|
||||
|
||||
// Test write access on Zotero data directory
|
||||
if (!dbfile.parent.isWritable()) {
|
||||
var msg = 'Cannot write to ' + dbfile.parent.path + '/';
|
||||
}
|
||||
// Test write access on Zotero database
|
||||
else if (!dbfile.isWritable()) {
|
||||
var msg = 'Cannot write to ' + dbfile.path;
|
||||
}
|
||||
else {
|
||||
var msg = false;
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
var e = {
|
||||
name: 'NS_ERROR_FILE_ACCESS_DENIED',
|
||||
message: msg,
|
||||
toString: function () {
|
||||
return this.name + ': ' + this.message;
|
||||
}
|
||||
};
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
|
||||
var msg = Zotero.localeJoin([
|
||||
Zotero.getString('startupError.databaseCannotBeOpened'),
|
||||
Zotero.getString('startupError.checkPermissions')
|
||||
]);
|
||||
this.startupError = msg;
|
||||
} else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
|
||||
var msg = Zotero.localeJoin([
|
||||
Zotero.getString('startupError.databaseInUse'),
|
||||
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
|
||||
]);
|
||||
this.startupError = msg;
|
||||
}
|
||||
|
||||
Components.utils.reportError(e);
|
||||
this.skipLoading = true;
|
||||
return;
|
||||
}
|
||||
if(!_initDB()) return;
|
||||
|
||||
// Add notifier queue callbacks to the DB layer
|
||||
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
|
||||
|
@ -477,7 +495,7 @@ var Zotero = new function(){
|
|||
Zotero.Fulltext.init();
|
||||
|
||||
// Require >=2.1b3 database to ensure proper locking
|
||||
if (this.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
|
||||
if (Zotero.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
|
||||
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
||||
.getService(Components.interfaces.nsIAppStartup);
|
||||
|
||||
|
@ -541,7 +559,7 @@ var Zotero = new function(){
|
|||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
|
||||
this.skipLoading = true;
|
||||
Zotero.skipLoading = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -549,7 +567,7 @@ var Zotero = new function(){
|
|||
if (Zotero.Schema.userDataUpgradeRequired()) {
|
||||
var upgraded = Zotero.Schema.showUpgradeWizard();
|
||||
if (!upgraded) {
|
||||
this.skipLoading = true;
|
||||
Zotero.skipLoading = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -567,12 +585,12 @@ var Zotero = new function(){
|
|||
]) + "\n\n"
|
||||
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
|
||||
+ Zotero.getString('general.seeForMoreInformation', kbURL);
|
||||
this.startupError = msg;
|
||||
Zotero.startupError = msg;
|
||||
}
|
||||
else {
|
||||
this.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
||||
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
||||
}
|
||||
this.skipLoading = true;
|
||||
Zotero.skipLoading = true;
|
||||
Components.utils.reportError(e);
|
||||
return false;
|
||||
}
|
||||
|
@ -589,8 +607,8 @@ var Zotero = new function(){
|
|||
// Initialize various services
|
||||
Zotero.Integration.init();
|
||||
|
||||
if(Zotero.Prefs.get("connector.enabled")) {
|
||||
Zotero.Connector.init();
|
||||
if(Zotero.Prefs.get("httpServer.enabled")) {
|
||||
Zotero.Server.init();
|
||||
}
|
||||
|
||||
Zotero.Zeroconf.init();
|
||||
|
@ -607,18 +625,108 @@ var Zotero = new function(){
|
|||
// Initialize Locate Manager
|
||||
Zotero.LocateManager.init();
|
||||
|
||||
this.initialized = true;
|
||||
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DB connection
|
||||
*/
|
||||
function _initDB() {
|
||||
try {
|
||||
// Test read access
|
||||
Zotero.DB.test();
|
||||
|
||||
var dbfile = Zotero.getZoteroDatabase();
|
||||
|
||||
// Test write access on Zotero data directory
|
||||
if (!dbfile.parent.isWritable()) {
|
||||
var msg = 'Cannot write to ' + dbfile.parent.path + '/';
|
||||
}
|
||||
// Test write access on Zotero database
|
||||
else if (!dbfile.isWritable()) {
|
||||
var msg = 'Cannot write to ' + dbfile.path;
|
||||
}
|
||||
else {
|
||||
var msg = false;
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
var e = {
|
||||
name: 'NS_ERROR_FILE_ACCESS_DENIED',
|
||||
message: msg,
|
||||
toString: function () {
|
||||
return Zotero.name + ': ' + Zotero.message;
|
||||
}
|
||||
};
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
|
||||
var msg = Zotero.localeJoin([
|
||||
Zotero.getString('startupError.databaseCannotBeOpened'),
|
||||
Zotero.getString('startupError.checkPermissions')
|
||||
]);
|
||||
Zotero.startupError = msg;
|
||||
} else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
|
||||
if(Zotero.isStandalone) {
|
||||
// Standalone should force Fx to release lock
|
||||
if(Zotero.IPC.broadcast("releaseLock")) {
|
||||
_waitingForDBLock = true;
|
||||
while(_waitingForDBLock) Zotero.mainThread.processNextEvent(true);
|
||||
// we will want to broadcast when initialization completes
|
||||
_broadcastInitComplete = true;
|
||||
return _initDB();
|
||||
}
|
||||
} else {
|
||||
// Fx should start as connector if Standalone is running
|
||||
var haveStandalone = Zotero.IPC.broadcast("test");
|
||||
if(haveStandalone) {
|
||||
throw "ZOTERO_SHOULD_START_AS_CONNECTOR";
|
||||
}
|
||||
}
|
||||
|
||||
var msg = Zotero.localeJoin([
|
||||
Zotero.getString('startupError.databaseInUse'),
|
||||
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
|
||||
]);
|
||||
Zotero.startupError = msg;
|
||||
}
|
||||
|
||||
Components.utils.reportError(e);
|
||||
Zotero.skipLoading = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the DB has been released by another Zotero process to perform necessary
|
||||
* initialization steps
|
||||
*/
|
||||
this.onDBLockReleased = function() {
|
||||
if(Zotero.isConnector) {
|
||||
// if DB lock is released, switch out of connector mode
|
||||
switchConnectorMode(false);
|
||||
} else if(_waitingForDBLock) {
|
||||
// if waiting for DB lock and we get it, continue init
|
||||
_waitingForDBLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an accessory process has been initialized to let use get data
|
||||
*/
|
||||
this.onInitComplete = function() {
|
||||
_waitingForInitComplete = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a DB transaction is open and, if so, disable Zotero
|
||||
*/
|
||||
function stateCheck() {
|
||||
if (Zotero.DB.transactionInProgress()) {
|
||||
if(!Zotero.isConnector && Zotero.DB.transactionInProgress()) {
|
||||
this.initialized = false;
|
||||
this.skipLoading = true;
|
||||
return false;
|
||||
|
@ -630,7 +738,33 @@ var Zotero = new function(){
|
|||
|
||||
this.shutdown = function (subject, topic, data) {
|
||||
Zotero.debug("Shutting down Zotero");
|
||||
Zotero.removeTempDirectory();
|
||||
|
||||
try {
|
||||
// run shutdown listener
|
||||
for each(var listener in _shutdownListeners) listener();
|
||||
|
||||
// remove temp directory
|
||||
Zotero.removeTempDirectory();
|
||||
|
||||
if(Zotero.initialized && Zotero.DB) {
|
||||
Zotero.debug("Closing database");
|
||||
|
||||
// run GC to finalize open statements
|
||||
// TODO remove this and finalize statements created with
|
||||
// Zotero.DBConnection.getStatement() explicitly
|
||||
Components.utils.forceGC();
|
||||
|
||||
// unlock DB
|
||||
Zotero.DB.closeDatabase();
|
||||
|
||||
// broadcast that DB lock has been released
|
||||
Zotero.IPC.broadcast("lockReleased");
|
||||
}
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1511,6 +1645,12 @@ var Zotero = new function(){
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to be called when Zotero shuts down (even if Firefox is not shut down)
|
||||
*/
|
||||
this.addShutdownListener = function(listener) {
|
||||
_shutdownListeners.push(listener);
|
||||
}
|
||||
|
||||
function _showWindowZoteroPaneOverlay(doc) {
|
||||
doc.getElementById('zotero-collections-tree').disabled = true;
|
||||
|
@ -1658,9 +1798,21 @@ var Zotero = new function(){
|
|||
Zotero.Creators.reloadAll();
|
||||
Zotero.Items.reloadAll();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Brings Zotero Standalone to the foreground
|
||||
*/
|
||||
this.activateStandalone = function() {
|
||||
var io = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var uri = io.newURI('zotero://select', null, null);
|
||||
var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1']
|
||||
.getService(Components.interfaces.nsIExternalProtocolService)
|
||||
.getProtocolHandlerInfo('zotero');
|
||||
handler.preferredAction = Components.interfaces.nsIHandlerInfo.useSystemDefault;
|
||||
handler.launchWithURI(uri, null);
|
||||
}
|
||||
}).call(Zotero);
|
||||
|
||||
Zotero.Prefs = new function(){
|
||||
// Privileged methods
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
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.
|
|
@ -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]);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<xpcomFiles.length; i++) {
|
||||
try {
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[i] + ".js");
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError("Error loading " + xpcomFiles[i] + ".js");
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
|
||||
var rdfXpcomFiles = [
|
||||
'rdf/uri',
|
||||
'rdf/term',
|
||||
'rdf/identity',
|
||||
'rdf/match',
|
||||
'rdf/n3parser',
|
||||
'rdf/rdfparser',
|
||||
'rdf/serialize',
|
||||
'rdf'
|
||||
/** XPCOM files to be loaded only for connector translation and DB access **/
|
||||
const xpcomFilesConnector = [
|
||||
'connector/translate_item',
|
||||
'connector/translator',
|
||||
'connector/connector',
|
||||
'connector/cachedTypes'
|
||||
];
|
||||
|
||||
Zotero.RDF = {AJAW:{}};
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
for (var i=0; i<rdfXpcomFiles.length; i++) {
|
||||
var instanceID = (new Date()).getTime();
|
||||
var isFirstLoadThisSession = true;
|
||||
var zContext = null;
|
||||
|
||||
ZoteroContext = function() {}
|
||||
ZoteroContext.prototype = {
|
||||
/**
|
||||
* Convenience method to replicate window.alert()
|
||||
**/
|
||||
// TODO: is this still used? if so, move to zotero.js
|
||||
"alert":function alert(msg){
|
||||
this.Zotero.debug("alert() is deprecated from Zotero XPCOM");
|
||||
Cc["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Ci.nsIPromptService)
|
||||
.alert(null, "", msg);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convenience method to replicate window.confirm()
|
||||
**/
|
||||
// TODO: is this still used? if so, move to zotero.js
|
||||
"confirm":function confirm(msg){
|
||||
this.Zotero.debug("confirm() is deprecated from Zotero XPCOM");
|
||||
return Cc["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Ci.nsIPromptService)
|
||||
.confirm(null, "", msg);
|
||||
},
|
||||
|
||||
"Cc":Cc,
|
||||
"Ci":Ci,
|
||||
|
||||
/**
|
||||
* Convenience method to replicate window.setTimeout()
|
||||
**/
|
||||
"setTimeout":function setTimeout(func, ms){
|
||||
this.Zotero.setTimeout(func, ms);
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches in or out of connector mode
|
||||
*/
|
||||
"switchConnectorMode":function(isConnector) {
|
||||
if(isConnector !== this.isConnector) {
|
||||
zContext.Zotero.shutdown();
|
||||
|
||||
// create a new zContext
|
||||
makeZoteroContext(isConnector);
|
||||
zContext.Zotero.init();
|
||||
}
|
||||
|
||||
return zContext;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The class from which the Zotero global XPCOM context is constructed
|
||||
*
|
||||
* @constructor
|
||||
* This runs when ZoteroService is first requested to load all applicable scripts and initialize
|
||||
* Zotero. Calls to other XPCOM components must be in here rather than in top-level code, as other
|
||||
* components may not have yet been initialized.
|
||||
*/
|
||||
function makeZoteroContext(isConnector) {
|
||||
if(zContext) {
|
||||
// Swap out old zContext
|
||||
var oldzContext = zContext;
|
||||
// Create new zContext
|
||||
zContext = new ZoteroContext();
|
||||
// Swap in old Zotero object, so that references don't break, but empty it
|
||||
zContext.Zotero = oldzContext.Zotero;
|
||||
for(var key in zContext.Zotero) delete zContext.Zotero[key];
|
||||
} else {
|
||||
zContext = new ZoteroContext();
|
||||
zContext.Zotero = function() {};
|
||||
}
|
||||
|
||||
// Load zotero.js first
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", Zotero.RDF.AJAW);
|
||||
}
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://global/content/nsTransferable.js");
|
||||
|
||||
/********************************************************************/
|
||||
|
||||
|
||||
// Initialize the Zotero service
|
||||
//
|
||||
// This runs when ZoteroService is first requested.
|
||||
// Calls to other XPCOM components must be in here rather than in top-level
|
||||
// code, as other components may not have yet been initialized.
|
||||
function setupService(){
|
||||
try {
|
||||
Zotero.init();
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext);
|
||||
|
||||
// Load CiteProc into Zotero.CiteProc namespace
|
||||
zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero};
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc);
|
||||
|
||||
// Load remaining xpcomFiles
|
||||
for (var i=1; i<xpcomFilesAll.length; i++) {
|
||||
try {
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext);
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext);
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
// Load xpcomFiles for specific mode
|
||||
for each(var xpcomFile in (isConnector ? xpcomFilesConnector : xpcomFilesLocal)) {
|
||||
try {
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFile + ".js", zContext);
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError("Error loading " + xpcomFile + ".js", zContext);
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
|
||||
const rdfXpcomFiles = [
|
||||
'rdf/uri',
|
||||
'rdf/term',
|
||||
'rdf/identity',
|
||||
'rdf/match',
|
||||
'rdf/n3parser',
|
||||
'rdf/rdfparser',
|
||||
'rdf/serialize',
|
||||
'rdf'
|
||||
];
|
||||
zContext.Zotero.RDF = {AJAW:{Zotero:zContext.Zotero}};
|
||||
for (var i=0; i<rdfXpcomFiles.length; i++) {
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", zContext.Zotero.RDF.AJAW);
|
||||
}
|
||||
|
||||
// load nsTransferable (query: do we still use this?)
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://global/content/nsTransferable.js", zContext);
|
||||
|
||||
// add connector-related properties
|
||||
zContext.Zotero.isConnector = isConnector;
|
||||
zContext.Zotero.instanceID = instanceID;
|
||||
zContext.Zotero.__defineGetter__("isFirstLoadThisSession", function() isFirstLoadThisSession);
|
||||
};
|
||||
|
||||
/**
|
||||
* The class representing the Zotero service, and affiliated XPCOM goop
|
||||
*/
|
||||
function ZoteroService(){
|
||||
try {
|
||||
if(isFirstLoadThisSession) {
|
||||
makeZoteroContext(false);
|
||||
try {
|
||||
zContext.Zotero.init();
|
||||
} catch(e) {
|
||||
if(e === "ZOTERO_SHOULD_START_AS_CONNECTOR") {
|
||||
// if Zotero should start as a connector, reload it
|
||||
zContext.Zotero.shutdown();
|
||||
makeZoteroContext(true);
|
||||
zContext.Zotero.init();
|
||||
} else {
|
||||
dump(e.toSource());
|
||||
Components.utils.reportError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
isFirstLoadThisSession = false; // no longer first load
|
||||
this.wrappedJSObject = zContext.Zotero;
|
||||
} catch(e) {
|
||||
var msg = typeof e == 'string' ? e : e.name;
|
||||
dump(e + "\n\n");
|
||||
Components.utils.reportError(e);
|
||||
throw (e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function ZoteroService(){
|
||||
this.wrappedJSObject = ZoteroWrapped.Zotero;
|
||||
setupService();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to replicate window.alert()
|
||||
**/
|
||||
// TODO: is this still used? if so, move to zotero.js
|
||||
function alert(msg){
|
||||
Cc["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Ci.nsIPromptService)
|
||||
.alert(null, "", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to replicate window.confirm()
|
||||
**/
|
||||
// TODO: is this still used? if so, move to zotero.js
|
||||
function confirm(msg){
|
||||
return Cc["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Ci.nsIPromptService)
|
||||
.confirm(null, "", msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to replicate window.setTimeout()
|
||||
**/
|
||||
function setTimeout(func, ms) {
|
||||
Zotero.setTimeout(func, ms);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// XPCOM goop
|
||||
//
|
||||
|
@ -227,8 +301,8 @@ ZoteroService.prototype = {
|
|||
contractID: ZOTERO_CONTRACTID,
|
||||
classDescription: ZOTERO_CLASSNAME,
|
||||
classID: ZOTERO_CID,
|
||||
service: true,
|
||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, ZOTERO_IID])
|
||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
|
||||
Components.interfaces.nsIProtocolHandler])
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -107,8 +107,8 @@ pref("extensions.zotero.integration.port", 50001);
|
|||
pref("extensions.zotero.integration.autoRegenerate", -1); // -1 = ask; 0 = no; 1 = yes
|
||||
|
||||
// Connector settings
|
||||
pref("extensions.zotero.connector.enabled", false);
|
||||
pref("extensions.zotero.connector.port", 23119); // ascii "ZO"
|
||||
pref("extensions.zotero.httpServer.enabled", false); // TODO enabled for testing only
|
||||
pref("extensions.zotero.httpServer.port", 23119); // ascii "ZO"
|
||||
|
||||
// Zeroconf
|
||||
pref("extensions.zotero.zeroconf.server.enabled", false);
|
||||
|
|
Loading…
Reference in a new issue