Remove unneeded connector files, update connectorTypeSchemaData.js
Addresses zotero/zotero-connectors#121
This commit is contained in:
parent
dbeecb9b0a
commit
e1f09d4655
9 changed files with 91 additions and 1984 deletions
90
chrome/content/zotero/tools/build_typeSchemaData.html
Normal file
90
chrome/content/zotero/tools/build_typeSchemaData.html
Normal file
|
@ -0,0 +1,90 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Build schemaData.js</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>This script builds schemaData.js, which contains Zotero schema information for the connector.</p>
|
||||
<p id="result"></p>
|
||||
<script src="../include.js"></script>
|
||||
<script type="text/javascript">
|
||||
Zotero.Promise.coroutine(function* (){
|
||||
// Create schema
|
||||
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
|
||||
var types = Zotero.ItemTypes.getTypes();
|
||||
|
||||
var fieldIDs = yield Zotero.DB.columnQueryAsync("SELECT fieldID FROM fieldsCombined");
|
||||
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
||||
|
||||
for (let fieldID of fieldIDs) {
|
||||
var fieldObj = [/* name */Zotero.ItemFields.getName(fieldID)];
|
||||
try {
|
||||
fieldObj.push(/* localizedString */Zotero.getString("itemFields." + fieldObj.name));
|
||||
} catch(e) {
|
||||
fieldObj.push(/* name -> localizedString */fieldObj[0]);
|
||||
}
|
||||
fieldObj.push(/* isBaseField */ !baseMappedFields.includes(fieldID));
|
||||
schema.fields[fieldID] = fieldObj;
|
||||
}
|
||||
|
||||
// names, localizedStrings, creatorTypes, and fields for each item type
|
||||
for (let type of types) {
|
||||
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
|
||||
var baseFields = {};
|
||||
for (let fieldID in fieldIDs) {
|
||||
if (baseMappedFields.includes(fieldID)) {
|
||||
baseFields[fieldID] = Zotero.ItemFields.getBaseIDFromTypeAndField(type.id, fieldID);
|
||||
}
|
||||
}
|
||||
|
||||
var icon = Zotero.ItemTypes.getImageSrc(type.name);
|
||||
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||
|
||||
try {
|
||||
var creatorTypes = Zotero.CreatorTypes.getTypesForItemType(type.id).map((creatorType) => creatorType.id);
|
||||
} catch (e) {
|
||||
creatorTypes = [];
|
||||
}
|
||||
var primaryCreatorType = Zotero.CreatorTypes.getPrimaryIDForType(type.id);
|
||||
if(creatorTypes[0] != primaryCreatorType) {
|
||||
creatorTypes.splice(creatorTypes.indexOf(primaryCreatorType), 1);
|
||||
creatorTypes.unshift(primaryCreatorType);
|
||||
}
|
||||
|
||||
schema.itemTypes[type.id] = [
|
||||
/* name */type.name,
|
||||
/* localizedString */Zotero.ItemTypes.getLocalizedString(type.name),
|
||||
/* creatorTypes */creatorTypes,
|
||||
/* fields */ fieldIDs,
|
||||
/* baseFields */baseFields,
|
||||
/* icon */icon
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
var types = Zotero.CreatorTypes.getTypes();
|
||||
for (let type of types) {
|
||||
schema.creatorTypes[type.id] = [
|
||||
/* name */type.name,
|
||||
/* localizedString */Zotero.CreatorTypes.getLocalizedString(type.name)
|
||||
];
|
||||
}
|
||||
|
||||
// Write to file
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(window, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
|
||||
|
||||
let resultElem = document.getElementById('result');
|
||||
if (fp.show() != nsIFilePicker.returnOK) {
|
||||
result.innerHTML = '<p>Failed.</p>';
|
||||
} else {
|
||||
let schemaFile = fp.file;
|
||||
schemaFile.append("connectorTypeSchemaData.js");
|
||||
Zotero.File.putContents(schemaFile, `Zotero.Connector_Types.schema = ${JSON.stringify(schema)}`);
|
||||
result.innerHTML = `<p>Wrote ${schemaFile.path} successfully.</p>`;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
***** 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 *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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] = Zotero.Utilities.deepCopy(Zotero.Connector_Types.schema[schemaType]);
|
||||
for(var id in Zotero.Connector_Types.schema[schemaType]) {
|
||||
var entry = this[schemaType][id];
|
||||
entry.unshift(parseInt(id, 10));
|
||||
this[schemaType][entry[1]/* name */] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
var itemTypes = Zotero.Connector_Types["itemTypes"];
|
||||
var creatorTypes = Zotero.Connector_Types["creatorTypes"];
|
||||
var fields = Zotero.Connector_Types["fields"];
|
||||
|
||||
Zotero.CachedTypes = function() {
|
||||
var thisType = Zotero.Connector_Types[this.schemaType];
|
||||
|
||||
this.getID = function(idOrName) {
|
||||
var type = thisType[idOrName];
|
||||
return (type ? type[0]/* id */ : false);
|
||||
};
|
||||
|
||||
this.getName = function(idOrName) {
|
||||
var type = thisType[idOrName];
|
||||
return (type ? type[1]/* name */ : false);
|
||||
};
|
||||
|
||||
this.getLocalizedString = function(idOrName) {
|
||||
var type = thisType[idOrName];
|
||||
return (type ? type[2]/* localizedString */ : false);
|
||||
};
|
||||
}
|
||||
|
||||
Zotero.ItemTypes = new function() {
|
||||
this.schemaType = "itemTypes";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getImageSrc = function(idOrName) {
|
||||
var itemType = Zotero.Connector_Types["itemTypes"][idOrName];
|
||||
var icon = itemType ? itemType[6]/* icon */ : "treeitem-"+idOrName+".png";
|
||||
|
||||
if(Zotero.isBookmarklet) {
|
||||
return ZOTERO_CONFIG.BOOKMARKLET_URL+"images/"+icon;
|
||||
} else if(Zotero.isFx) {
|
||||
return "chrome://zotero/skin/"+icon;
|
||||
} else if(Zotero.isBrowserExt) {
|
||||
return chrome.extension.getURL("images/"+icon);
|
||||
} else if(Zotero.isSafari) {
|
||||
return safari.extension.baseURI+"images/"+icon;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Zotero.CreatorTypes = new function() {
|
||||
this.schemaType = "creatorTypes";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getTypesForItemType = function(idOrName) {
|
||||
var itemType = itemTypes[idOrName];
|
||||
if(!itemType) return false;
|
||||
|
||||
var itemCreatorTypes = itemType[3]; // creatorTypes
|
||||
if (!itemCreatorTypes
|
||||
// TEMP: 'note' and 'attachment' have an array containing false
|
||||
|| (itemCreatorTypes.length == 1 && !itemCreatorTypes[0])) {
|
||||
return [];
|
||||
}
|
||||
var n = itemCreatorTypes.length;
|
||||
var outputTypes = new Array(n);
|
||||
|
||||
for(var i=0; i<n; i++) {
|
||||
var creatorType = creatorTypes[itemCreatorTypes[i]];
|
||||
outputTypes[i] = {"id":creatorType[0]/* id */,
|
||||
"name":creatorType[1]/* name */};
|
||||
}
|
||||
return outputTypes;
|
||||
};
|
||||
|
||||
this.getPrimaryIDForType = function(idOrName) {
|
||||
var itemType = itemTypes[idOrName];
|
||||
if(!itemType) return false;
|
||||
return itemType[3]/* creatorTypes */[0];
|
||||
};
|
||||
}
|
||||
|
||||
Zotero.ItemFields = new function() {
|
||||
this.schemaType = "fields";
|
||||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.isValidForType = function(fieldIdOrName, typeIdOrName) {
|
||||
var field = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
||||
|
||||
// mimics itemFields.js
|
||||
if(!field || !itemType) return false;
|
||||
|
||||
/* fields */ /* id */
|
||||
return itemType[4].indexOf(field[0]) !== -1;
|
||||
};
|
||||
|
||||
this.getFieldIDFromTypeAndBase = function(typeIdOrName, fieldIdOrName) {
|
||||
var baseField = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
||||
|
||||
if(!baseField || !itemType) return false;
|
||||
|
||||
// get as ID
|
||||
baseField = baseField[0]/* id */;
|
||||
|
||||
// loop through base fields for item type
|
||||
var baseFields = itemType[5];
|
||||
for(var i in baseFields) {
|
||||
if(baseFields[i] === baseField) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.getBaseIDFromTypeAndField = function(typeIdOrName, fieldIdOrName) {
|
||||
var field = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
||||
if(!field || !itemType) {
|
||||
throw new Error("Invalid field or type ID");
|
||||
}
|
||||
|
||||
var baseField = itemType[5]/* baseFields */[field[0]/* id */];
|
||||
return baseField ? baseField : false;
|
||||
};
|
||||
|
||||
this.getItemTypeFields = function(typeIdOrName) {
|
||||
return itemTypes[typeIdOrName][4]/* fields */.slice();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Passes schema to a callback
|
||||
* @param {Function} callback
|
||||
*/
|
||||
this.getSchema = function(callback) {
|
||||
callback(Zotero.Connector_Types.schema);
|
||||
};
|
||||
}
|
|
@ -1,338 +0,0 @@
|
|||
/*
|
||||
***** 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/";
|
||||
const CONNECTOR_API_VERSION = 2;
|
||||
|
||||
var _ieStandaloneIframeTarget, _ieConnectorCallbacks;
|
||||
// As of Chrome 38 (and corresponding Opera version 24?) pages loaded over
|
||||
// https (i.e. the zotero bookmarklet iframe) can not send requests over
|
||||
// http, so pinging Standalone at http://127.0.0.1 fails.
|
||||
// Disable for all browsers, except IE, which may be used frequently with ZSA
|
||||
this.isOnline = Zotero.isBookmarklet && !Zotero.isIE ? false : null;
|
||||
|
||||
/**
|
||||
* Checks if Zotero is online and passes current status to callback
|
||||
* @param {Function} callback
|
||||
*/
|
||||
this.checkIsOnline = function(callback) {
|
||||
// Only check once in bookmarklet
|
||||
if(Zotero.isBookmarklet && this.isOnline !== null) {
|
||||
callback(this.isOnline);
|
||||
return;
|
||||
}
|
||||
|
||||
if(Zotero.isIE) {
|
||||
if(window.location.protocol !== "http:") {
|
||||
this.isOnline = false;
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Connector: Looking for Zotero Standalone");
|
||||
var me = this;
|
||||
var fail = function() {
|
||||
if(me.isOnline !== null) return;
|
||||
Zotero.debug("Connector: Zotero Standalone is not online or cannot be contacted");
|
||||
me.isOnline = false;
|
||||
callback(false);
|
||||
};
|
||||
|
||||
window.setTimeout(fail, 1000);
|
||||
try {
|
||||
var xdr = new XDomainRequest();
|
||||
xdr.timeout = 700;
|
||||
xdr.open("POST", "http://127.0.0.1:23119/connector/ping", true);
|
||||
xdr.onerror = function() {
|
||||
Zotero.debug("Connector: XDomainRequest to Zotero Standalone experienced an error");
|
||||
fail();
|
||||
};
|
||||
xdr.ontimeout = function() {
|
||||
Zotero.debug("Connector: XDomainRequest to Zotero Standalone timed out");
|
||||
fail();
|
||||
};
|
||||
xdr.onload = function() {
|
||||
if(me.isOnline !== null) return;
|
||||
me.isOnline = true;
|
||||
Zotero.debug("Connector: Standalone found; trying IE hack");
|
||||
|
||||
_ieConnectorCallbacks = [];
|
||||
var listener = function(event) {
|
||||
if(event.origin !== "http://127.0.0.1:23119"
|
||||
|| event.source !== iframe.contentWindow) return;
|
||||
if(event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
// If this is the first time the target was loaded, then this is a loaded
|
||||
// event
|
||||
if(!_ieStandaloneIframeTarget) {
|
||||
Zotero.debug("Connector: Standalone loaded");
|
||||
_ieStandaloneIframeTarget = iframe.contentWindow;
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, this is a response event
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
} catch(e) {
|
||||
Zotero.debug("Invalid JSON received: "+event.data);
|
||||
return;
|
||||
}
|
||||
var xhrSurrogate = {
|
||||
"status":data[1],
|
||||
"responseText":data[2],
|
||||
"getResponseHeader":function(x) { return data[3][x.toLowerCase()] }
|
||||
};
|
||||
_ieConnectorCallbacks[data[0]](xhrSurrogate);
|
||||
delete _ieConnectorCallbacks[data[0]];
|
||||
};
|
||||
|
||||
if(window.addEventListener) {
|
||||
window.addEventListener("message", listener, false);
|
||||
} else {
|
||||
window.attachEvent("onmessage", function() { listener(event); });
|
||||
}
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
iframe.src = "http://127.0.0.1:23119/connector/ieHack";
|
||||
document.documentElement.appendChild(iframe);
|
||||
};
|
||||
xdr.send("");
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
fail();
|
||||
}
|
||||
} else {
|
||||
Zotero.Connector.callMethod("ping", {}, function(status) {
|
||||
callback(status !== false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the XHR to execute an RPC call.
|
||||
*
|
||||
* @param {String|Object} options - The method name as a string or an object with the
|
||||
* following properties:
|
||||
* method - method name
|
||||
* headers - an object of HTTP headers to send
|
||||
* queryString - a query string to pass on the HTTP call
|
||||
* @param {Object} data - RPC data to POST. If null or undefined, a GET request is sent.
|
||||
* @param {Function} callback - Function to be called when requests complete.
|
||||
*/
|
||||
this.callMethod = function(options, data, callback, tab) {
|
||||
// Don't bother trying if not online in bookmarklet
|
||||
if(Zotero.isBookmarklet && this.isOnline === false) {
|
||||
callback(false, 0);
|
||||
return;
|
||||
}
|
||||
if (typeof options == 'string') {
|
||||
options = {method: options};
|
||||
}
|
||||
var method = options.method;
|
||||
var headers = Object.assign({
|
||||
"Content-Type":"application/json",
|
||||
"X-Zotero-Version":Zotero.version,
|
||||
"X-Zotero-Connector-API-Version":CONNECTOR_API_VERSION
|
||||
}, options.headers || {});
|
||||
var queryString = options.queryString ? ("?" + options.queryString) : "";
|
||||
|
||||
var newCallback = function(req) {
|
||||
try {
|
||||
var isOnline = req.status !== 0 && req.status !== 403 && req.status !== 412;
|
||||
|
||||
if(Zotero.Connector.isOnline !== isOnline) {
|
||||
Zotero.Connector.isOnline = isOnline;
|
||||
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) {
|
||||
Zotero.Connector_Browser.onStateChange(isOnline);
|
||||
}
|
||||
}
|
||||
var val = null;
|
||||
if(req.responseText) {
|
||||
if(req.getResponseHeader("Content-Type") === "application/json") {
|
||||
val = JSON.parse(req.responseText);
|
||||
} else {
|
||||
val = req.responseText;
|
||||
}
|
||||
}
|
||||
if(req.status == 0 || req.status >= 400) {
|
||||
Zotero.debug("Connector: Method "+method+" failed with status "+req.status);
|
||||
if(callback) callback(false, req.status, val);
|
||||
|
||||
// Check for incompatible version
|
||||
if(req.status === 412) {
|
||||
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onIncompatibleStandaloneVersion) {
|
||||
var standaloneVersion = req.getResponseHeader("X-Zotero-Version");
|
||||
Zotero.Connector_Browser.onIncompatibleStandaloneVersion(Zotero.version, standaloneVersion);
|
||||
throw "Connector: Version mismatch: Connector version "+Zotero.version
|
||||
+", Standalone version "+(standaloneVersion ? standaloneVersion : "<unknown>");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Zotero.debug("Connector: Method "+method+" succeeded");
|
||||
if(callback) callback(val, req.status);
|
||||
}
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if(Zotero.isIE) { // IE requires XDR for CORS
|
||||
if(_ieStandaloneIframeTarget) {
|
||||
var requestID = Zotero.Utilities.randomString();
|
||||
_ieConnectorCallbacks[requestID] = newCallback;
|
||||
_ieStandaloneIframeTarget.postMessage(JSON.stringify([null, "connectorRequest",
|
||||
[requestID, method, JSON.stringify(data)]]), "http://127.0.0.1:23119/connector/ieHack");
|
||||
} else {
|
||||
Zotero.debug("Connector: No iframe target; not sending to Standalone");
|
||||
callback(false, 0);
|
||||
}
|
||||
} else { // Other browsers can use plain doPost
|
||||
var uri = CONNECTOR_URI + "connector/" + method + queryString;
|
||||
if (headers["Content-Type"] == 'application/json') {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
if (data == null || data == undefined) {
|
||||
Zotero.HTTP.doGet(uri, newCallback, headers);
|
||||
} else {
|
||||
Zotero.HTTP.doPost(uri, data, newCallback, headers);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds detailed cookies to the data before sending "saveItems" request to
|
||||
* the server/Standalone
|
||||
*
|
||||
* @param {Object} data RPC data. See documentation above.
|
||||
* @param {Function} callback Function to be called when requests complete.
|
||||
*/
|
||||
this.setCookiesThenSaveItems = function(data, callback, tab) {
|
||||
if(Zotero.isFx && !Zotero.isBookmarklet && data.uri) {
|
||||
var host = Services.io.newURI(data.uri, null, null).host;
|
||||
var cookieEnum = Services.cookies.getCookiesFromHost(host);
|
||||
var cookieHeader = '';
|
||||
while(cookieEnum.hasMoreElements()) {
|
||||
var cookie = cookieEnum.getNext().QueryInterface(Components.interfaces.nsICookie2);
|
||||
cookieHeader += '\n' + cookie.name + '=' + cookie.value
|
||||
+ ';Domain=' + cookie.host
|
||||
+ (cookie.path ? ';Path=' + cookie.path : '')
|
||||
+ (!cookie.isDomain ? ';hostOnly' : '') //not a legit flag, but we have to use it internally
|
||||
+ (cookie.isSecure ? ';secure' : '');
|
||||
}
|
||||
|
||||
if(cookieHeader) {
|
||||
data.detailedCookies = cookieHeader.substr(1);
|
||||
}
|
||||
|
||||
this.callMethod("saveItems", data, callback, tab);
|
||||
return;
|
||||
} else if(Zotero.isBrowserExt && !Zotero.isBookmarklet) {
|
||||
var self = this;
|
||||
chrome.cookies.getAll({url: tab.url}, function(cookies) {
|
||||
var cookieHeader = '';
|
||||
for(var i=0, n=cookies.length; i<n; i++) {
|
||||
cookieHeader += '\n' + cookies[i].name + '=' + cookies[i].value
|
||||
+ ';Domain=' + cookies[i].domain
|
||||
+ (cookies[i].path ? ';Path=' + cookies[i].path : '')
|
||||
+ (cookies[i].hostOnly ? ';hostOnly' : '') //not a legit flag, but we have to use it internally
|
||||
+ (cookies[i].secure ? ';secure' : '');
|
||||
}
|
||||
|
||||
if(cookieHeader) {
|
||||
data.detailedCookies = cookieHeader.substr(1);
|
||||
}
|
||||
|
||||
// Cookie URI needed to set up the cookie sandbox on standalone
|
||||
data.uri = tab.url;
|
||||
|
||||
self.callMethod("saveItems", data, callback, tab);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.callMethod("saveItems", data, callback, tab);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Connector_Debug = new function() {
|
||||
/**
|
||||
* Call a callback depending upon whether debug output is being stored
|
||||
*/
|
||||
this.storing = function(callback) {
|
||||
callback(Zotero.Debug.storing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a callback with the lines themselves
|
||||
*/
|
||||
this.get = function(callback) {
|
||||
Zotero.Debug.get().then(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Call a callback with the number of lines of output
|
||||
*/
|
||||
this.count = function(callback) {
|
||||
callback(Zotero.Debug.count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit data to the server
|
||||
*/
|
||||
this.submitReport = function(callback) {
|
||||
Zotero.Debug.get().then(function(output){
|
||||
return Zotero.HTTP.request(
|
||||
ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1",
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/plain"
|
||||
},
|
||||
body: output,
|
||||
successCodes: false
|
||||
}
|
||||
);
|
||||
}).then(function(xmlhttp){
|
||||
if (!xmlhttp.responseXML) {
|
||||
callback(false, 'Invalid response from server');
|
||||
return;
|
||||
}
|
||||
var reported = xmlhttp.responseXML.getElementsByTagName('reported');
|
||||
if (reported.length != 1) {
|
||||
callback(false, 'The server returned an error. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
var reportID = reported[0].getAttribute('reportID');
|
||||
callback(true, reportID);
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
***** 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_Browser = new function() {
|
||||
/**
|
||||
* Called if Zotero version is determined to be incompatible with Standalone
|
||||
*/
|
||||
this.onIncompatibleStandaloneVersion = function(zoteroVersion, standaloneVersion) {
|
||||
Zotero.startupError = 'Zotero for Firefox '+Zotero.version+' is incompatible with the running '+
|
||||
'version of Zotero Standalone'+(standaloneVersion ? " ("+standaloneVersion+")" : "")+
|
||||
'.\n\nPlease ensure that you have installed the latest version of these components. See '+
|
||||
'http://www.zotero.org/support/standalone for more details.';
|
||||
Zotero.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if connector is offline. This should only happen if Zotero is getting a DB busy
|
||||
* message and no connector is open, so use the DB busy error message here.
|
||||
*/
|
||||
this.onStateChange = function(isOnline) {
|
||||
if(isOnline) return;
|
||||
|
||||
var msg = Zotero.localeJoin([
|
||||
Zotero.getString('startupError.databaseInUse'),
|
||||
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
|
||||
]);
|
||||
Zotero.startupError = msg;
|
||||
Zotero.initialized = false;
|
||||
}
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
***** 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 *****
|
||||
*/
|
||||
|
||||
const TRANSLATOR_CODE_PREFIX = "translatorCode-";
|
||||
Zotero.Repo = new function() {
|
||||
var _nextCheck;
|
||||
var _timeoutID;
|
||||
const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/;
|
||||
|
||||
this.SOURCE_ZOTERO_STANDALONE = 1;
|
||||
this.SOURCE_REPO = 2;
|
||||
|
||||
/**
|
||||
* Try to retrieve translator metadata from Zotero Standalone and initialize repository check
|
||||
* timer
|
||||
*/
|
||||
this.init = function() {
|
||||
// get time of next check
|
||||
_nextCheck = Zotero.Prefs.get("connector.repo.lastCheck.localTime")
|
||||
+ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL*1000;
|
||||
|
||||
// update from standalone, but only cascade to repo if we are overdue
|
||||
_updateFromStandalone(_nextCheck <= Date.now());
|
||||
};
|
||||
|
||||
/**
|
||||
* Force updating translators
|
||||
*/
|
||||
var update = this.update = function(reset) {
|
||||
_updateFromStandalone(true, reset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get translator code from repository
|
||||
* @param {String} translatorID ID of the translator to retrieve code for
|
||||
*/
|
||||
this.getTranslatorCode = Zotero.Promise.method(function (translatorID, debugMode) {
|
||||
var deferred = Zotero.Promise.defer();
|
||||
|
||||
// try standalone
|
||||
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) {
|
||||
if(result) {
|
||||
deferred.resolve(
|
||||
Zotero.Promise.all(
|
||||
[
|
||||
_haveCode(result, translatorID),
|
||||
Zotero.Repo.SOURCE_ZOTERO_STANDALONE
|
||||
]
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Don't fetch from repo in debug mode
|
||||
if (debugMode) {
|
||||
deferred.resolve([false, Zotero.Repo.SOURCE_ZOTERO_STANDALONE]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// then try repo
|
||||
Zotero.HTTP.doGet(
|
||||
ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version,
|
||||
function(xmlhttp) {
|
||||
deferred.resolve(
|
||||
Zotero.Promise.all(
|
||||
[
|
||||
_haveCode(
|
||||
xmlhttp.status === 200 ? xmlhttp.responseText : false,
|
||||
translatorID
|
||||
),
|
||||
Zotero.Repo.SOURCE_REPO
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
});
|
||||
|
||||
/**
|
||||
* Called when code has been retrieved from standalone or repo
|
||||
*/
|
||||
function _haveCode(code, translatorID) {
|
||||
if(!code) {
|
||||
Zotero.logError(new Error("Code could not be retrieved for " + translatorID));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!Zotero.isFx) {
|
||||
code = Zotero.Translators.preprocessCode(code);
|
||||
|
||||
var m = infoRe.exec(code);
|
||||
if (!m) {
|
||||
Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var metadata = JSON.parse(m[0]);
|
||||
} catch(e) {
|
||||
Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
|
||||
return false;
|
||||
}
|
||||
|
||||
var translator = Zotero.Translators.getWithoutCode(translatorID);
|
||||
|
||||
if(metadata.lastUpdated !== translator.lastUpdated) {
|
||||
if(Zotero.Date.sqlToDate(metadata.lastUpdated) > Zotero.Date.sqlToDate(translator.lastUpdated)) {
|
||||
Zotero.debug("Repo: Retrieved code for "+metadata.label+" newer than stored metadata; updating");
|
||||
Zotero.Translators.update([metadata]);
|
||||
} else {
|
||||
Zotero.debug("Repo: Retrieved code for "+metadata.label+" older than stored metadata; not caching");
|
||||
}
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve translator metadata from Zotero Standalone
|
||||
* @param {Boolean} [tryRepoOnFailure] If true, run _updateFromRepo() if standalone cannot be
|
||||
* contacted
|
||||
*/
|
||||
function _updateFromStandalone(tryRepoOnFailure, reset, callback) {
|
||||
Zotero.Connector.callMethod("getTranslators", {}, function(result) {
|
||||
if(!result && tryRepoOnFailure) {
|
||||
_updateFromRepo(reset, callback);
|
||||
} else {
|
||||
// Standalone always returns all translators without .deleted property
|
||||
_handleResponse(result, true);
|
||||
if(callback) callback(!!result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve metadata from repository
|
||||
*/
|
||||
function _updateFromRepo(reset, callback) {
|
||||
var url = ZOTERO_CONFIG.REPOSITORY_URL + "metadata?version=" + Zotero.version + "&last="+
|
||||
(reset ? "0" : Zotero.Prefs.get("connector.repo.lastCheck.repoTime"));
|
||||
|
||||
Zotero.HTTP.doGet(url, function(xmlhttp) {
|
||||
var success = xmlhttp.status === 200;
|
||||
_handleResponse(success ? JSON.parse(xmlhttp.responseText) : false, reset);
|
||||
|
||||
if(success) {
|
||||
var date = xmlhttp.getResponseHeader("Date");
|
||||
Zotero.Prefs.set("connector.repo.lastCheck.repoTime",
|
||||
Math.floor(Date.parse(date)/1000));
|
||||
}
|
||||
if(callback) callback(!!result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle response from Zotero Standalone or repository and set timer for next update
|
||||
*/
|
||||
function _handleResponse(result, reset) {
|
||||
// set up timer
|
||||
var now = Date.now();
|
||||
|
||||
if(result) {
|
||||
Zotero.Translators.update(result, reset);
|
||||
Zotero.Prefs.set("connector.repo.lastCheck.localTime", now);
|
||||
Zotero.debug("Repo: Check succeeded");
|
||||
} else {
|
||||
Zotero.debug("Repo: Check failed");
|
||||
}
|
||||
|
||||
if(result || _nextCheck <= now) {
|
||||
// if we failed a scheduled check, then use retry interval
|
||||
_nextCheck = now+(result
|
||||
? ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL
|
||||
: ZOTERO_CONFIG.REPOSITORY_RETRY_INTERVAL)*1000;
|
||||
} else if(_timeoutID) {
|
||||
// if we didn't fail a scheduled check and another is already scheduled, leave it
|
||||
return;
|
||||
}
|
||||
|
||||
// remove old timeout and create a new one
|
||||
if(_timeoutID) clearTimeout(_timeoutID);
|
||||
var nextCheckIn = (_nextCheck-now+2000);
|
||||
_timeoutID = setTimeout(update, nextCheckIn);
|
||||
Zotero.debug("Repo: Next check in "+nextCheckIn);
|
||||
}
|
||||
}
|
|
@ -1,726 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2012 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 *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* Save translator items.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* <li>libraryID - ID of library in which items should be saved</li>
|
||||
* <li>collections - New collections to create (used during Import translation</li>
|
||||
* <li>attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved</li>
|
||||
* <li>forceTagType - Force tags to specified tag type</li>
|
||||
* <li>cookieSandbox - Cookie sandbox for attachment requests</li>
|
||||
* <li>proxy - A proxy to deproxify item URLs</li>
|
||||
* <li>baseURI - URI to which attachment paths should be relative</li>
|
||||
*
|
||||
*/
|
||||
Zotero.Translate.ItemSaver = function(options) {
|
||||
this.newItems = [];
|
||||
this._proxy = options.proxy;
|
||||
this._baseURI = options.baseURI;
|
||||
|
||||
// Add listener for callbacks, but only for Safari or the bookmarklet. In Chrome, we
|
||||
// (have to) save attachments from the inject page.
|
||||
if(Zotero.Messaging && !Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded
|
||||
&& (Zotero.isBookmarklet || Zotero.isSafari)) {
|
||||
Zotero.Messaging.addMessageListener("attachmentCallback", function(data) {
|
||||
var id = data[0],
|
||||
status = data[1];
|
||||
var callback = Zotero.Translate.ItemSaver._attachmentCallbacks[id];
|
||||
if(callback) {
|
||||
if(status === false || status === 100) {
|
||||
delete Zotero.Translate.ItemSaver._attachmentCallbacks[id];
|
||||
} else {
|
||||
data[1] = 50+data[1]/2;
|
||||
}
|
||||
callback(data[1], data[2]);
|
||||
}
|
||||
});
|
||||
Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = true;
|
||||
}
|
||||
}
|
||||
Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = false;
|
||||
Zotero.Translate.ItemSaver._attachmentCallbacks = {};
|
||||
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
|
||||
|
||||
Zotero.Translate.ItemSaver.prototype = {
|
||||
/**
|
||||
* Saves items to Standalone or the server
|
||||
* @param items Items in Zotero.Item.toArray() format
|
||||
* @param {Function} [attachmentCallback] A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
*/
|
||||
saveItems: function (items, attachmentCallback) {
|
||||
var deferred = Zotero.Promise.defer();
|
||||
// first try to save items via connector
|
||||
var payload = { items, uri: this._baseURI };
|
||||
if (Zotero.isSafari) {
|
||||
// This is the best in terms of cookies we can do in Safari
|
||||
payload.cookie = document.cookie;
|
||||
}
|
||||
payload.proxy = this._proxy && this._proxy.toJSON();
|
||||
Zotero.Connector.setCookiesThenSaveItems(payload, function(data, status) {
|
||||
if(data !== false) {
|
||||
Zotero.debug("Translate: Save via Standalone succeeded");
|
||||
var haveAttachments = false;
|
||||
if(data && data.items) {
|
||||
for(var i=0; i<data.items.length; i++) {
|
||||
var attachments = items[i].attachments = data.items[i].attachments;
|
||||
for(var j=0; j<attachments.length; j++) {
|
||||
if(attachments[j].id) {
|
||||
attachmentCallback(attachments[j], 0);
|
||||
haveAttachments = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deferred.resolve(items);
|
||||
if (haveAttachments) this._pollForProgress(items, attachmentCallback);
|
||||
} else if(Zotero.isFx) {
|
||||
deferred.reject(new Error("Save via Standalone failed with " + status));
|
||||
} else {
|
||||
deferred.resolve(this._saveToServer(items, attachmentCallback));
|
||||
}
|
||||
}.bind(this));
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Polls for updates to attachment progress
|
||||
* @param items Items in Zotero.Item.toArray() format
|
||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
* attachmentCallback() will be called with all attachments that will be saved
|
||||
*/
|
||||
"_pollForProgress":function(items, attachmentCallback) {
|
||||
var attachments = [];
|
||||
var progressIDs = [];
|
||||
var previousStatus = [];
|
||||
for(var i=0; i<items.length; i++) {
|
||||
var itemAttachments = items[i].attachments;
|
||||
for(var j=0; j<itemAttachments.length; j++) {
|
||||
if(itemAttachments[j].id) {
|
||||
attachments.push(itemAttachments[j]);
|
||||
progressIDs.push(itemAttachments[j].id);
|
||||
previousStatus.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nPolls = 0;
|
||||
var poll = function() {
|
||||
Zotero.Connector.callMethod("attachmentProgress", progressIDs, function(currentStatus, status) {
|
||||
if(currentStatus) {
|
||||
for(var i=0; i<attachments.length; i++) {
|
||||
if(currentStatus[i] === 100 || currentStatus[i] === false) {
|
||||
attachmentCallback(attachments[i], currentStatus[i]);
|
||||
attachments.splice(i, 1);
|
||||
progressIDs.splice(i, 1);
|
||||
previousStatus.splice(i, 1);
|
||||
currentStatus.splice(i, 1);
|
||||
i--;
|
||||
} else if(currentStatus[i] !== previousStatus[i]) {
|
||||
attachmentCallback(attachments[i], currentStatus[i]);
|
||||
previousStatus[i] = currentStatus[i];
|
||||
}
|
||||
}
|
||||
|
||||
if(nPolls++ < 60 && attachments.length) {
|
||||
setTimeout(poll, 1000);
|
||||
}
|
||||
} else {
|
||||
for(var i=0; i<attachments.length; i++) {
|
||||
attachmentCallback(attachments[i], false, "Lost connection to Zotero Standalone");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
poll();
|
||||
},
|
||||
|
||||
// ALL CODE BELOW THIS POINT IS EXECUTED ONLY IN NON-FIREFOX ENVIRONMENTS
|
||||
|
||||
/**
|
||||
* Saves items to server
|
||||
* @param items Items in Zotero.Item.toArray() format
|
||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
* attachmentCallback() will be called with all attachments that will be saved
|
||||
*/
|
||||
_saveToServer: function (items, attachmentCallback) {
|
||||
var newItems = [], itemIndices = [], typedArraysSupported = false;
|
||||
try {
|
||||
typedArraysSupported = !!(new Uint8Array(1) && new Blob());
|
||||
} catch(e) {}
|
||||
|
||||
for(var i=0, n=items.length; i<n; i++) {
|
||||
var item = items[i];
|
||||
// deproxify url
|
||||
if (this._proxy && item.url) {
|
||||
item.url = this._proxy.toProper(item.url);
|
||||
}
|
||||
itemIndices[i] = newItems.length;
|
||||
newItems = newItems.concat(Zotero.Utilities.itemToServerJSON(item));
|
||||
if(typedArraysSupported) {
|
||||
for(var j=0; j<item.attachments.length; j++) {
|
||||
item.attachments[j].id = Zotero.Utilities.randomString();
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
}
|
||||
|
||||
var deferred = Zotero.Promise.defer();
|
||||
Zotero.API.createItem({"items":newItems}, function(statusCode, response) {
|
||||
if(statusCode !== 200) {
|
||||
deferred.reject(new Error("Save to server failed with " + statusCode + " " + response));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var resp = JSON.parse(response);
|
||||
} catch(e) {
|
||||
deferred.reject(new Error("Unexpected response received from server"));
|
||||
return;
|
||||
}
|
||||
for(var i in resp.failed) {
|
||||
deferred.reject(new Error("Save to server failed with " + statusCode + " " + response));
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Translate: Save to server complete");
|
||||
Zotero.Prefs.getCallback(
|
||||
["downloadAssociatedFiles", "automaticSnapshots"],
|
||||
function (prefs) {
|
||||
if(typedArraysSupported) {
|
||||
for(var i=0; i<items.length; i++) {
|
||||
var item = items[i], key = resp.success[itemIndices[i]];
|
||||
if(item.attachments && item.attachments.length) {
|
||||
this._saveAttachmentsToServer(key, this._getFileBaseNameFromItem(item),
|
||||
item.attachments, prefs, attachmentCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
deferred.resolve(items);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves an attachment to server
|
||||
* @param {String} itemKey The key of the parent item
|
||||
* @param {String} baseName A string to use as the base name for attachments
|
||||
* @param {Object[]} attachments An array of attachment objects
|
||||
* @param {Object} prefs An object with the values of the downloadAssociatedFiles and automaticSnapshots preferences
|
||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
*/
|
||||
"_saveAttachmentsToServer":function(itemKey, baseName, attachments, prefs, attachmentCallback) {
|
||||
var me = this,
|
||||
uploadAttachments = [],
|
||||
retrieveHeadersForAttachments = attachments.length;
|
||||
|
||||
/**
|
||||
* Creates attachments on the z.org server. This is executed after we have received
|
||||
* headers for all attachments to be downloaded, but before they are uploaded to
|
||||
* z.org.
|
||||
* @inner
|
||||
*/
|
||||
var createAttachments = function() {
|
||||
if(uploadAttachments.length === 0) return;
|
||||
var attachmentPayload = [];
|
||||
for(var i=0; i<uploadAttachments.length; i++) {
|
||||
var attachment = uploadAttachments[i];
|
||||
// deproxify url
|
||||
if (this._proxy && attachment.url) {
|
||||
attachment.url = this._proxy.toProper(attachment.url);
|
||||
}
|
||||
attachmentPayload.push({
|
||||
"itemType":"attachment",
|
||||
"parentItem":itemKey,
|
||||
"linkMode":attachment.linkMode,
|
||||
"title":(attachment.title ? attachment.title.toString() : "Untitled Attachment"),
|
||||
"accessDate":"CURRENT_TIMESTAMP",
|
||||
"url":attachment.url,
|
||||
"note":(attachment.note ? attachment.note.toString() : ""),
|
||||
"tags":(attachment.tags && attachment.tags instanceof Array ? attachment.tags : [])
|
||||
});
|
||||
}
|
||||
|
||||
Zotero.API.createItem({"items":attachmentPayload}, function(statusCode, response) {
|
||||
var resp;
|
||||
if(statusCode === 200) {
|
||||
try {
|
||||
resp = JSON.parse(response);
|
||||
if(!resp.success) resp = undefined;
|
||||
} catch(e) {};
|
||||
}
|
||||
|
||||
Zotero.debug("Finished creating items");
|
||||
for(var i=0; i<uploadAttachments.length; i++) {
|
||||
var attachment = uploadAttachments[i];
|
||||
if(!resp || !resp.success[i]) {
|
||||
attachmentCallback(attachment, false,
|
||||
new Error("Unexpected response received from server "+statusCode+" "+response));
|
||||
} else {
|
||||
attachment.key = resp.success[i];
|
||||
|
||||
if(attachment.linkMode === "linked_url") {
|
||||
attachmentCallback(attachment, 100);
|
||||
} else if("data" in attachment) {
|
||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
for(var i=0; i<attachments.length; i++) {
|
||||
// Also begin to download attachments
|
||||
(function(attachment) {
|
||||
var headersValidated = null;
|
||||
|
||||
// Ensure these are undefined before continuing, since we'll use them to determine
|
||||
// whether an attachment has been created on the Zotero server and downloaded from
|
||||
// the host
|
||||
delete attachment.key;
|
||||
delete attachment.data;
|
||||
|
||||
var isSnapshot = false;
|
||||
if(attachment.mimeType) {
|
||||
switch(attachment.mimeType.toLowerCase()) {
|
||||
case "text/html":
|
||||
case "application/xhtml+xml":
|
||||
isSnapshot = true;
|
||||
}
|
||||
}
|
||||
|
||||
if((isSnapshot && !prefs.automaticSnapshots) || (!isSnapshot && !prefs.downloadAssociatedFiles)) {
|
||||
// Check preferences to see if we should download this file
|
||||
if(--retrieveHeadersForAttachments === 0) createAttachments();
|
||||
return;
|
||||
} else if(attachment.snapshot === false && attachment.mimeType) {
|
||||
// If we aren't taking a snapshot and we have the MIME type, we don't need
|
||||
// to retrieve anything
|
||||
attachment.linkMode = "linked_url";
|
||||
uploadAttachments.push(attachment);
|
||||
if(attachmentCallback) attachmentCallback(attachment, 0);
|
||||
if(--retrieveHeadersForAttachments === 0) createAttachments();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks headers to ensure that they reflect our expectations. When headers have
|
||||
* been checked for all attachments, creates new items on the z.org server and
|
||||
* begins uploading them.
|
||||
* @inner
|
||||
*/
|
||||
var checkHeaders = function() {
|
||||
if(headersValidated !== null) return headersValidated;
|
||||
|
||||
retrieveHeadersForAttachments--;
|
||||
headersValidated = false;
|
||||
|
||||
var err = null,
|
||||
status = xhr.status;
|
||||
|
||||
// Validate status
|
||||
if(status === 0 || attachment.snapshot === false) {
|
||||
// Failed due to SOP, or we are supposed to be getting a snapshot
|
||||
attachment.linkMode = "linked_url";
|
||||
} else if(status !== 200) {
|
||||
err = new Error("Server returned unexpected status code "+status);
|
||||
} else {
|
||||
// Validate content type
|
||||
var contentType = "application/octet-stream",
|
||||
charset = null,
|
||||
contentTypeHeader = xhr.getResponseHeader("Content-Type");
|
||||
if(contentTypeHeader) {
|
||||
// See RFC 2616 sec 3.7
|
||||
var m = /^[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+\/[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+/.exec(contentTypeHeader);
|
||||
if(m) contentType = m[0].toLowerCase();
|
||||
m = /;\s*charset\s*=\s*("[^"]+"|[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+)/.exec(contentTypeHeader);
|
||||
if(m) {
|
||||
charset = m[1];
|
||||
if(charset[0] === '"') charset = charset.substring(1, charset.length-1);
|
||||
}
|
||||
|
||||
if(attachment.mimeType
|
||||
&& attachment.mimeType.toLowerCase() !== contentType.toLowerCase()) {
|
||||
err = new Error("Attachment MIME type "+contentType+
|
||||
" does not match specified type "+attachment.mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
if(!err) {
|
||||
attachment.mimeType = contentType;
|
||||
attachment.linkMode = "imported_url";
|
||||
switch(contentType.toLowerCase()) {
|
||||
case "application/pdf":
|
||||
attachment.filename = baseName+".pdf";
|
||||
break;
|
||||
case "text/html":
|
||||
case "application/xhtml+xml":
|
||||
attachment.filename = baseName+".html";
|
||||
break;
|
||||
default:
|
||||
attachment.filename = baseName;
|
||||
}
|
||||
if(charset) attachment.charset = charset;
|
||||
headersValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't validate the headers, cancel the request
|
||||
if(headersValidated === false && "abort" in xhr) xhr.abort();
|
||||
|
||||
// Add attachments to attachment payload if there was no error
|
||||
if(!err) {
|
||||
uploadAttachments.push(attachment);
|
||||
}
|
||||
|
||||
// If we have retrieved the headers for all attachments, create items on z.org
|
||||
// server
|
||||
if(retrieveHeadersForAttachments === 0) createAttachments();
|
||||
|
||||
// If there was an error, throw it now
|
||||
if(err) {
|
||||
attachmentCallback(attachment, false, err);
|
||||
Zotero.logError(err);
|
||||
}
|
||||
return headersValidated;
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open((attachment.snapshot === false ? "HEAD" : "GET"), attachment.url, true);
|
||||
xhr.responseType = (isSnapshot ? "document" : "arraybuffer");
|
||||
xhr.onreadystatechange = function() {
|
||||
if(xhr.readyState !== 4 || !checkHeaders()) return;
|
||||
|
||||
attachmentCallback(attachment, 50);
|
||||
attachment.data = xhr.response;
|
||||
// If item already created, head to upload
|
||||
if("key" in attachment) {
|
||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
||||
}
|
||||
};
|
||||
xhr.onprogress = function(event) {
|
||||
if(xhr.readyState < 2 || !checkHeaders()) return;
|
||||
|
||||
if(event.total && attachmentCallback) {
|
||||
attachmentCallback(attachment, event.loaded/event.total*50);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
|
||||
if(attachmentCallback) attachmentCallback(attachment, 0);
|
||||
})(attachments[i]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Uploads an attachment to the Zotero server
|
||||
* @param {Object} attachment Attachment object, including
|
||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
*/
|
||||
"_uploadAttachmentToServer":function(attachment, attachmentCallback) {
|
||||
Zotero.debug("Uploading attachment to server");
|
||||
switch(attachment.mimeType.toLowerCase()) {
|
||||
case "text/html":
|
||||
case "application/xhtml+xml":
|
||||
// It's possible that we didn't know if this was a snapshot until after the
|
||||
// download began. If this is the case, we need to convert it to a document.
|
||||
if(attachment.data instanceof ArrayBuffer) {
|
||||
var me = this,
|
||||
blob = new Blob([attachment.data], {"type":attachment.mimeType}),
|
||||
reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
if(reader.error) {
|
||||
attachmentCallback(attachment, false, reader.error);
|
||||
} else {
|
||||
// Convert to an HTML document
|
||||
var result = reader.result, doc;
|
||||
try {
|
||||
// First try using DOMParser
|
||||
doc = (new DOMParser()).parseFromString(result, "text/html");
|
||||
} catch(e) {}
|
||||
|
||||
// If DOMParser fails, use document.implementation.createHTMLDocument,
|
||||
// as documented at https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
|
||||
if(!doc) {
|
||||
doc = document.implementation.createHTMLDocument("");
|
||||
var docEl = doc.documentElement;
|
||||
// AMO reviewer: This code is not run in Firefox, and the document
|
||||
// is never rendered anyway
|
||||
docEl.innerHTML = result;
|
||||
if(docEl.children.length === 1 && docEl.firstElementChild === "html") {
|
||||
doc.replaceChild(docEl.firstElementChild, docEl);
|
||||
}
|
||||
}
|
||||
|
||||
attachment.data = doc;
|
||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
||||
}
|
||||
}
|
||||
reader.readAsText(blob, attachment.charset || "iso-8859-1");
|
||||
return;
|
||||
}
|
||||
|
||||
// We are now assured that attachment.data is an HTMLDocument, so we can
|
||||
// add a base tag
|
||||
|
||||
// Get the head tag
|
||||
var doc = attachment.data,
|
||||
head = doc.getElementsByTagName("head");
|
||||
if(!head.length) {
|
||||
head = doc.createElement("head");
|
||||
var docEl = attachment.data.documentElement;
|
||||
docEl.insertBefore(head, docEl.firstChildElement);
|
||||
} else {
|
||||
head = head[0];
|
||||
}
|
||||
|
||||
// Add the base tag
|
||||
var base = doc.createElement("base");
|
||||
base.href = attachment.url;
|
||||
head.appendChild(base);
|
||||
|
||||
// Remove content type tags
|
||||
var metaTags = doc.getElementsByTagName("meta"), metaTag;
|
||||
for(var i=0; i<metaTags.length; i++) {
|
||||
metaTag = metaTags[i];
|
||||
var attr = metaTag.getAttribute("http-equiv");
|
||||
if(attr && attr.toLowerCase() === "content-type") {
|
||||
metaTag.parentNode.removeChild(metaTag);
|
||||
}
|
||||
}
|
||||
|
||||
// Add UTF-8 content type
|
||||
metaTag = doc.createElement("meta");
|
||||
metaTag.setAttribute("http-equiv", "Content-Type");
|
||||
metaTag.setAttribute("content", attachment.mimeType+"; charset=UTF-8");
|
||||
head.insertBefore(metaTag, head.firstChild);
|
||||
|
||||
// Serialize document to UTF-8
|
||||
var src = new XMLSerializer().serializeToString(doc),
|
||||
srcLength = Zotero.Utilities.getStringByteLength(src),
|
||||
srcArray = new Uint8Array(srcLength);
|
||||
Zotero.Utilities.stringToUTF8Array(src, srcArray);
|
||||
|
||||
// Rewrite data
|
||||
attachment.data = srcArray.buffer;
|
||||
attachment.charset = "UTF-8";
|
||||
break;
|
||||
}
|
||||
|
||||
var binaryHash = this._md5(new Uint8Array(attachment.data), 0, attachment.data.byteLength),
|
||||
hash = "";
|
||||
for(var i=0; i<binaryHash.length; i++) {
|
||||
if(binaryHash[i] < 16) hash += "0";
|
||||
hash += binaryHash[i].toString(16);
|
||||
}
|
||||
attachment.md5 = hash;
|
||||
|
||||
if(Zotero.isBrowserExt && !Zotero.isBookmarklet) {
|
||||
// In Chrome, we don't use messaging for Zotero.API.uploadAttachment, since
|
||||
// we can't pass ArrayBuffers to the background page
|
||||
Zotero.API.uploadAttachment(attachment, attachmentCallback.bind(this, attachment));
|
||||
} else {
|
||||
// In Safari and the connectors, we can pass ArrayBuffers
|
||||
Zotero.Translate.ItemSaver._attachmentCallbacks[attachment.id] = function(status, error) {
|
||||
attachmentCallback(attachment, status, error);
|
||||
};
|
||||
Zotero.API.uploadAttachment(attachment);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the base name for an attachment from an item object. This mimics the default behavior
|
||||
* of Zotero.Attachments.getFileBaseNameFromItem
|
||||
* @param {Object} item
|
||||
*/
|
||||
"_getFileBaseNameFromItem":function(item) {
|
||||
var parts = [];
|
||||
if(item.creators && item.creators.length) {
|
||||
if(item.creators.length === 1) {
|
||||
parts.push(item.creators[0].lastName);
|
||||
} else if(item.creators.length === 2) {
|
||||
parts.push(item.creators[0].lastName+" and "+item.creators[1].lastName);
|
||||
} else {
|
||||
parts.push(item.creators[0].lastName+" et al.");
|
||||
}
|
||||
}
|
||||
|
||||
if(item.date) {
|
||||
var date = Zotero.Date.strToDate(item.date);
|
||||
if(date.year) parts.push(date.year);
|
||||
}
|
||||
|
||||
if(item.title) {
|
||||
parts.push(item.title.substr(0, 50));
|
||||
}
|
||||
|
||||
if(parts.length) return parts.join(" - ");
|
||||
return "Attachment";
|
||||
},
|
||||
|
||||
/*
|
||||
pdf.js MD5 implementation
|
||||
Copyright (c) 2011 Mozilla Foundation
|
||||
|
||||
Contributors: Andreas Gal <gal@mozilla.com>
|
||||
Chris G Jones <cjones@mozilla.com>
|
||||
Shaon Barman <shaon.barman@gmail.com>
|
||||
Vivien Nicolas <21@vingtetun.org>
|
||||
Justin D'Arcangelo <justindarc@gmail.com>
|
||||
Yury Delendik
|
||||
Kalervo Kujala
|
||||
Adil Allawi <@ironymark>
|
||||
Jakob Miland <saebekassebil@gmail.com>
|
||||
Artur Adib <aadib@mozilla.com>
|
||||
Brendan Dahl <bdahl@mozilla.com>
|
||||
David Quintana <gigaherz@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
"_md5":(function calculateMD5Closure() {
|
||||
// Don't throw if typed arrays are not supported
|
||||
try {
|
||||
var r = new Uint8Array([
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
|
||||
|
||||
var k = new Int32Array([
|
||||
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
|
||||
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
|
||||
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
|
||||
643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
|
||||
568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
|
||||
1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
|
||||
-1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
|
||||
-722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
|
||||
-198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
|
||||
-1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
|
||||
-145523070, -1120210379, 718787259, -343485551]);
|
||||
} catch(e) {};
|
||||
|
||||
function hash(data, offset, length) {
|
||||
var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
|
||||
// pre-processing
|
||||
var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
|
||||
var padded = new Uint8Array(paddedLength);
|
||||
var i, j, n;
|
||||
if (offset || length != data.byteLength) {
|
||||
padded.set(new Uint8Array(data.buffer, offset, length));
|
||||
} else {
|
||||
padded.set(data);
|
||||
}
|
||||
i = length;
|
||||
padded[i++] = 0x80;
|
||||
n = paddedLength - 8;
|
||||
while (i < n)
|
||||
padded[i++] = 0;
|
||||
padded[i++] = (length << 3) & 0xFF;
|
||||
padded[i++] = (length >> 5) & 0xFF;
|
||||
padded[i++] = (length >> 13) & 0xFF;
|
||||
padded[i++] = (length >> 21) & 0xFF;
|
||||
padded[i++] = (length >>> 29) & 0xFF;
|
||||
padded[i++] = 0;
|
||||
padded[i++] = 0;
|
||||
padded[i++] = 0;
|
||||
// chunking
|
||||
// TODO ArrayBuffer ?
|
||||
var w = new Int32Array(16);
|
||||
for (i = 0; i < paddedLength;) {
|
||||
for (j = 0; j < 16; ++j, i += 4) {
|
||||
w[j] = (padded[i] | (padded[i + 1] << 8) |
|
||||
(padded[i + 2] << 16) | (padded[i + 3] << 24));
|
||||
}
|
||||
var a = h0, b = h1, c = h2, d = h3, f, g;
|
||||
for (j = 0; j < 64; ++j) {
|
||||
if (j < 16) {
|
||||
f = (b & c) | ((~b) & d);
|
||||
g = j;
|
||||
} else if (j < 32) {
|
||||
f = (d & b) | ((~d) & c);
|
||||
g = (5 * j + 1) & 15;
|
||||
} else if (j < 48) {
|
||||
f = b ^ c ^ d;
|
||||
g = (3 * j + 5) & 15;
|
||||
} else {
|
||||
f = c ^ (b | (~d));
|
||||
g = (7 * j) & 15;
|
||||
}
|
||||
var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
|
||||
d = c;
|
||||
c = b;
|
||||
b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
|
||||
a = tmp;
|
||||
}
|
||||
h0 = (h0 + a) | 0;
|
||||
h1 = (h1 + b) | 0;
|
||||
h2 = (h2 + c) | 0;
|
||||
h3 = (h3 + d) | 0;
|
||||
}
|
||||
return new Uint8Array([
|
||||
h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
|
||||
h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
|
||||
h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
|
||||
h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
|
||||
]);
|
||||
}
|
||||
return hash;
|
||||
})()
|
||||
};
|
|
@ -1,473 +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 *****
|
||||
*/
|
||||
|
||||
// Enumeration of types of translators
|
||||
var 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
|
||||
* @param {Zotero.Translate[]} [translators] List of translators. If not specified, it will be
|
||||
* retrieved from storage.
|
||||
*/
|
||||
this.init = function(translators) {
|
||||
if(!translators) {
|
||||
translators = [];
|
||||
if((Zotero.isBrowserExt || Zotero.isSafari) && localStorage["translatorMetadata"]) {
|
||||
try {
|
||||
translators = JSON.parse(localStorage["translatorMetadata"]);
|
||||
if(typeof translators !== "object") {
|
||||
translators = [];
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
_cache = {"import":[], "export":[], "web":[], "search":[]};
|
||||
_translators = {};
|
||||
_initialized = true;
|
||||
|
||||
// Build caches
|
||||
for(var i=0; i<translators.length; i++) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
try {
|
||||
Zotero.logError("Could not load translator "+JSON.stringify(translators[i]));
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, without attempting to retrieve code
|
||||
* @param {String} id The ID of the translator
|
||||
*/
|
||||
this.getWithoutCode = function(id) {
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
return _translators[id] ? _translators[id] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translator that corresponds to a given ID
|
||||
*
|
||||
* @param {String} id The ID of the translator
|
||||
*/
|
||||
this.get = Zotero.Promise.method(function (id) {
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
var translator = _translators[id];
|
||||
if(!translator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only need to get code if it is of some use
|
||||
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER
|
||||
&& !translator.hasOwnProperty("code")) {
|
||||
return translator.getCode().then(() => translator);
|
||||
} else {
|
||||
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 {Boolean} [debugMode] Whether to assume debugging mode. If true, code is included for
|
||||
* unsupported translators, and code originally retrieved from the
|
||||
* repo is re-retrieved from Zotero Standalone.
|
||||
*/
|
||||
this.getAllForType = Zotero.Promise.method(function (type, debugMode) {
|
||||
if(!_initialized) Zotero.Translators.init()
|
||||
var translators = _cache[type].slice(0);
|
||||
var codeGetter = new Zotero.Translators.CodeGetter(translators, debugMode);
|
||||
return codeGetter.getAll().then(function() {
|
||||
return translators;
|
||||
});;
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets web translators for a specific location
|
||||
* @param {String} uri The URI for which to look for translators
|
||||
* @return {Promise<Array[]>} - A promise for a 2-item array containing an array of translators and
|
||||
* an array of functions for converting URLs from proper to proxied forms
|
||||
*/
|
||||
this.getWebTranslatorsForLocation = Zotero.Promise.method(function (URI, rootURI) {
|
||||
var isFrame = URI !== rootURI;
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
var allTranslators = _cache["web"];
|
||||
var potentialTranslators = [];
|
||||
var proxies = [];
|
||||
|
||||
var rootSearchURIs = Zotero.Proxies.getPotentialProxies(rootURI);
|
||||
var frameSearchURIs = isFrame ? Zotero.Proxies.getPotentialProxies(URI) : rootSearchURIs;
|
||||
|
||||
Zotero.debug("Translators: Looking for translators for "+Object.keys(frameSearchURIs).join(', '));
|
||||
|
||||
for(var i=0; i<allTranslators.length; i++) {
|
||||
var translator = allTranslators[i];
|
||||
if (isFrame && !translator.webRegexp.all) {
|
||||
continue;
|
||||
}
|
||||
rootURIsLoop:
|
||||
for(var rootSearchURI in rootSearchURIs) {
|
||||
var isGeneric = !allTranslators[i].webRegexp.root;
|
||||
// don't attempt to use generic translators that can't be run in this browser
|
||||
// since that would require transmitting every page to Zotero host
|
||||
if(isGeneric && allTranslators[i].runMode !== Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var rootURIMatches = isGeneric || rootSearchURI.length < 8192 && translator.webRegexp.root.test(rootSearchURI);
|
||||
if (translator.webRegexp.all && rootURIMatches) {
|
||||
for (var frameSearchURI in frameSearchURIs) {
|
||||
var frameURIMatches = frameSearchURI.length < 8192 && translator.webRegexp.all.test(frameSearchURI);
|
||||
|
||||
if (frameURIMatches) {
|
||||
potentialTranslators.push(translator);
|
||||
proxies.push(frameSearchURIs[frameSearchURI]);
|
||||
// prevent adding the translator multiple times
|
||||
break rootURIsLoop;
|
||||
}
|
||||
}
|
||||
} else if(!isFrame && (isGeneric || rootURIMatches)) {
|
||||
potentialTranslators.push(translator);
|
||||
proxies.push(rootSearchURIs[rootSearchURI]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var codeGetter = new Zotero.Translators.CodeGetter(potentialTranslators);
|
||||
return codeGetter.getAll().then(function () {
|
||||
return [potentialTranslators, proxies];
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts translators to JSON-serializable objects
|
||||
*/
|
||||
this.serialize = function(translator, properties) {
|
||||
// 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], properties);
|
||||
}
|
||||
return newTranslators;
|
||||
}
|
||||
|
||||
// handle individual translator
|
||||
var newTranslator = {};
|
||||
for(var i in properties) {
|
||||
var property = properties[i];
|
||||
newTranslator[property] = translator[property];
|
||||
}
|
||||
return newTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all translator metadata to localStorage
|
||||
* @param {Object[]} newMetadata Metadata for new translators
|
||||
* @param {Boolean} reset Whether to clear all existing translators and overwrite them with
|
||||
* the specified translators.
|
||||
*/
|
||||
this.update = function(newMetadata, reset) {
|
||||
if (!_initialized) Zotero.Translators.init();
|
||||
if (!newMetadata.length) return;
|
||||
var serializedTranslators = [];
|
||||
|
||||
if (reset) {
|
||||
serializedTranslators = newMetadata.map((t) => new Zotero.Translator(t));
|
||||
}
|
||||
else {
|
||||
var hasChanged = false;
|
||||
|
||||
// Update translators with new metadata
|
||||
for(var i in newMetadata) {
|
||||
var newTranslator = newMetadata[i];
|
||||
|
||||
if(_translators.hasOwnProperty(newTranslator.translatorID)) {
|
||||
var oldTranslator = _translators[newTranslator.translatorID];
|
||||
|
||||
// check whether translator has changed
|
||||
if(oldTranslator.lastUpdated !== newTranslator.lastUpdated) {
|
||||
// check whether newTranslator is actually newer than the existing
|
||||
// translator, and if not, don't update
|
||||
if(Zotero.Date.sqlToDate(newTranslator.lastUpdated) < Zotero.Date.sqlToDate(oldTranslator.lastUpdated)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Zotero.debug(`Translators: Updating ${newTranslator.label}`);
|
||||
oldTranslator.init(newTranslator);
|
||||
hasChanged = true;
|
||||
}
|
||||
} else {
|
||||
Zotero.debug(`Translators: Adding ${newTranslator.label}`);
|
||||
_translators[newTranslator.translatorID] = new Zotero.Translator(newTranslator);
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
let deletedTranslators = Object.keys(_translators).filter(id => _translators[id].deleted);
|
||||
if (deletedTranslators.length) {
|
||||
hasChanged = true;
|
||||
for (let id of deletedTranslators) {
|
||||
Zotero.debug(`Translators: Removing ${_translators[id].label}`);
|
||||
delete _translators[id];
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasChanged) return;
|
||||
|
||||
// Serialize translators
|
||||
for(var i in _translators) {
|
||||
var serializedTranslator = this.serialize(_translators[i], TRANSLATOR_SAVE_PROPERTIES);
|
||||
|
||||
// don't save run mode
|
||||
delete serializedTranslator.runMode;
|
||||
|
||||
serializedTranslators.push(serializedTranslator);
|
||||
}
|
||||
}
|
||||
|
||||
// Store
|
||||
if (Zotero.isBrowserExt || Zotero.isSafari) {
|
||||
var serialized = JSON.stringify(serializedTranslators);
|
||||
localStorage["translatorMetadata"] = serialized;
|
||||
Zotero.debug("Translators: Saved updated translator list ("+serialized.length+" characters)");
|
||||
}
|
||||
|
||||
// Reinitialize
|
||||
Zotero.Translators.init(serializedTranslators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses code for a translator
|
||||
*/
|
||||
this.preprocessCode = function(code) {
|
||||
if(!Zotero.isFx) {
|
||||
const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm;
|
||||
code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+
|
||||
"for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+
|
||||
"$2$3 = $3_zForEachSubject[$3_zForEachIndex];", code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to get the code for a set of translators at once
|
||||
*
|
||||
* @param {Zotero.Translator[]} translators Translators for which to retrieve code
|
||||
* @param {Boolean} [debugMode] If true, include code for unsupported translators
|
||||
*/
|
||||
Zotero.Translators.CodeGetter = function(translators, debugMode) {
|
||||
this._translators = translators;
|
||||
this._debugMode = debugMode;
|
||||
this._concurrency = 1;
|
||||
};
|
||||
|
||||
Zotero.Translators.CodeGetter.prototype.getCodeFor = Zotero.Promise.method(function(i) {
|
||||
let translator = this._translators[i];
|
||||
// retrieve code if no code and translator is supported locally
|
||||
if((translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER && !translator.hasOwnProperty("code"))
|
||||
// or if debug mode is enabled (even if unsupported locally)
|
||||
|| (this._debugMode && (!translator.hasOwnProperty("code")
|
||||
// or if in debug mode and the code we have came from the repo (which doesn't
|
||||
// include test cases)
|
||||
|| (Zotero.Repo && translator.codeSource === Zotero.Repo.SOURCE_REPO)))) {
|
||||
// get code
|
||||
return translator.getCode().catch((e) => Zotero.debug(`Failed to retrieve code for ${translator.translatorID}`));
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Translators.CodeGetter.prototype.getAll = function () {
|
||||
var codes = [];
|
||||
// Chain promises with some level of concurrency. If unchained, fires
|
||||
// off hundreds of xhttprequests on connectors and crashes the extension
|
||||
for (let i = 0; i < this._translators.length; i++) {
|
||||
if (i < this._concurrency) {
|
||||
codes.push(this.getCodeFor(i));
|
||||
} else {
|
||||
codes.push(codes[i-this._concurrency].then(() => this.getCodeFor(i)));
|
||||
}
|
||||
}
|
||||
return Promise.all(codes);
|
||||
};
|
||||
|
||||
var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
|
||||
"priority", "lastUpdated"];
|
||||
var TRANSLATOR_OPTIONAL_PROPERTIES = ["targetAll", "browserSupport", "minVersion", "maxVersion",
|
||||
"inRepository", "configOptions", "displayOptions",
|
||||
"hiddenPrefs", "itemType"];
|
||||
var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES
|
||||
.concat(["targetAll", "browserSupport", "code", "runMode", "itemType"]);
|
||||
var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport", "targetAll"]);
|
||||
/**
|
||||
* @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
|
||||
* b = Bookmarklet
|
||||
* v = Server
|
||||
* @property {Object} configOptions Configuration options for import/export
|
||||
* @property {Object} displayOptions Display options for export
|
||||
* @property {Object} hiddenPrefs Hidden preferences configurable through about:config
|
||||
* @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) {
|
||||
this.init(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a translator from a set of info, clearing code if it is set
|
||||
*/
|
||||
Zotero.Translator.prototype.init = function(info) {
|
||||
// make sure we have all the properties
|
||||
for(var i in TRANSLATOR_REQUIRED_PROPERTIES) {
|
||||
var property = TRANSLATOR_REQUIRED_PROPERTIES[i];
|
||||
if(info[property] === undefined) {
|
||||
Zotero.logError(new Error('Missing property "'+property+'" in translator metadata JSON object in ' + info.label));
|
||||
break;
|
||||
} else {
|
||||
this[property] = info[property];
|
||||
}
|
||||
}
|
||||
for(var i=0; i<TRANSLATOR_OPTIONAL_PROPERTIES.length; i++) {
|
||||
var property = TRANSLATOR_OPTIONAL_PROPERTIES[i];
|
||||
if(info[property] !== undefined) {
|
||||
this[property] = info[property];
|
||||
}
|
||||
}
|
||||
|
||||
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
||||
|
||||
if(this.browserSupport.indexOf(Zotero.browser) !== -1) {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
} else {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
|
||||
}
|
||||
|
||||
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
||||
// compile import regexp to match only file extension
|
||||
this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
|
||||
} else if(this.hasOwnProperty("importRegexp")) {
|
||||
delete this.importRegexp;
|
||||
}
|
||||
|
||||
if(this.translatorType & TRANSLATOR_TYPES["web"]) {
|
||||
// compile web regexp
|
||||
this.webRegexp = {
|
||||
root: this.target ? new RegExp(this.target, "i") : null,
|
||||
all: this.targetAll ? new RegExp(this.targetAll, "i") : null
|
||||
};
|
||||
} else if(this.hasOwnProperty("webRegexp")) {
|
||||
delete this.webRegexp;
|
||||
}
|
||||
|
||||
if(info.code) {
|
||||
this.code = Zotero.Translators.preprocessCode(info.code);
|
||||
} else if(this.hasOwnProperty("code")) {
|
||||
delete this.code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves code for this translator
|
||||
*
|
||||
* @return {Promise<String|false>} - Promise for translator code or false if none
|
||||
*/
|
||||
Zotero.Translator.prototype.getCode = function (debugMode) {
|
||||
return Zotero.Repo.getTranslatorCode(this.translatorID, debugMode)
|
||||
.then(function (args) {
|
||||
var code = args[0];
|
||||
var source = args[1];
|
||||
if (!code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// cache code for session only (we have standalone anyway)
|
||||
this.code = code;
|
||||
this.codeSource = source;
|
||||
return code;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.logError(message);
|
||||
}
|
||||
|
||||
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
|
||||
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
|
File diff suppressed because one or more lines are too long
1
resource/schema/connectorTypeSchemaData.js
Normal file
1
resource/schema/connectorTypeSchemaData.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue