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
|
component {9BC3D762-9038-486A-9D70-C997AF848A7C} components/zotero-protocol-handler.js
|
||||||
contract @mozilla.org/network/protocol;1?name=zotero {9BC3D762-9038-486A-9D70-C997AF848A7C}
|
contract @mozilla.org/network/protocol;1?name=zotero {9BC3D762-9038-486A-9D70-C997AF848A7C}
|
||||||
|
|
||||||
component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-integration-service.js
|
component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-command-line-handler.js
|
||||||
contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration {531828f8-a16c-46be-b9aa-14845c3b010f}
|
contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero {531828f8-a16c-46be-b9aa-14845c3b010f}
|
||||||
category command-line-handler m-zotero-integration @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration
|
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);
|
function(e) { Zotero_Browser.chromeLoad(e) }, false);
|
||||||
window.addEventListener("unload",
|
window.addEventListener("unload",
|
||||||
function(e) { Zotero_Browser.chromeUnload(e) }, false);
|
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
|
// get libraryID and collectionID
|
||||||
var libraryID, collectionID;
|
var libraryID, collectionID;
|
||||||
if(ZoteroPane) {
|
if(ZoteroPane && !Zotero.isConnector) {
|
||||||
libraryID = ZoteroPane.getSelectedLibraryID();
|
libraryID = ZoteroPane.getSelectedLibraryID();
|
||||||
collectionID = ZoteroPane.getSelectedCollection(true);
|
collectionID = ZoteroPane.getSelectedCollection(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -374,7 +385,7 @@ var Zotero_Browser = new function() {
|
||||||
// get data object
|
// get data object
|
||||||
var tab = _getTabObject(browser);
|
var tab = _getTabObject(browser);
|
||||||
|
|
||||||
if(isHTML) {
|
if(isHTML && !Zotero.isConnector) {
|
||||||
var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec);
|
var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec);
|
||||||
if(annotationID) {
|
if(annotationID) {
|
||||||
if(Zotero.Annotate.isAnnotated(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
|
* Callback to be executed when an item has been finished
|
||||||
*/
|
*/
|
||||||
function itemDone(obj, item, collection) {
|
function itemDone(obj, item, collection) {
|
||||||
var title = item.getField("title", false, true);
|
var title = item.title;
|
||||||
var icon = item.getImageSrc();
|
var icon = Zotero.ItemTypes.getImageSrc(item.itemType);
|
||||||
Zotero_Browser.progress.show();
|
Zotero_Browser.progress.show();
|
||||||
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scraping"));
|
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scraping"));
|
||||||
Zotero_Browser.progress.addLines([title], [icon]);
|
Zotero_Browser.progress.addLines([title], [icon]);
|
||||||
|
@ -742,7 +753,7 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
|
||||||
this.page.hasBeenTranslated = true;
|
this.page.hasBeenTranslated = true;
|
||||||
}
|
}
|
||||||
this.page.translate.clearHandlers("itemDone");
|
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);
|
this.page.translate.translate(libraryID);
|
||||||
}
|
}
|
||||||
|
@ -755,12 +766,10 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
|
||||||
Zotero_Browser.Tab.prototype.getCaptureIcon = function() {
|
Zotero_Browser.Tab.prototype.getCaptureIcon = function() {
|
||||||
if(this.page.translators && this.page.translators.length) {
|
if(this.page.translators && this.page.translators.length) {
|
||||||
var itemType = this.page.translators[0].itemType;
|
var itemType = this.page.translators[0].itemType;
|
||||||
if(itemType == "multiple") {
|
Zotero.debug("want capture icon for "+itemType);
|
||||||
// Use folder icon for multiple types, for now
|
return (itemType === "multiple"
|
||||||
return "chrome://zotero/skin/treesource-collection.png";
|
? "chrome://zotero/skin/treesource-collection.png"
|
||||||
} else {
|
: Zotero.ItemTypes.getImageSrc(itemType));
|
||||||
return Zotero.ItemTypes.getImageSrc(itemType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -784,7 +793,7 @@ Zotero_Browser.Tab.prototype.getCaptureTooltip = function() {
|
||||||
/*
|
/*
|
||||||
* called when a user is supposed to select items
|
* 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!
|
// this is kinda ugly, mozillazine made me do it! honest!
|
||||||
var io = { dataIn:itemList, dataOut:null }
|
var io = { dataIn:itemList, dataOut:null }
|
||||||
var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
|
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();
|
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();
|
Zotero_Browser.updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles the display of a div showing progress in scraping
|
|
||||||
Zotero_Browser.progress = new Zotero.ProgressWindow();
|
|
||||||
|
|
||||||
Zotero_Browser.init();
|
Zotero_Browser.init();
|
|
@ -29,11 +29,16 @@
|
||||||
var ZoteroOverlay = new function()
|
var ZoteroOverlay = new function()
|
||||||
{
|
{
|
||||||
const DEFAULT_ZPANE_HEIGHT = 300;
|
const DEFAULT_ZPANE_HEIGHT = 300;
|
||||||
var toolbarCollapseState, isFx36, showInPref;
|
var toolbarCollapseState, isFx36, showInPref;
|
||||||
|
var zoteroPane, zoteroSplitter;
|
||||||
|
var _stateBeforeReload = false;
|
||||||
|
|
||||||
this.isTab = false;
|
this.isTab = false;
|
||||||
|
|
||||||
this.onLoad = function() {
|
this.onLoad = function() {
|
||||||
|
zoteroPane = document.getElementById('zotero-pane-stack');
|
||||||
|
zoteroSplitter = document.getElementById('zotero-splitter');
|
||||||
|
|
||||||
ZoteroPane_Overlay = ZoteroPane;
|
ZoteroPane_Overlay = ZoteroPane;
|
||||||
ZoteroPane.init();
|
ZoteroPane.init();
|
||||||
|
|
||||||
|
@ -141,6 +146,19 @@ var ZoteroOverlay = new function()
|
||||||
if(Zotero.isFx4) {
|
if(Zotero.isFx4) {
|
||||||
XULBrowserWindow.inContentWhitelist.push("chrome://zotero/content/tab.xul");
|
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() {
|
this.onUnload = function() {
|
||||||
|
@ -158,10 +176,16 @@ var ZoteroOverlay = new function()
|
||||||
*/
|
*/
|
||||||
this.toggleDisplay = function(makeVisible)
|
this.toggleDisplay = function(makeVisible)
|
||||||
{
|
{
|
||||||
if(this.isTab && (makeVisible || makeVisible === undefined)) {
|
if(makeVisible || makeVisible === undefined) {
|
||||||
// If in separate tab mode, just open the tab
|
if(Zotero.isConnector) {
|
||||||
this.loadZoteroTab();
|
// If in connector mode, bring Zotero Standalone to foreground
|
||||||
return;
|
Zotero.activateStandalone();
|
||||||
|
return;
|
||||||
|
} else if(this.isTab) {
|
||||||
|
// If in separate tab mode, just open the tab
|
||||||
|
this.loadZoteroTab();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Zotero || !Zotero.initialized) {
|
if(!Zotero || !Zotero.initialized) {
|
||||||
|
@ -169,12 +193,7 @@ var ZoteroOverlay = new function()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var zoteroPane = document.getElementById('zotero-pane-stack');
|
if(makeVisible === undefined) makeVisible = zoteroPane.hidden || zoteroPane.collapsed;
|
||||||
var zoteroSplitter = document.getElementById('zotero-splitter');
|
|
||||||
var isHidden = zoteroPane.getAttribute('hidden') == 'true';
|
|
||||||
var isCollapsed = zoteroPane.getAttribute('collapsed') == 'true';
|
|
||||||
|
|
||||||
if(makeVisible === undefined) makeVisible = isHidden || isCollapsed;
|
|
||||||
|
|
||||||
zoteroSplitter.setAttribute('hidden', !makeVisible);
|
zoteroSplitter.setAttribute('hidden', !makeVisible);
|
||||||
zoteroPane.setAttribute('hidden', false);
|
zoteroPane.setAttribute('hidden', false);
|
||||||
|
|
|
@ -402,7 +402,7 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
|
||||||
Zotero.Browser.deleteHiddenBrowser(me._hiddenBrowser);
|
Zotero.Browser.deleteHiddenBrowser(me._hiddenBrowser);
|
||||||
me._callback(item);
|
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(); });
|
translate.setHandler("done", function(translate, success) { if(!success) me._queryGoogle(); });
|
||||||
|
|
||||||
this._hiddenBrowser.addEventListener("pageshow", function() { me._scrape(translate) }, true);
|
this._hiddenBrowser.addEventListener("pageshow", function() { me._scrape(translate) }, true);
|
||||||
|
@ -449,10 +449,12 @@ Zotero_RecognizePDF.Recognizer.prototype._scrape = function(/**Zotero.Translate*
|
||||||
* @private
|
* @private
|
||||||
* @type Object
|
* @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) {
|
for(var i in items) {
|
||||||
var obj = {};
|
var obj = {};
|
||||||
obj[i] = items;
|
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 *****
|
***** END LICENSE BLOCK *****
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Zotero.Translate.Item = {
|
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
|
||||||
"saveItem":function (translate, item) {
|
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);
|
.getService(Components.interfaces.nsIStringBundleService).createBundle(src, appLocale);
|
||||||
|
|
||||||
_months = {"short":[], "long":[]};
|
_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.short.push(bundle.GetStringFromName("month."+i+".Mmm"));
|
||||||
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
|
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
|
||||||
}
|
}
|
||||||
dataset.push(row);
|
dataset.push(row);
|
||||||
}
|
}
|
||||||
statement.reset();
|
statement.finalize();
|
||||||
|
|
||||||
return dataset.length ? dataset : false;
|
return dataset.length ? dataset : false;
|
||||||
}
|
}
|
||||||
|
@ -170,12 +170,12 @@ Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
|
||||||
|
|
||||||
// No rows
|
// No rows
|
||||||
if (!statement.executeStep()) {
|
if (!statement.executeStep()) {
|
||||||
statement.reset();
|
statement.finalize();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = this._getTypedValue(statement, 0);
|
var value = this._getTypedValue(statement, 0);
|
||||||
statement.reset();
|
statement.finalize();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
|
||||||
while (statement.executeStep()) {
|
while (statement.executeStep()) {
|
||||||
column.push(this._getTypedValue(statement, 0));
|
column.push(this._getTypedValue(statement, 0));
|
||||||
}
|
}
|
||||||
statement.reset();
|
statement.finalize();
|
||||||
return column.length ? column : false;
|
return column.length ? column : false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -630,7 +630,7 @@ Zotero.DBConnection.prototype.getColumns = function (table) {
|
||||||
for (var i=0,len=statement.columnCount; i<len; i++) {
|
for (var i=0,len=statement.columnCount; i<len; i++) {
|
||||||
cols.push(statement.getColumnName(i));
|
cols.push(statement.getColumnName(i));
|
||||||
}
|
}
|
||||||
statement.reset();
|
statement.finalize();
|
||||||
return cols;
|
return cols;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
@ -771,8 +771,11 @@ Zotero.DBConnection.prototype.checkException = function (e) {
|
||||||
|
|
||||||
|
|
||||||
Zotero.DBConnection.prototype.closeDatabase = function () {
|
Zotero.DBConnection.prototype.closeDatabase = function () {
|
||||||
var db = this._getDBConnection();
|
if(this._connection) {
|
||||||
db.close();
|
this.stopDummyStatement();
|
||||||
|
this._connection.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -855,10 +858,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
|
||||||
var hadDummyStatement = !!this._dummyStatement;
|
var hadDummyStatement = !!this._dummyStatement;
|
||||||
try {
|
try {
|
||||||
if (dbLockExclusive) {
|
if (dbLockExclusive) {
|
||||||
Zotero.DB.query("PRAGMA locking_mode=NORMAL");
|
this.query("PRAGMA locking_mode=NORMAL");
|
||||||
}
|
}
|
||||||
if (hadDummyStatement) {
|
if (hadDummyStatement) {
|
||||||
Zotero.DB.stopDummyStatement();
|
this.stopDummyStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = Components.classes["@mozilla.org/storage/service;1"].
|
var store = Components.classes["@mozilla.org/storage/service;1"].
|
||||||
|
@ -872,10 +875,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (dbLockExclusive) {
|
if (dbLockExclusive) {
|
||||||
Zotero.DB.query("PRAGMA locking_mode=EXCLUSIVE");
|
this.query("PRAGMA locking_mode=EXCLUSIVE");
|
||||||
}
|
}
|
||||||
if (hadDummyStatement) {
|
if (hadDummyStatement) {
|
||||||
Zotero.DB.startDummyStatement();
|
this.startDummyStatement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1003,8 +1006,10 @@ Zotero.DBConnection.prototype.stopDummyStatement = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Stopping dummy statement for '" + this._dbName + "'");
|
Zotero.debug("Stopping dummy statement for '" + this._dbName + "'");
|
||||||
this._dummyStatement.reset();
|
this._dummyStatement.finalize();
|
||||||
this._dummyStatement = null;
|
this._dummyConnection.close();
|
||||||
|
delete this._dummyConnection;
|
||||||
|
delete this._dummyStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
|
|
||||||
|
|
||||||
Zotero.Debug = new function () {
|
Zotero.Debug = new function () {
|
||||||
this.__defineGetter__('storing', function () _store);
|
this.__defineGetter__('storing', function () { return _store; });
|
||||||
this.__defineGetter__('enabled', function () _console || _store);
|
this.__defineGetter__('enabled', function () { return _console || _store; });
|
||||||
|
|
||||||
var _console;
|
var _console;
|
||||||
var _store;
|
var _store;
|
||||||
|
@ -81,7 +81,12 @@ Zotero.Debug = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_console) {
|
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 (_store) {
|
||||||
if (Math.random() < 1/1000) {
|
if (Math.random() < 1/1000) {
|
||||||
|
|
|
@ -31,7 +31,6 @@ const DATA_VERSION = 3;
|
||||||
// this is used only for update checking
|
// this is used only for update checking
|
||||||
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
|
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
|
||||||
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
|
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
|
||||||
const INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
|
|
||||||
|
|
||||||
Zotero.Integration = new function() {
|
Zotero.Integration = new function() {
|
||||||
var _fifoFile = null;
|
var _fifoFile = null;
|
||||||
|
@ -39,8 +38,7 @@ Zotero.Integration = new function() {
|
||||||
var _osascriptFile;
|
var _osascriptFile;
|
||||||
var _inProgress = false;
|
var _inProgress = false;
|
||||||
var _integrationVersionsOK = null;
|
var _integrationVersionsOK = null;
|
||||||
var _pipeMode = false;
|
var INTEGRATION_MIN_VERSIONS;
|
||||||
var _winUser32;
|
|
||||||
|
|
||||||
// these need to be global because of GC
|
// these need to be global because of GC
|
||||||
var _timer;
|
var _timer;
|
||||||
|
@ -52,29 +50,32 @@ Zotero.Integration = new function() {
|
||||||
* Initializes the pipe used for integration on non-Windows platforms.
|
* Initializes the pipe used for integration on non-Windows platforms.
|
||||||
*/
|
*/
|
||||||
this.init = function() {
|
this.init = function() {
|
||||||
// initialize SOAP server just to throw version errors
|
if(Zotero.isMac || Zotero.isWin) { // on Mac or Windows, we don't have pipe issues
|
||||||
Zotero.Integration.Compat.init();
|
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.
|
// components/zotero-integration-service.js for this implementation.
|
||||||
if(Zotero.isWin) return;
|
if(!Zotero.isMac) return;
|
||||||
|
|
||||||
// Determine where to put the pipe
|
// 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
|
||||||
// on OS X, first try /Users/Shared for those who can't put pipes in their home
|
// directories
|
||||||
// directories
|
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
|
||||||
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
|
createInstance(Components.interfaces.nsILocalFile);
|
||||||
createInstance(Components.interfaces.nsILocalFile);
|
_fifoFile.initWithPath("/Users/Shared");
|
||||||
_fifoFile.initWithPath("/Users/Shared");
|
|
||||||
|
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
|
||||||
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
|
var logname = Components.classes["@mozilla.org/process/environment;1"].
|
||||||
var logname = Components.classes["@mozilla.org/process/environment;1"].
|
getService(Components.interfaces.nsIEnvironment).
|
||||||
getService(Components.interfaces.nsIEnvironment).
|
get("LOGNAME");
|
||||||
get("LOGNAME");
|
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
|
||||||
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
|
} else {
|
||||||
} else {
|
_fifoFile = null;
|
||||||
_fifoFile = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_fifoFile) {
|
if(!_fifoFile) {
|
||||||
|
@ -85,8 +86,6 @@ Zotero.Integration = new function() {
|
||||||
_fifoFile.append(".zoteroIntegrationPipe");
|
_fifoFile.append(".zoteroIntegrationPipe");
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.debug("Initializing Zotero integration pipe at "+_fifoFile.path);
|
|
||||||
|
|
||||||
// destroy old pipe, if one exists
|
// destroy old pipe, if one exists
|
||||||
try {
|
try {
|
||||||
if(_fifoFile.exists()) {
|
if(_fifoFile.exists()) {
|
||||||
|
@ -100,35 +99,27 @@ Zotero.Integration = new function() {
|
||||||
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
|
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
|
||||||
+ "for instructions on correcting this problem."
|
+ "for instructions on correcting this problem."
|
||||||
);
|
);
|
||||||
if(Zotero.isMac) {
|
|
||||||
// can attempt to delete on OS X
|
// can attempt to delete on OS X
|
||||||
try {
|
try {
|
||||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||||
.getService(Components.interfaces.nsIPromptService);
|
.getService(Components.interfaces.nsIPromptService);
|
||||||
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
|
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
|
||||||
if(!deletePipe) return;
|
if(!deletePipe) return;
|
||||||
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
|
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
|
||||||
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
|
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
|
||||||
if(_fifoFile.exists()) return;
|
if(_fifoFile.exists()) return;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Zotero.logError(e);
|
Zotero.logError(e);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to initialize pipe
|
// try to initialize pipe
|
||||||
try {
|
try {
|
||||||
var pipeInitialized = _initializeIntegrationPipe();
|
Zotero.IPC.Pipe.initPipeListener(_fifoFile, _parseIntegrationPipeCommand);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Components.utils.reportError(e);
|
Zotero.logError(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateTimer = Components.classes["@mozilla.org/timer;1"].
|
_updateTimer = Components.classes["@mozilla.org/timer;1"].
|
||||||
|
@ -210,246 +201,14 @@ Zotero.Integration = new function() {
|
||||||
if(parts) {
|
if(parts) {
|
||||||
var agent = parts[1].toString();
|
var agent = parts[1].toString();
|
||||||
var cmd = parts[2].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;
|
var document = parts[3] ? parts[3].toString() : null;
|
||||||
Zotero.Integration.execCommand(agent, cmd, document);
|
Zotero.Integration.execCommand(agent, cmd, document);
|
||||||
} else {
|
} else {
|
||||||
_initializePipeStreamPump();
|
|
||||||
Components.utils.reportError("Zotero: Invalid integration input received: "+string);
|
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
|
* 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
|
* 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() {
|
var _Observer = new function() {
|
||||||
this.observe = function(channel) {
|
this.observe = function(channel) {
|
||||||
|
if(Zotero.isConnector) return;
|
||||||
|
|
||||||
channel.QueryInterface(Components.interfaces.nsIRequest);
|
channel.QueryInterface(Components.interfaces.nsIRequest);
|
||||||
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
|
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
|
||||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||||
|
@ -222,6 +224,7 @@ Zotero.MIMETypeHandler = new function () {
|
||||||
* Called to see if we can handle a content type
|
* Called to see if we can handle a content type
|
||||||
*/
|
*/
|
||||||
this.canHandleContent = this.isPreferred = function(contentType, isContentPreferred, desiredContentType) {
|
this.canHandleContent = this.isPreferred = function(contentType, isContentPreferred, desiredContentType) {
|
||||||
|
if(Zotero.isConnector) return false;
|
||||||
return !!_typeHandlers[contentType.toLowerCase()];
|
return !!_typeHandlers[contentType.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,19 +54,12 @@ onmessage = function(event) {
|
||||||
|
|
||||||
// extract message
|
// extract message
|
||||||
var string = buf.readString();
|
var string = buf.readString();
|
||||||
var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
|
if(string === "Zotero shutdown\n") {
|
||||||
if(!parts) {
|
postMessage(["Debug", "IPC: Worker closing "+event.data.path]);
|
||||||
postMessage(["Exception", "Integration Worker: Invalid input received: "+string]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var agent = parts[1].toString();
|
|
||||||
var cmd = parts[2].toString();
|
|
||||||
var document = parts[3] ? parts[3] : null;
|
|
||||||
if(agent == "Zotero" && cmd == "shutdown") {
|
|
||||||
postMessage(["Debug", "Integration Worker: Shutting down"]);
|
|
||||||
lib.close();
|
lib.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
postMessage([agent, cmd, document]);
|
|
||||||
|
postMessage(["Read", string]);
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -480,8 +480,6 @@ const Zotero_Proxy_schemeParameterRegexps = {
|
||||||
"%a":/([^%])%a/
|
"%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
|
* Compiles the regular expression against which we match URLs to determine if this proxy is in use
|
||||||
* and saves it in this.regexp
|
* and saves it in this.regexp
|
||||||
|
@ -514,7 +512,7 @@ Zotero.Proxy.prototype.compileRegexp = function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// now replace with regexp fragment in reverse order
|
// 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--) {
|
for(var i=this.parameters.length-1; i>=0; i--) {
|
||||||
var param = this.parameters[i];
|
var param = this.parameters[i];
|
||||||
re = re.replace(Zotero_Proxy_schemeParameterRegexps[param], "$1"+parametersToCheck[param]);
|
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];
|
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
|
// 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.autoAssociate = this.multiHost && this.autoAssociate;
|
||||||
this.compileRegexp();
|
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
|
* @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
|
* New code should use Zotero.Translate.Web, Zotero.Translate.Import, Zotero.Translate.Export, or
|
||||||
* Zotero.Translate.Search
|
* Zotero.Translate.Search
|
||||||
*/
|
*/
|
||||||
Zotero.Translate = function(type) {
|
Zotero.Translate = function(type) {
|
||||||
Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to");
|
Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to");
|
||||||
// hack
|
// hack
|
||||||
var translate = Zotero.Translate.new(type);
|
var translate = Zotero.Translate.newInstance(type);
|
||||||
for(var i in translate) {
|
for(var i in translate) {
|
||||||
this[i] = translate[i];
|
this[i] = translate[i];
|
||||||
}
|
}
|
||||||
|
@ -43,10 +44,14 @@ Zotero.Translate = function(type) {
|
||||||
/**
|
/**
|
||||||
* Create a new translator by a string 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()];
|
return new Zotero.Translate[type[0].toUpperCase()+type.substr(1).toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for Zotero sandboxes
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
Zotero.Translate.Sandbox = {
|
Zotero.Translate.Sandbox = {
|
||||||
/**
|
/**
|
||||||
* Combines a sandbox with the base sandbox
|
* Combines a sandbox with the base sandbox
|
||||||
|
@ -67,10 +72,11 @@ Zotero.Translate.Sandbox = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base sandbox. These methods are available to all translators.
|
* Base sandbox. These methods are available to all translators.
|
||||||
|
* @namespace
|
||||||
*/
|
*/
|
||||||
"Base": {
|
"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 {Zotero.Translate} translate
|
||||||
* @param {SandboxItem} An item created using the Zotero.Item class from the sandbox
|
* @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
|
// just return the item array
|
||||||
if(translate._libraryID === false || translate._parentTranslator) {
|
if(translate._libraryID === false || translate._parentTranslator) {
|
||||||
translate.newItems.push(item);
|
translate.newItems.push(item);
|
||||||
translate._runHandler("itemDone", item);
|
translate._runHandler("itemDone", item, item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +105,8 @@ Zotero.Translate.Sandbox = {
|
||||||
Zotero.wait();
|
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");
|
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;
|
translation._parentTranslator = translate;
|
||||||
|
|
||||||
if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) {
|
if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) {
|
||||||
throw("Translate: only export translators may call other export translators");
|
throw("Translate: only export translators may call other export translators");
|
||||||
}
|
}
|
||||||
|
|
||||||
// for security reasons, safeTranslator wraps the translator object.
|
/**
|
||||||
// note that setLocation() is not allowed
|
* @class Wrapper for {@link Zotero.Translate} for safely calling another translator
|
||||||
var safeTranslator = new Object();
|
* from inside an existing translator
|
||||||
|
* @inner
|
||||||
|
*/
|
||||||
|
var safeTranslator = {};
|
||||||
safeTranslator.__exposedProps__ = {
|
safeTranslator.__exposedProps__ = {
|
||||||
"setSearch":"r",
|
"setSearch":"r",
|
||||||
"setDocument":"r",
|
"setDocument":"r",
|
||||||
|
@ -185,43 +195,61 @@ Zotero.Translate.Sandbox = {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
safeTranslator.setString = function(arg) { translation.setString(arg) };
|
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.getTranslators = function() { return translation.getTranslators() };
|
||||||
safeTranslator.translate = function() {
|
safeTranslator.translate = function() {
|
||||||
setDefaultHandlers(translate, translation);
|
setDefaultHandlers(translate, translation);
|
||||||
return translation.translate(false);
|
return translation.translate(false);
|
||||||
};
|
};
|
||||||
|
// TODO
|
||||||
safeTranslator.getTranslatorObject = function(callback) {
|
safeTranslator.getTranslatorObject = function(callback) {
|
||||||
translation._loadTranslator(translation.translator[0]);
|
var haveTranslatorFunction = function(translator) {
|
||||||
|
translation.translator[0] = translator;
|
||||||
if(Zotero.isFx) {
|
if(!Zotero._loadTranslator(translator)) throw "Translator could not be loaded";
|
||||||
// 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" ?
|
if(Zotero.isFx) {
|
||||||
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
|
// do same origin check
|
||||||
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
|
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
||||||
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
|
.getService(Components.interfaces.nsIScriptSecurityManager);
|
||||||
Zotero.debug(outerSandboxURI.spec);
|
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||||
Zotero.debug(innerSandboxURI.spec);
|
.getService(Components.interfaces.nsIIOService);
|
||||||
|
|
||||||
try {
|
var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
|
||||||
secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
|
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
|
||||||
} catch(e) {
|
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
|
||||||
throw "Translate: getTranslatorObject() may not be called from web or search "+
|
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
|
||||||
"translators to web or search translators from different origins.";
|
|
||||||
|
try {
|
||||||
|
secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
|
||||||
|
} catch(e) {
|
||||||
|
throw "Translate: getTranslatorObject() may not be called from web or search "+
|
||||||
|
"translators to web or search translators from different origins.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
translation._prepareTranslation();
|
||||||
|
setDefaultHandlers(translate, translation);
|
||||||
|
|
||||||
|
if(callback) callback(translation._sandboxManager.sandbox);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(typeof translation.translator[0] === "object") {
|
||||||
|
haveTranslatorFunction(translation.translator[0]);
|
||||||
|
return translation._sandboxManager.sandbox;
|
||||||
|
} else {
|
||||||
|
if(Zotero.isConnector && !callback) {
|
||||||
|
throw "Translate: Translator must accept a callback to getTranslatorObject() to "+
|
||||||
|
"operate in this translation environment.";
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.Translators.get(translation.translator[0], haveTranslatorFunction);
|
||||||
|
if(!Zotero.isConnector) return translation._sandboxManager.sandbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
translation._prepareTranslation();
|
|
||||||
setDefaultHandlers(translate, translation);
|
|
||||||
|
|
||||||
// return sandbox
|
|
||||||
if(callback) callback(translation._sandboxManager.sandbox);
|
|
||||||
return translation._sandboxManager.sandbox;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO security is not super-tight here, as someone could pass something into arg
|
// 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
|
* Lets user pick which items s/he wants to put in his/her library
|
||||||
* @param {Zotero.Translate} translate
|
* @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) {
|
"selectItems":function(translate, items, callback) {
|
||||||
// hack to see if there are options
|
if(Zotero.Utilities.isEmpty(items)) {
|
||||||
var haveOptions = false;
|
|
||||||
for(var i in options) {
|
|
||||||
haveOptions = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!haveOptions) {
|
|
||||||
throw "Translate: translator called select items with no items";
|
throw "Translate: translator called select items with no items";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(translate._handlers.select) {
|
if(translate._selectedItems) {
|
||||||
options = translate._runHandler("select", options);
|
// 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);
|
if(callback) callback(options);
|
||||||
|
@ -378,7 +441,7 @@ Zotero.Translate.Sandbox = {
|
||||||
"Import":{
|
"Import":{
|
||||||
/**
|
/**
|
||||||
* Saves a collection to the DB
|
* 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 {Zotero.Translate} translate
|
||||||
* @param {SandboxCollection} collection
|
* @param {SandboxCollection} collection
|
||||||
*/
|
*/
|
||||||
|
@ -520,7 +583,7 @@ Zotero.Translate.Base.prototype = {
|
||||||
throw("No translatorID specified");
|
throw("No translatorID specified");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.translator = [Zotero.Translators.get(translator)];
|
this.translator = [translator];
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!this.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 {String} type See {@link Zotero.Translate.Base#setHandler} for valid values
|
||||||
* @param {Any} argument Argument to be passed to handler
|
* @param {Any} argument Argument to be passed to handler
|
||||||
*/
|
*/
|
||||||
"_runHandler":function(type, argument) {
|
"_runHandler":function(type) {
|
||||||
var returnValue = undefined;
|
var returnValue = undefined;
|
||||||
if(this._handlers[type]) {
|
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]) {
|
for(var i in this._handlers[type]) {
|
||||||
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
|
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
|
||||||
try {
|
try {
|
||||||
if(this._parentTranslator) {
|
returnValue = this._handlers[type][i].apply(null, args);
|
||||||
returnValue = this._handlers[type][i](null, argument);
|
|
||||||
} else {
|
|
||||||
returnValue = this._handlers[type][i](this, argument);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(this._parentTranslator) {
|
if(this._parentTranslator) {
|
||||||
// throw handler errors if they occur when a translator is
|
// 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";
|
if(this._currentState == "detect") throw "Translate: getTranslators: detection is already running";
|
||||||
this._currentState = "detect";
|
this._currentState = "detect";
|
||||||
this._getAllTranslators = getAllTranslators;
|
this._getAllTranslators = getAllTranslators;
|
||||||
this._potentialTranslators = this._getPotentialTranslators();
|
this._getTranslatorsGetPotentialTranslators();
|
||||||
this._foundTranslators = [];
|
|
||||||
|
|
||||||
Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3);
|
|
||||||
|
|
||||||
this._detect();
|
|
||||||
|
|
||||||
// if detection returns immediately, return found translators
|
// if detection returns immediately, return found translators
|
||||||
if(!this._currentState) return this._foundTranslators;
|
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
|
* 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
|
* if FALSE, don't save items
|
||||||
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
|
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
|
||||||
*/
|
*/
|
||||||
"translate":function(libraryID, saveAttachments) {
|
"translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
|
||||||
// initialize properties specific to each translation
|
|
||||||
this._currentState = "translate";
|
this._currentState = "translate";
|
||||||
|
|
||||||
if(!this.translator || !this.translator.length) {
|
if(!this.translator || !this.translator.length) {
|
||||||
throw("Translate: Failed: no translator specified");
|
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
|
// load translators
|
||||||
if(!this._loadTranslator(this.translator[0])) return;
|
if(!this._loadTranslator(this.translator[0])) return;
|
||||||
|
|
||||||
|
@ -671,15 +819,13 @@ Zotero.Translate.Base.prototype = {
|
||||||
if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions;
|
if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions;
|
||||||
|
|
||||||
// prepare translation
|
// prepare translation
|
||||||
this._libraryID = libraryID;
|
|
||||||
this._saveAttachments = typeof saveAttachments === "undefined" ? true : saveAttachments;
|
|
||||||
this._prepareTranslation();
|
this._prepareTranslation();
|
||||||
|
|
||||||
Zotero.debug("Translate: Beginning translation with "+this.translator[0].label);
|
Zotero.debug("Translate: Beginning translation with "+this.translator[0].label);
|
||||||
|
|
||||||
// translate
|
// translate
|
||||||
try {
|
try {
|
||||||
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(this.null, this._getParameters());
|
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(null, this._getParameters());
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(this._parentTranslator) {
|
if(this._parentTranslator) {
|
||||||
throw(e);
|
throw(e);
|
||||||
|
@ -715,9 +861,10 @@ Zotero.Translate.Base.prototype = {
|
||||||
if(oldState === "detect") {
|
if(oldState === "detect") {
|
||||||
if(this._potentialTranslators.length) {
|
if(this._potentialTranslators.length) {
|
||||||
var lastTranslator = this._potentialTranslators.shift();
|
var lastTranslator = this._potentialTranslators.shift();
|
||||||
|
var lastProperToProxyFunction = this._properToProxyFunctions ? this._properToProxyFunctions.shift() : null;
|
||||||
|
|
||||||
if(returnValue) {
|
if(returnValue) {
|
||||||
var dupeTranslator = {"itemType":returnValue};
|
var dupeTranslator = {"itemType":returnValue, "properToProxy":lastProperToProxyFunction};
|
||||||
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
|
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
|
||||||
this._foundTranslators.push(dupeTranslator);
|
this._foundTranslators.push(dupeTranslator);
|
||||||
} else if(error) {
|
} else if(error) {
|
||||||
|
@ -730,7 +877,7 @@ Zotero.Translate.Base.prototype = {
|
||||||
this._detect();
|
this._detect();
|
||||||
} else {
|
} else {
|
||||||
this._currentState = null;
|
this._currentState = null;
|
||||||
this._runHandler("translators", this._foundTranslators ? this._foundTranslators : false);
|
if(!this._waitingForRPC) this._detectTranslatorsCollected();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._currentState = null;
|
this._currentState = null;
|
||||||
|
@ -762,6 +909,12 @@ Zotero.Translate.Base.prototype = {
|
||||||
* Runs detect code for a translator
|
* Runs detect code for a translator
|
||||||
*/
|
*/
|
||||||
"_detect":function() {
|
"_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])) {
|
if(!this._loadTranslator(this._potentialTranslators[0])) {
|
||||||
this.complete(false, "Error loading translator into sandbox");
|
this.complete(false, "Error loading translator into sandbox");
|
||||||
return;
|
return;
|
||||||
|
@ -778,6 +931,15 @@ Zotero.Translate.Base.prototype = {
|
||||||
if(!this._waitForCompletion) this.complete(returnValue);
|
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
|
* Loads the translator into its sandbox
|
||||||
* @param {Zotero.Translator} translator
|
* @param {Zotero.Translator} translator
|
||||||
|
@ -795,7 +957,6 @@ Zotero.Translate.Base.prototype = {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox);
|
this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox);
|
||||||
return true;
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(translator.logError) {
|
if(translator.logError) {
|
||||||
translator.logError(e.toString());
|
translator.logError(e.toString());
|
||||||
|
@ -815,13 +976,14 @@ Zotero.Translate.Base.prototype = {
|
||||||
*/
|
*/
|
||||||
"_generateSandbox":function() {
|
"_generateSandbox":function() {
|
||||||
Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4);
|
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']";
|
const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']";
|
||||||
var src = "var Zotero = {};"+
|
var src = "var Zotero = {};"+
|
||||||
"Zotero.Item = function (itemType) {"+
|
"Zotero.Item = function (itemType) {"+
|
||||||
|
"const createArrays = "+createArrays+";"+
|
||||||
"this.itemType = itemType;"+
|
"this.itemType = itemType;"+
|
||||||
"for each(var array in "+createArrays+") {"+
|
"for(var i in createArrays) {"+
|
||||||
"this[array] = [];"+
|
"this[createArrays[i]] = [];"+
|
||||||
"}"+
|
"}"+
|
||||||
"};"+
|
"};"+
|
||||||
"Zotero.Collection = function () {};"+
|
"Zotero.Collection = function () {};"+
|
||||||
|
@ -927,17 +1089,11 @@ Zotero.Translate.Base.prototype = {
|
||||||
* No-op for preparing translation
|
* No-op for preparing translation
|
||||||
*/
|
*/
|
||||||
"_prepareTranslation":function() {},
|
"_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 {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
|
* @property {Zotero.Connector.CookieManager} cookieManager A CookieManager to manage cookies for
|
||||||
* this Translate instance.
|
* this Translate instance.
|
||||||
|
@ -975,30 +1131,21 @@ Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) {
|
||||||
* @param {String} location The URL of the page to translate
|
* @param {String} location The URL of the page to translate
|
||||||
*/
|
*/
|
||||||
Zotero.Translate.Web.prototype.setLocation = function(location) {
|
Zotero.Translate.Web.prototype.setLocation = function(location) {
|
||||||
// account for proxies
|
this.location = location;
|
||||||
this.location = Zotero.Proxies.proxyToProper(location);
|
|
||||||
if(this.location != location) {
|
|
||||||
// figure out if this URL is being proxies
|
|
||||||
this.locationIsProxied = true;
|
|
||||||
}
|
|
||||||
this.path = this.location;
|
this.path = this.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all potential translators
|
* Get potential web translators
|
||||||
*/
|
*/
|
||||||
Zotero.Translate.Web.prototype._getPotentialTranslators = function() {
|
Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() {
|
||||||
var allTranslators = Zotero.Translators.getAllForType("web");
|
var me = this;
|
||||||
var potentialTranslators = [];
|
Zotero.Translators.getWebTranslatorsForLocation(this.location,
|
||||||
|
function(data) {
|
||||||
Zotero.debug("Translate: Running regular expressions");
|
// data[0] = list of translators
|
||||||
for(var i=0; i<allTranslators.length; i++) {
|
// data[1] = list of functions to convert proper URIs to proxied URIs
|
||||||
if(!allTranslators[i].webRegexp || (this.location.length < 8192 && allTranslators[i].webRegexp.test(this.location))) {
|
me._getTranslatorsTranslatorsReceived(data[0], data[1]);
|
||||||
potentialTranslators.push(allTranslators[i]);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialTranslators;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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) {
|
Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
|
||||||
// call super
|
// call super
|
||||||
var oldState = this._currentState;
|
var oldState = this._currentState;
|
||||||
var errorString = Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]);
|
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")) {
|
if(oldState == "translate" && errorString && this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) {
|
||||||
// Don't report failure if in private browsing mode
|
// Don't report failure if in private browsing mode
|
||||||
if(Zotero.isFx && !Zotero.isStandalone) {
|
if(Zotero.isFx && !Zotero.isStandalone) {
|
||||||
|
@ -1049,6 +1249,9 @@ Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Import translation
|
||||||
|
*/
|
||||||
Zotero.Translate.Import = function() {
|
Zotero.Translate.Import = function() {
|
||||||
this.init();
|
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() {
|
Zotero.Translate.Import.prototype._getTranslatorsGetPotentialTranslators = function() {
|
||||||
var allTranslators = Zotero.Translators.getAllForType("import");
|
var me = this;
|
||||||
var tier1Translators = [];
|
Zotero.Translators.getImportTranslatorsForLocation(this.location,
|
||||||
var tier2Translators = [];
|
function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overload Zotero.Translate.Base#_detect to return all translators immediately only if no string
|
* Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately only
|
||||||
* or location is set
|
* if no string or location is set
|
||||||
*/
|
*/
|
||||||
Zotero.Translate.Import.prototype._detect = function() {
|
Zotero.Translate.Import.prototype.getTranslators = function() {
|
||||||
if(!this._string && !this.location) {
|
if(!this._string && !this.location) {
|
||||||
this._foundTranslators = this._potentialTranslators;
|
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
|
||||||
this._potentialTranslators = [];
|
this._potentialTranslators = [];
|
||||||
this.complete(true);
|
this.complete(true);
|
||||||
|
return this._foundTranslators;
|
||||||
} else {
|
} 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);
|
this._sandboxManager.importObject(this._io);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare translation
|
* Prepare translation
|
||||||
|
@ -1182,6 +1376,9 @@ function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Export translation
|
||||||
|
*/
|
||||||
Zotero.Translate.Export = function() {
|
Zotero.Translate.Export = function() {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
@ -1235,12 +1432,13 @@ Zotero.Translate.Export.prototype.setDisplayOptions = function(displayOptions) {
|
||||||
Zotero.Translate.Export.prototype.complete = Zotero.Translate.Import.prototype.complete;
|
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() {
|
Zotero.Translate.Export.prototype.getTranslators = function() {
|
||||||
this._foundTranslators = this._potentialTranslators;
|
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
|
||||||
this._potentialTranslators = [];
|
this._potentialTranslators = [];
|
||||||
this.complete(true);
|
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
|
* @property {Array[]} search Item (in {@link Zotero.Item#serialize} format) to extrapolate data
|
||||||
* (set with setSearch)
|
* (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);
|
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;
|
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
|
// accept a list of objects
|
||||||
this.translator = [];
|
this.translator = [];
|
||||||
for(var i in translator) {
|
for(var i in translator) {
|
||||||
if(typeof(translator[i]) == "object") {
|
this.translator.push(translator[i]);
|
||||||
this.translator.push(translator[i]);
|
|
||||||
} else {
|
|
||||||
this.translator.push(Zotero.Translators.get(translator[i]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1436,6 +1631,9 @@ Zotero.Translate.IO = {
|
||||||
|
|
||||||
/******* String support *******/
|
/******* String support *******/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Translate backend for translating from a string
|
||||||
|
*/
|
||||||
Zotero.Translate.IO.String = function(string, uri, mode) {
|
Zotero.Translate.IO.String = function(string, uri, mode) {
|
||||||
if(string && typeof string === "string") {
|
if(string && typeof string === "string") {
|
||||||
this._string = string;
|
this._string = string;
|
||||||
|
|
|
@ -38,10 +38,9 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||||
* @param {Zotero.Translate} translate
|
* @param {Zotero.Translate} translate
|
||||||
* @param {String|window} sandboxLocation
|
* @param {String|window} sandboxLocation
|
||||||
*/
|
*/
|
||||||
Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
|
Zotero.Translate.SandboxManager = function(sandboxLocation) {
|
||||||
this.sandbox = new Components.utils.Sandbox(sandboxLocation);
|
this.sandbox = new Components.utils.Sandbox(sandboxLocation);
|
||||||
this.sandbox.Zotero = {};
|
this.sandbox.Zotero = {};
|
||||||
this._translate = translate;
|
|
||||||
|
|
||||||
// import functions missing from global scope into Fx sandbox
|
// import functions missing from global scope into Fx sandbox
|
||||||
this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
|
this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
|
||||||
|
@ -111,6 +110,7 @@ Zotero.Translate.SandboxManager.prototype = {
|
||||||
|
|
||||||
return object[localKey].apply(object, args);
|
return object[localKey].apply(object, args);
|
||||||
};
|
};
|
||||||
|
attachTo[localKey].name = localKey;
|
||||||
|
|
||||||
// attach members
|
// attach members
|
||||||
if(!(object instanceof Components.interfaces.nsISupports)) {
|
if(!(object instanceof Components.interfaces.nsISupports)) {
|
|
@ -149,18 +149,153 @@ Zotero.Translators = new function() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the translator that corresponds to a given ID
|
* 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();
|
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
|
* Gets all translators for a specific type of translation
|
||||||
*/
|
*/
|
||||||
this.getAllForType = function(type) {
|
this.getAll = function() {
|
||||||
if(!_initialized) this.init();
|
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";
|
return Zotero.File.getValidFileName(label) + ".js";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} metadata
|
* @param {String} metadata
|
||||||
* @param {String} metadata.translatorID Translator GUID
|
* @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) {
|
Zotero.Translator = function(file, json, code) {
|
||||||
const codeGetterFunction = function() { return Zotero.File.getContents(this.file); }
|
const codeGetterFunction = function() { return Zotero.File.getContents(this.file); }
|
||||||
// Maximum length for the info JSON in a translator
|
// 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._configOptions = info["configOptions"] ? info["configOptions"] : {};
|
||||||
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
|
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
|
||||||
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
||||||
|
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||||
|
|
||||||
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
||||||
// compile import regexp to match only file extension
|
// compile import regexp to match only file extension
|
||||||
|
@ -374,7 +486,6 @@ Zotero.Translator = function(file, json, code) {
|
||||||
try {
|
try {
|
||||||
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
|
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(fStream) fStream.close();
|
|
||||||
this.logError("Invalid target in " + file.leafName);
|
this.logError("Invalid target in " + file.leafName);
|
||||||
this.webRegexp = null;
|
this.webRegexp = null;
|
||||||
if(fStream) fStream.close();
|
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"].
|
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||||
getService(Components.interfaces.nsIIOService);
|
getService(Components.interfaces.nsIIOService);
|
||||||
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec);
|
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) {
|
} catch(e) {
|
||||||
return false;
|
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
|
* @ignore
|
||||||
*/
|
*/
|
||||||
Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) {
|
Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) {
|
||||||
if(this._translate.locationIsProxied) {
|
if(typeof(urls) == "string") {
|
||||||
if(typeof(urls) == "string") {
|
urls = [this._convertURL(urls)];
|
||||||
urls = [this._convertURL(urls)];
|
} else {
|
||||||
} else {
|
for(var i in urls) {
|
||||||
for(var i in urls) {
|
urls[i] = this._convertURL(urls[i]);
|
||||||
urls[i] = this._convertURL(urls[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,7 +785,7 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
|
||||||
* @return {Document} DOM document object
|
* @return {Document} DOM document object
|
||||||
*/
|
*/
|
||||||
Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
|
Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
|
||||||
if(this._translate.locationIsProxied) url = this._convertURL(url);
|
url = this._convertURL(url);
|
||||||
|
|
||||||
var mainThread = Zotero.mainThread;
|
var mainThread = Zotero.mainThread;
|
||||||
var loaded = false;
|
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
|
/* 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
|
* in FF3.5, synchronous XHR blocks all JavaScript on the thread. See
|
||||||
* http://hacks.mozilla.org/2009/07/synchronous-xhr/. */
|
* 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(!headers) headers = null;
|
||||||
if(!responseCharset) responseCharset = 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) {
|
Zotero.Utilities.Translate.prototype._convertURL = function(url) {
|
||||||
const protocolRe = /^(?:(?:http|https|ftp):)/i;
|
const protocolRe = /^(?:(?:http|https|ftp):)/i;
|
||||||
const fileRe = /^[^:]*/;
|
|
||||||
|
|
||||||
if(this._translate.locationIsProxied) {
|
// convert proxy to proper if applicable
|
||||||
url = Zotero.Proxies.properToProxy(url);
|
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)) {
|
if(Zotero.isChrome || Zotero.isSafari) {
|
||||||
throw "Invalid URL supplied for HTTP request";
|
// this code is sandboxed, so we don't worry
|
||||||
|
return url;
|
||||||
} else {
|
} 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"].
|
return Components.classes["@mozilla.org/network/io-service;1"].
|
||||||
getService(Components.interfaces.nsIIOService).
|
getService(Components.interfaces.nsIIOService).
|
||||||
newURI(this._translate.location, "", null).resolve(url);
|
newURI(this._translate.location, "", null).resolve(url);
|
||||||
|
|
|
@ -36,6 +36,8 @@ const ZOTERO_CONFIG = {
|
||||||
PREF_BRANCH: 'extensions.zotero.'
|
PREF_BRANCH: 'extensions.zotero.'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ZOTERO_METAREGEXP = /[-[\]{}()*+?.\\^$|,#\s]/g;
|
||||||
|
|
||||||
// Fx4.0b8+ use implicit SJOWs and get rid of explicit XPCSafeJSObjectWrapper constructor
|
// 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
|
// Ugly hack to get around this until we can just kill the XPCSafeJSObjectWrapper calls (when we
|
||||||
// drop Fx3.6 support)
|
// drop Fx3.6 support)
|
||||||
|
@ -45,10 +47,17 @@ try {
|
||||||
eval("var XPCSafeJSObjectWrapper = function(arg) { return arg }");
|
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
|
* Core functions
|
||||||
*/
|
*/
|
||||||
var Zotero = new function(){
|
(function(){
|
||||||
// Privileged (public) methods
|
// Privileged (public) methods
|
||||||
this.init = init;
|
this.init = init;
|
||||||
this.stateCheck = stateCheck;
|
this.stateCheck = stateCheck;
|
||||||
|
@ -173,34 +182,39 @@ var Zotero = new function(){
|
||||||
|
|
||||||
var _locked;
|
var _locked;
|
||||||
var _unlockCallbacks = [];
|
var _unlockCallbacks = [];
|
||||||
|
var _shutdownListeners = [];
|
||||||
var _progressMeters;
|
var _progressMeters;
|
||||||
var _lastPercentage;
|
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
|
* A set of nsITimerCallbacks to be executed when Zotero.wait() completes
|
||||||
*/
|
*/
|
||||||
var _waitTimerCallbacks = [];
|
var _waitTimerCallbacks = [];
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Initialize the extension
|
* Initialize the extension
|
||||||
*/
|
*/
|
||||||
function init(){
|
function init() {
|
||||||
if (this.initialized || this.skipLoading) {
|
if (this.initialized || this.skipLoading) {
|
||||||
return false;
|
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"]
|
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||||
.getService(Components.interfaces.nsIObserverService);
|
.getService(Components.interfaces.nsIObserverService);
|
||||||
observerService.addObserver({
|
|
||||||
observe: Zotero.shutdown
|
|
||||||
}, "quit-application", false);
|
|
||||||
|
|
||||||
// Load in the preferences branch for the extension
|
// Load in the preferences branch for the extension
|
||||||
Zotero.Prefs.init();
|
Zotero.Prefs.init();
|
||||||
|
|
||||||
Zotero.Debug.init();
|
Zotero.Debug.init();
|
||||||
|
|
||||||
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
|
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
|
||||||
|
@ -214,6 +228,7 @@ var Zotero = new function(){
|
||||||
this.isFx31 = this.isFx35;
|
this.isFx31 = this.isFx35;
|
||||||
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
|
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
|
||||||
this.isFx4 = appInfo.platformVersion[0] >= 2;
|
this.isFx4 = appInfo.platformVersion[0] >= 2;
|
||||||
|
this.isFx5 = appInfo.platformVersion[0] >= 5;
|
||||||
|
|
||||||
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
|
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
|
||||||
if(this.isStandalone) {
|
if(this.isStandalone) {
|
||||||
|
@ -242,6 +257,9 @@ var Zotero = new function(){
|
||||||
this.isLinux = (this.platform.substr(0, 5) == "Linux");
|
this.isLinux = (this.platform.substr(0, 5) == "Linux");
|
||||||
this.oscpu = win.navigator.oscpu;
|
this.oscpu = win.navigator.oscpu;
|
||||||
|
|
||||||
|
// Browser
|
||||||
|
Zotero.browser = "g";
|
||||||
|
|
||||||
// Locale
|
// Locale
|
||||||
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
.getService(Components.interfaces.nsIPrefService);
|
.getService(Components.interfaces.nsIPrefService);
|
||||||
|
@ -275,19 +293,19 @@ var Zotero = new function(){
|
||||||
xmlhttp.send(null);
|
xmlhttp.send(null);
|
||||||
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
|
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
|
||||||
if (matches && matches[0] == 'rtl') {
|
if (matches && matches[0] == 'rtl') {
|
||||||
this.dir = 'rtl';
|
Zotero.dir = 'rtl';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.dir = 'ltr';
|
Zotero.dir = 'ltr';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var dataDir = this.getZoteroDirectory();
|
var dataDir = Zotero.getZoteroDirectory();
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
// Zotero dir not found
|
// Zotero dir not found
|
||||||
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
|
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
|
||||||
this.startupError = Zotero.getString('dataDir.notFound');
|
Zotero.startupError = Zotero.getString('dataDir.notFound');
|
||||||
_startupErrorHandler = function() {
|
_startupErrorHandler = function() {
|
||||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||||
.getService(Components.interfaces.nsIWindowMediator);
|
.getService(Components.interfaces.nsIWindowMediator);
|
||||||
|
@ -300,7 +318,7 @@ var Zotero = new function(){
|
||||||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||||||
var index = ps.confirmEx(win,
|
var index = ps.confirmEx(win,
|
||||||
Zotero.getString('general.error'),
|
Zotero.getString('general.error'),
|
||||||
this.startupError + '\n\n' +
|
Zotero.startupError + '\n\n' +
|
||||||
Zotero.getString('dataDir.previousDir') + ' '
|
Zotero.getString('dataDir.previousDir') + ' '
|
||||||
+ Zotero.Prefs.get('lastDataDir'),
|
+ Zotero.Prefs.get('lastDataDir'),
|
||||||
buttonFlags, null,
|
buttonFlags, null,
|
||||||
|
@ -382,7 +400,6 @@ var Zotero = new function(){
|
||||||
else if (index == 2) {
|
else if (index == 2) {
|
||||||
Zotero.chooseZoteroDirectory(true);
|
Zotero.chooseZoteroDirectory(true);
|
||||||
}
|
}
|
||||||
var dataDir = this.getZoteroDirectory();
|
|
||||||
}
|
}
|
||||||
// DEBUG: handle more startup errors
|
// DEBUG: handle more startup errors
|
||||||
else {
|
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();
|
Zotero.VersionHeader.init();
|
||||||
|
|
||||||
// Check for DB restore
|
// Check for DB restore
|
||||||
|
@ -412,7 +477,7 @@ var Zotero = new function(){
|
||||||
Zotero.Schema.skipDefaultData = true;
|
Zotero.Schema.skipDefaultData = true;
|
||||||
Zotero.Schema.updateSchema();
|
Zotero.Schema.updateSchema();
|
||||||
|
|
||||||
this.restoreFromServer = true;
|
Zotero.restoreFromServer = true;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
// Restore from backup?
|
// Restore from backup?
|
||||||
|
@ -420,54 +485,7 @@ var Zotero = new function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if(!_initDB()) return;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add notifier queue callbacks to the DB layer
|
// Add notifier queue callbacks to the DB layer
|
||||||
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
|
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
|
||||||
|
@ -477,7 +495,7 @@ var Zotero = new function(){
|
||||||
Zotero.Fulltext.init();
|
Zotero.Fulltext.init();
|
||||||
|
|
||||||
// Require >=2.1b3 database to ensure proper locking
|
// 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"]
|
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
||||||
.getService(Components.interfaces.nsIAppStartup);
|
.getService(Components.interfaces.nsIAppStartup);
|
||||||
|
|
||||||
|
@ -541,7 +559,7 @@ var Zotero = new function(){
|
||||||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.skipLoading = true;
|
Zotero.skipLoading = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +567,7 @@ var Zotero = new function(){
|
||||||
if (Zotero.Schema.userDataUpgradeRequired()) {
|
if (Zotero.Schema.userDataUpgradeRequired()) {
|
||||||
var upgraded = Zotero.Schema.showUpgradeWizard();
|
var upgraded = Zotero.Schema.showUpgradeWizard();
|
||||||
if (!upgraded) {
|
if (!upgraded) {
|
||||||
this.skipLoading = true;
|
Zotero.skipLoading = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -567,12 +585,12 @@ var Zotero = new function(){
|
||||||
]) + "\n\n"
|
]) + "\n\n"
|
||||||
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
|
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
|
||||||
+ Zotero.getString('general.seeForMoreInformation', kbURL);
|
+ Zotero.getString('general.seeForMoreInformation', kbURL);
|
||||||
this.startupError = msg;
|
Zotero.startupError = msg;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
||||||
}
|
}
|
||||||
this.skipLoading = true;
|
Zotero.skipLoading = true;
|
||||||
Components.utils.reportError(e);
|
Components.utils.reportError(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -589,8 +607,8 @@ var Zotero = new function(){
|
||||||
// Initialize various services
|
// Initialize various services
|
||||||
Zotero.Integration.init();
|
Zotero.Integration.init();
|
||||||
|
|
||||||
if(Zotero.Prefs.get("connector.enabled")) {
|
if(Zotero.Prefs.get("httpServer.enabled")) {
|
||||||
Zotero.Connector.init();
|
Zotero.Server.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Zeroconf.init();
|
Zotero.Zeroconf.init();
|
||||||
|
@ -607,18 +625,108 @@ var Zotero = new function(){
|
||||||
// Initialize Locate Manager
|
// Initialize Locate Manager
|
||||||
Zotero.LocateManager.init();
|
Zotero.LocateManager.init();
|
||||||
|
|
||||||
this.initialized = true;
|
return true;
|
||||||
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
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
|
* Check if a DB transaction is open and, if so, disable Zotero
|
||||||
*/
|
*/
|
||||||
function stateCheck() {
|
function stateCheck() {
|
||||||
if (Zotero.DB.transactionInProgress()) {
|
if(!Zotero.isConnector && Zotero.DB.transactionInProgress()) {
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.skipLoading = true;
|
this.skipLoading = true;
|
||||||
return false;
|
return false;
|
||||||
|
@ -630,7 +738,33 @@ var Zotero = new function(){
|
||||||
|
|
||||||
this.shutdown = function (subject, topic, data) {
|
this.shutdown = function (subject, topic, data) {
|
||||||
Zotero.debug("Shutting down Zotero");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1511,6 +1645,12 @@ var Zotero = new function(){
|
||||||
return true;
|
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) {
|
function _showWindowZoteroPaneOverlay(doc) {
|
||||||
doc.getElementById('zotero-collections-tree').disabled = true;
|
doc.getElementById('zotero-collections-tree').disabled = true;
|
||||||
|
@ -1658,9 +1798,21 @@ var Zotero = new function(){
|
||||||
Zotero.Creators.reloadAll();
|
Zotero.Creators.reloadAll();
|
||||||
Zotero.Items.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(){
|
Zotero.Prefs = new function(){
|
||||||
// Privileged methods
|
// Privileged methods
|
||||||
|
|
|
@ -90,18 +90,16 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var _loaded = false;
|
var _loaded = false;
|
||||||
var titlebarcolorState, titleState;
|
var titlebarcolorState, titleState, observerService;
|
||||||
|
var _reloadFunctions = [];
|
||||||
|
|
||||||
// Also needs to be changed in collectionTreeView.js
|
// Also needs to be changed in collectionTreeView.js
|
||||||
var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/;
|
var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Called when the window containing Zotero pane is open
|
* Called when the window containing Zotero pane is open
|
||||||
*/
|
*/
|
||||||
function init()
|
function init() {
|
||||||
{
|
|
||||||
if(!Zotero || !Zotero.initialized) return;
|
|
||||||
|
|
||||||
// Set "Report Errors..." label via property rather than DTD entity,
|
// Set "Report Errors..." label via property rather than DTD entity,
|
||||||
// since we need to reference it in script elsewhere
|
// since we need to reference it in script elsewhere
|
||||||
document.getElementById('zotero-tb-actions-reportErrors').setAttribute('label',
|
document.getElementById('zotero-tb-actions-reportErrors').setAttribute('label',
|
||||||
|
@ -116,9 +114,9 @@ var ZoteroPane = new function()
|
||||||
|
|
||||||
var zp = document.getElementById('zotero-pane');
|
var zp = document.getElementById('zotero-pane');
|
||||||
Zotero.setFontSize(zp);
|
Zotero.setFontSize(zp);
|
||||||
this.updateToolbarPosition();
|
ZoteroPane_Local.updateToolbarPosition();
|
||||||
window.addEventListener("resize", this.updateToolbarPosition, false);
|
window.addEventListener("resize", ZoteroPane_Local.updateToolbarPosition, false);
|
||||||
window.setTimeout(this.updateToolbarPosition, 0);
|
window.setTimeout(ZoteroPane_Local.updateToolbarPosition, 0);
|
||||||
|
|
||||||
Zotero.updateQuickSearchBox(document);
|
Zotero.updateQuickSearchBox(document);
|
||||||
|
|
||||||
|
@ -136,10 +134,34 @@ var ZoteroPane = new function()
|
||||||
zp.setAttribute("ignoreActiveAttribute", "true");
|
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
|
//Initialize collections view
|
||||||
this.collectionsView = new Zotero.CollectionTreeView();
|
ZoteroPane_Local.collectionsView = new Zotero.CollectionTreeView();
|
||||||
var collectionsTree = document.getElementById('zotero-collections-tree');
|
var collectionsTree = document.getElementById('zotero-collections-tree');
|
||||||
collectionsTree.view = this.collectionsView;
|
collectionsTree.view = ZoteroPane_Local.collectionsView;
|
||||||
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
|
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
|
||||||
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
||||||
|
|
||||||
|
@ -147,8 +169,6 @@ var ZoteroPane = new function()
|
||||||
itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
|
itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
|
||||||
itemsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
itemsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
|
||||||
|
|
||||||
this.buildItemTypeSubMenu();
|
|
||||||
|
|
||||||
var menu = document.getElementById("contentAreaContextMenu");
|
var menu = document.getElementById("contentAreaContextMenu");
|
||||||
menu.addEventListener("popupshowing", ZoteroPane_Local.contextPopupShowing, false);
|
menu.addEventListener("popupshowing", ZoteroPane_Local.contextPopupShowing, false);
|
||||||
|
|
||||||
|
@ -322,6 +342,8 @@ var ZoteroPane = new function()
|
||||||
this.collectionsView.unregister();
|
this.collectionsView.unregister();
|
||||||
if (this.itemsView)
|
if (this.itemsView)
|
||||||
this.itemsView.unregister();
|
this.itemsView.unregister();
|
||||||
|
|
||||||
|
observerService.removeObserver(_reload, "zotero-reloaded", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,6 +371,7 @@ var ZoteroPane = new function()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.buildItemTypeSubMenu();
|
||||||
this.unserializePersist();
|
this.unserializePersist();
|
||||||
this.updateToolbarPosition();
|
this.updateToolbarPosition();
|
||||||
this.updateTagSelectorSize();
|
this.updateTagSelectorSize();
|
||||||
|
@ -3644,6 +3667,22 @@ var ZoteroPane = new function()
|
||||||
this.openAboutDialog = function() {
|
this.openAboutDialog = function() {
|
||||||
window.openDialog('chrome://zotero/content/about.xul', 'about', 'chrome');
|
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.libraryLookup.tooltip = Look up this item using the selected OpenURL resolver
|
||||||
locate.manageLocateEngines = Manage Lookup Engines...
|
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
|
https://developer.mozilla.org/en/Chrome/Command_Line
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const nsISupports = Components.interfaces.nsISupports;
|
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero";
|
||||||
const nsICategoryManager = Components.interfaces.nsICategoryManager;
|
|
||||||
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
|
|
||||||
const nsICommandLine = Components.interfaces.nsICommandLine;
|
|
||||||
const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
|
|
||||||
const nsIFactory = Components.interfaces.nsIFactory;
|
|
||||||
const nsIModule = Components.interfaces.nsIModule;
|
|
||||||
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
|
|
||||||
|
|
||||||
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration";
|
|
||||||
const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
|
const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
|
||||||
const clh_category = "m-zotero-integration";
|
const clh_category = "m-zotero";
|
||||||
const clh_description = "Zotero Integration Command Line Handler";
|
const clh_description = "Zotero Command Line Handler";
|
||||||
|
|
||||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The XPCOM component that implements nsICommandLineHandler.
|
* The XPCOM component that implements nsICommandLineHandler.
|
||||||
*/
|
*/
|
||||||
function ZoteroIntegrationCommandLineHandler() {}
|
function ZoteroCommandLineHandler() {}
|
||||||
ZoteroIntegrationCommandLineHandler.prototype = {
|
ZoteroCommandLineHandler.prototype = {
|
||||||
Zotero : null,
|
|
||||||
|
|
||||||
/* nsISupports */
|
/* nsISupports */
|
||||||
QueryInterface : function(iid) {
|
QueryInterface : XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler,
|
||||||
if(iid.equals(nsICommandLineHandler) ||
|
Components.interfaces.nsIFactory, Components.interfaces.nsISupports]),
|
||||||
iid.equals(nsIFactory) ||
|
|
||||||
iid.equals(nsISupports)) return this;
|
|
||||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* nsICommandLineHandler */
|
/* nsICommandLineHandler */
|
||||||
handle : function(cmdLine) {
|
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 agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false);
|
||||||
var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
|
if(agent) {
|
||||||
var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
|
// Don't open a new window
|
||||||
if(agent && command) {
|
cmdLine.preventDefault = true;
|
||||||
if(!this.Zotero) this.Zotero = Components.classes["@zotero.org/Zotero;1"]
|
|
||||||
.getService(Components.interfaces.nsISupports).wrappedJSObject;
|
var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
|
||||||
var Zotero = this.Zotero;
|
var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
|
||||||
|
|
||||||
// Not quite sure why this is necessary to get the appropriate scoping
|
// 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);
|
var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
|
||||||
timer.initWithCallback({notify:function() { Zotero.Integration.execCommand(agent, command, docId) }}, 0,
|
timer.initWithCallback({notify:function() { Zotero.Integration.execCommand(agent, command, docId) }}, 0,
|
||||||
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
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,
|
classDescription: clh_description,
|
||||||
|
@ -88,12 +101,20 @@ ZoteroIntegrationCommandLineHandler.prototype = {
|
||||||
Components.interfaces.nsISupports])
|
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.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
|
||||||
* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
|
* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
|
||||||
*/
|
*/
|
||||||
if (XPCOMUtils.generateNSGetFactory) {
|
if (XPCOMUtils.generateNSGetFactory) {
|
||||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroIntegrationCommandLineHandler]);
|
var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroCommandLineHandler]);
|
||||||
} else {
|
} 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 [path, queryString] = uri.path.substr(1).split('?');
|
||||||
var [type, id] = path.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"]
|
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||||
.getService(Components.interfaces.nsIWindowMediator);
|
.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();
|
win.ZoteroPane.show();
|
||||||
|
|
||||||
|
if(!id) return;
|
||||||
|
|
||||||
var lkh = Zotero.Items.parseLibraryKeyHash(id);
|
var lkh = Zotero.Items.parseLibraryKeyHash(id);
|
||||||
if (lkh) {
|
if (lkh) {
|
||||||
var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key);
|
var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key);
|
||||||
|
@ -1026,10 +1034,10 @@ function ChromeExtensionHandler() {
|
||||||
try {
|
try {
|
||||||
var originalURI = uri.path;
|
var originalURI = uri.path;
|
||||||
originalURI = decodeURIComponent(originalURI.substr(originalURI.indexOf("/")+1));
|
originalURI = decodeURIComponent(originalURI.substr(originalURI.indexOf("/")+1));
|
||||||
if(!Zotero.Connector.Data[originalURI]) {
|
if(!Zotero.Server.Connector.Data[originalURI]) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return new ConnectorChannel(originalURI, Zotero.Connector.Data[originalURI]);
|
return new ConnectorChannel(originalURI, Zotero.Server.Connector.Data[originalURI]);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Zotero.debug(e);
|
Zotero.debug(e);
|
||||||
|
|
|
@ -35,30 +35,31 @@ const ZOTERO_IID = Components.interfaces.chnmIZoteroService; //unused
|
||||||
const Cc = Components.classes;
|
const Cc = Components.classes;
|
||||||
const Ci = Components.interfaces;
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
/** XPCOM files to be loaded for all modes **/
|
||||||
|
const xpcomFilesAll = [
|
||||||
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 = [
|
|
||||||
'zotero',
|
'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',
|
'annotate',
|
||||||
'attachments',
|
'attachments',
|
||||||
'cite',
|
'cite',
|
||||||
'collectionTreeView',
|
|
||||||
'commons',
|
'commons',
|
||||||
'connector',
|
|
||||||
'dataServer',
|
|
||||||
'data_access',
|
'data_access',
|
||||||
'data/dataObjects',
|
'data/dataObjects',
|
||||||
'data/cachedTypes',
|
'data/cachedTypes',
|
||||||
|
@ -79,28 +80,21 @@ var xpcomFiles = [
|
||||||
'data/tags',
|
'data/tags',
|
||||||
'date',
|
'date',
|
||||||
'db',
|
'db',
|
||||||
'debug',
|
|
||||||
'duplicate',
|
'duplicate',
|
||||||
'enstyle',
|
'enstyle',
|
||||||
'error',
|
|
||||||
'file',
|
|
||||||
'fulltext',
|
'fulltext',
|
||||||
'http',
|
|
||||||
'id',
|
'id',
|
||||||
'integration',
|
'integration',
|
||||||
'integration_compat',
|
|
||||||
'itemTreeView',
|
'itemTreeView',
|
||||||
'locateManager',
|
'locateManager',
|
||||||
'mime',
|
'mime',
|
||||||
'mimeTypeHandler',
|
|
||||||
'notifier',
|
'notifier',
|
||||||
'openurl',
|
|
||||||
'progressWindow',
|
|
||||||
'proxy',
|
'proxy',
|
||||||
'quickCopy',
|
'quickCopy',
|
||||||
'report',
|
'report',
|
||||||
'schema',
|
'schema',
|
||||||
'search',
|
'search',
|
||||||
|
'server',
|
||||||
'style',
|
'style',
|
||||||
'sync',
|
'sync',
|
||||||
'storage',
|
'storage',
|
||||||
|
@ -108,117 +102,197 @@ var xpcomFiles = [
|
||||||
'storage/zfs',
|
'storage/zfs',
|
||||||
'storage/webdav',
|
'storage/webdav',
|
||||||
'timeline',
|
'timeline',
|
||||||
'translation/translator',
|
|
||||||
'translation/translate',
|
|
||||||
'translation/browser_firefox',
|
|
||||||
'translation/item_local',
|
|
||||||
'uri',
|
'uri',
|
||||||
'utilities',
|
'zeroconf',
|
||||||
'zeroconf'
|
'translation/translate_item',
|
||||||
|
'translation/translator',
|
||||||
|
'server_connector'
|
||||||
];
|
];
|
||||||
|
|
||||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
/** XPCOM files to be loaded only for connector translation and DB access **/
|
||||||
.getService(Ci.mozIJSSubScriptLoader)
|
const xpcomFilesConnector = [
|
||||||
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[0] + ".js");
|
'connector/translate_item',
|
||||||
|
'connector/translator',
|
||||||
// Load CiteProc into Zotero.CiteProc namespace
|
'connector/connector',
|
||||||
Zotero.CiteProc = {"Zotero":Zotero};
|
'connector/cachedTypes'
|
||||||
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'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
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"]
|
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
.getService(Ci.mozIJSSubScriptLoader)
|
.getService(Ci.mozIJSSubScriptLoader)
|
||||||
.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", Zotero.RDF.AJAW);
|
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext);
|
||||||
}
|
|
||||||
|
// Load CiteProc into Zotero.CiteProc namespace
|
||||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero};
|
||||||
.getService(Ci.mozIJSSubScriptLoader)
|
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
.loadSubScript("chrome://global/content/nsTransferable.js");
|
.getService(Ci.mozIJSSubScriptLoader)
|
||||||
|
.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc);
|
||||||
/********************************************************************/
|
|
||||||
|
// Load remaining xpcomFiles
|
||||||
|
for (var i=1; i<xpcomFilesAll.length; i++) {
|
||||||
// Initialize the Zotero service
|
try {
|
||||||
//
|
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
// This runs when ZoteroService is first requested.
|
.getService(Ci.mozIJSSubScriptLoader)
|
||||||
// Calls to other XPCOM components must be in here rather than in top-level
|
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext);
|
||||||
// code, as other components may not have yet been initialized.
|
}
|
||||||
function setupService(){
|
catch (e) {
|
||||||
try {
|
Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext);
|
||||||
Zotero.init();
|
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;
|
var msg = typeof e == 'string' ? e : e.name;
|
||||||
dump(e + "\n\n");
|
dump(e + "\n\n");
|
||||||
Components.utils.reportError(e);
|
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
|
// XPCOM goop
|
||||||
//
|
//
|
||||||
|
@ -227,8 +301,8 @@ ZoteroService.prototype = {
|
||||||
contractID: ZOTERO_CONTRACTID,
|
contractID: ZOTERO_CONTRACTID,
|
||||||
classDescription: ZOTERO_CLASSNAME,
|
classDescription: ZOTERO_CLASSNAME,
|
||||||
classID: ZOTERO_CID,
|
classID: ZOTERO_CID,
|
||||||
service: true,
|
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
|
||||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, ZOTERO_IID])
|
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
|
pref("extensions.zotero.integration.autoRegenerate", -1); // -1 = ask; 0 = no; 1 = yes
|
||||||
|
|
||||||
// Connector settings
|
// Connector settings
|
||||||
pref("extensions.zotero.connector.enabled", false);
|
pref("extensions.zotero.httpServer.enabled", false); // TODO enabled for testing only
|
||||||
pref("extensions.zotero.connector.port", 23119); // ascii "ZO"
|
pref("extensions.zotero.httpServer.port", 23119); // ascii "ZO"
|
||||||
|
|
||||||
// Zeroconf
|
// Zeroconf
|
||||||
pref("extensions.zotero.zeroconf.server.enabled", false);
|
pref("extensions.zotero.zeroconf.server.enabled", false);
|
||||||
|
|
Loading…
Reference in a new issue