- Closes #1832, Connectors should be able to retrieve translator data and code from server in the absence of Zotero Standalone

- Closes #1831, Connectors should be able to save via API in the absence of Zotero Standalone
- Fixes Zotero.Utilities.deepCopy() for arrays
- Fixes some circumstances where an error would not be saved for future error reporting
- Fixes connector status checking
This commit is contained in:
Simon Kornblith 2011-06-30 01:08:30 +00:00
parent 2bee3ecf1e
commit b114266fb3
13 changed files with 508 additions and 307 deletions

View file

@ -30,42 +30,49 @@
/** /**
* @namespace * @namespace
*/ */
if(!Zotero.Connector) Zotero.Connector = {}; Zotero.Connector_Types = new function() {
Zotero.Connector.Types = new function() {
/** /**
* Initializes types * Initializes types
* @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema * @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
*/ */
this.init = function(typeSchema) { this.init = function() {
const schemaTypes = ["itemTypes", "creatorTypes", "fields"]; const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
// attach IDs and make referenceable by either ID or name // attach IDs and make referenceable by either ID or name
for(var i=0; i<schemaTypes.length; i++) { for(var i=0; i<schemaTypes.length; i++) {
var schemaType = schemaTypes[i]; var schemaType = schemaTypes[i];
this[schemaType] = typeSchema[schemaType]; this[schemaType] = Zotero.Utilities.deepCopy(Zotero.Connector_Types.schema[schemaType]);
for(var id in this[schemaType]) { for(var id in this[schemaType]) {
var entry = this[schemaType][id]; var entry = this[schemaType][id];
entry.id = id; entry.id = parseInt(id);
this[schemaType][entry.name] = entry; this[schemaType][entry.name] = entry;
} }
} }
} }
/**
* Passes schema to a callback
* @param {Function} callback
*/
this.getSchema = function(callback) {
callback(Zotero.Connector_Types.schema);
}
} }
Zotero.CachedTypes = function() { Zotero.CachedTypes = function() {
this.getID = function(idOrName) { this.getID = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].id; return Zotero.Connector_Types[this.schemaType][idOrName].id;
} }
this.getName = function(idOrName) { this.getName = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].name; return Zotero.Connector_Types[this.schemaType][idOrName].name;
} }
this.getLocalizedString = function(idOrName) { this.getLocalizedString = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].localizedString; return Zotero.Connector_Types[this.schemaType][idOrName].localizedString;
} }
} }
@ -74,14 +81,14 @@ Zotero.ItemTypes = new function() {
Zotero.CachedTypes.call(this); Zotero.CachedTypes.call(this);
this.getImageSrc = function(idOrName) { this.getImageSrc = function(idOrName) {
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false; if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false;
if(Zotero.isFx) { if(Zotero.isFx) {
return "chrome://zotero/skin/"+Zotero.Connector.Types["itemTypes"][idOrName].icon; return "chrome://zotero/skin/"+Zotero.Connector_Types["itemTypes"][idOrName].icon;
} else if(Zotero.isChrome) { } else if(Zotero.isChrome) {
return chrome.extension.getURL("images/"+Zotero.Connector.Types["itemTypes"][idOrName].icon); return chrome.extension.getURL("images/"+Zotero.Connector_Types["itemTypes"][idOrName].icon);
} else if(Zotero.isSafari) { } else if(Zotero.isSafari) {
return safari.extension.baseURI+"images/itemTypes/"+Zotero.Connector.Types["itemTypes"][idOrName].icon; return safari.extension.baseURI+"images/itemTypes/"+Zotero.Connector_Types["itemTypes"][idOrName].icon;
} }
} }
} }
@ -91,11 +98,11 @@ Zotero.CreatorTypes = new function() {
Zotero.CachedTypes.call(this); Zotero.CachedTypes.call(this);
this.getTypesForItemType = function(idOrName) { this.getTypesForItemType = function(idOrName) {
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false; if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false;
var itemType = Zotero.Connector.Types["itemTypes"][idOrName]; var itemType = Zotero.Connector_Types["itemTypes"][idOrName];
var creatorTypes = []; var creatorTypes = [];
for(var i=0; i<itemType.creatorTypes.length; i++) { for(var i=0; i<itemType.creatorTypes.length; i++) {
creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]); creatorTypes.push(Zotero.Connector_Types["creatorTypes"][itemType.creatorTypes[i]]);
} }
return creatorTypes; return creatorTypes;
} }
@ -107,10 +114,29 @@ Zotero.ItemFields = new function() {
this.isValidForType = function(fieldIdOrName, typeIdOrName) { this.isValidForType = function(fieldIdOrName, typeIdOrName) {
// mimics itemFields.js // mimics itemFields.js
if(!Zotero.Connector.Types["fields"][fieldIdOrName] if(!Zotero.Connector_Types["fields"][fieldIdOrName]
|| !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID"; || !Zotero.Connector_Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
return Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf( return Zotero.Connector_Types["itemTypes"][typeIdOrName].fields.indexOf(
Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1; Zotero.Connector_Types["fields"][fieldIdOrName].id) !== -1;
}
this.getFieldIDFromTypeAndBase = function(itemType, baseField) {
if(!Zotero.Connector_Types["fields"][baseField]
|| !Zotero.Connector_Types["itemTypes"][itemType]) throw "Invalid field or type ID";
// get as ID
baseField = Zotero.Connector_Types["fields"][baseField].id;
// loop through base fields for item type
var baseFields = Zotero.Connector_Types["itemTypes"][itemType]["baseFields"];
for(var i in baseFields) {
if(baseFields[i] === baseField) {
return i;
}
}
return false;
} }
} }

View file

@ -27,63 +27,16 @@ Zotero.Connector = new function() {
const CONNECTOR_URI = "http://127.0.0.1:23119/"; const CONNECTOR_URI = "http://127.0.0.1:23119/";
const CONNECTOR_SERVER_API_VERSION = 1; const CONNECTOR_SERVER_API_VERSION = 1;
this.isOnline = true; this.isOnline = null;
this.haveRefreshedData = false;
this.data = null;
/**
* Called to initialize Zotero
*/
this.init = function() {
Zotero.Connector.getData();
}
/** /**
* Checks if Zotero is online and passes current status to callback * Checks if Zotero is online and passes current status to callback
* @param {Function} callback * @param {Function} callback
*/ */
this.checkIsOnline = function(callback) { this.checkIsOnline = function(callback) {
if(Zotero.Connector.isOnline) { Zotero.Connector.callMethod("ping", {}, function(status) {
callback(true); callback(status !== false);
} else { });
Zotero.Connector.getData(callback);
}
}
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 // saner descriptions of some HTTP error codes
@ -96,25 +49,17 @@ Zotero.Connector = new function() {
this.EXCEPTION_CODES = [0, 400, 404, 412, 500, 501]; this.EXCEPTION_CODES = [0, 400, 404, 412, 500, 501];
/** /**
* Updates Zotero's status depending on the success or failure of a request * Sends the XHR to execute an RPC call.
* *
* @param {Boolean} isOnline Whether or not Zotero was online * @param {String} method RPC method. See documentation above.
* @param {Function} successCallback Function to be called after loading new data if * @param {Object} data RPC data. See documentation above.
* Zotero is online * @param {Function} callback Function to be called when requests complete.
* @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) { this.callMethod = function(method, data, callback) {
if(isOnline) { var newCallback = function(req) {
if(Zotero.Connector.haveRefreshedData) { try {
if(callback) callback(true); var isOnline = req.status !== Zotero.Connector.EXCEPTION_NOT_AVAILABLE
} else { && req.status !== Zotero.Connector.EXCEPTION_INCOMPATIBLE_VERSION;
Zotero.Connector.getData(callback);
}
} else {
if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE);
}
if(Zotero.Connector.isOnline !== isOnline) { if(Zotero.Connector.isOnline !== isOnline) {
Zotero.Connector.isOnline = isOnline; Zotero.Connector.isOnline = isOnline;
@ -123,77 +68,11 @@ Zotero.Connector = new function() {
} }
} }
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.browser, "apiVersion":CONNECTOR_SERVER_API_VERSION}),
function(req) {
var isOnline = req.status !== 0 && req.status !== 412;
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"});
}
/**
* Gives callback an object containing schema and preferences from Zotero.Connector.data
*/
this.getSchemaAndPreferences = function(callback) {
if(Zotero.Connector.data) {
callback({"schema":Zotero.Connector.data["schema"],
"preferences":Zotero.Connector.data["preferences"]});
return;
}
this.getData(function(success) {
if(success) {
callback({"schema":Zotero.Connector.data["schema"],
"preferences":Zotero.Connector.data["preferences"]});
return;
}
callback(false);
});
}
/**
* 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 !== this.EXCEPTION_NOT_AVAILABLE
&& req.status !== this.EXCEPTION_INCOMPATIBLE_VERSION, function() {
if(!callback) return;
if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) { if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) {
callback(false, req.status); Zotero.debug("Connector: Method "+method+" failed");
if(callback) callback(false, req.status);
} else { } else {
Zotero.debug("Connector: Method "+method+" succeeded");
var val = null; var val = null;
if(req.responseText) { if(req.responseText) {
if(req.getResponseHeader("Content-Type") === "application/json") { if(req.getResponseHeader("Content-Type") === "application/json") {
@ -202,9 +81,16 @@ Zotero.Connector = new function() {
val = req.responseText; val = req.responseText;
} }
} }
callback(val, req.status); if(callback) callback(val, req.status);
} }
}); } catch(e) {
}, {"Content-Type":"application/json"}); Zotero.logError(e);
return;
}
};
var uri = CONNECTOR_URI+"connector/"+method;
Zotero.HTTP.doPost(uri, JSON.stringify(data),
newCallback, {"Content-Type":"application/json"});
} }
} }

View file

@ -81,6 +81,6 @@ Zotero.Connector_Debug = new function() {
var reportID = reported[0].getAttribute('reportID'); var reportID = reported[0].getAttribute('reportID');
callback(true, reportID); callback(true, reportID);
}, {"Content-Type":"application/octet-stream"}); }, {"Content-Type":"text/plain"});
} }
} }

View file

@ -0,0 +1,170 @@
/*
***** 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;
/**
* 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());
};
/**
* Reset all translators and code
*/
this.reset = function(callback) {
Zotero.Prefs.set("connector.repo.lastCheck.repoTime", 0);
this.update(true);
};
/**
* Force updating translators
*/
var update = this.update = function(reset, callback) {
_updateFromStandalone(true, reset);
};
/**
* Get translator code from repository
* @param {String} translatorID ID of the translator to retrieve code for
* @param {Function} callback Callback to pass code when retreived
*/
this.getTranslatorCode = function(translatorID, callback) {
// we might have code in localstorage
if(!Zotero.isFx) {
var localCode = localStorage[TRANSLATOR_CODE_PREFIX+translatorID];
if(localCode) {
callback(localCode);
return;
}
}
// otherwise, try standalone
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) {
if(result) {
_haveCode(result, translatorID, callback);
return;
}
// then try repo
Zotero.HTTP.doGet(ZOTERO_CONFIG.REPOSITORY_URL+"/code/"+translatorID, function(xmlhttp) {
_haveCode(xmlhttp.status === 200 ? xmlhttp.responseText : false, translatorID, callback);
});
});
};
/**
* Called when code has been retrieved from standalone or repo
*/
function _haveCode(code, translatorID, callback) {
if(!code) {
callback(false);
return;
}
if(!Zotero.isFx) {
localStorage["translatorCode-"+translatorID] = Zotero.Translators.preprocessCode(code);
}
callback(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 {
_handleResponse(result, reset);
if(callback) callback(!!result);
}
});
}
/**
* Retrieve metadata from repository
*/
function _updateFromRepo(reset, callback) {
var url = ZOTERO_CONFIG.REPOSITORY_URL+"/metadata?last="+
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);
}
}

View file

@ -25,6 +25,9 @@
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
this.newItems = []; this.newItems = [];
this._itemsToSaveToServer = [];
this._timeoutID = null;
} }
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
@ -43,6 +46,84 @@ Zotero.Translate.ItemSaver.prototype = {
// save items // save items
this.newItems.push(item); this.newItems.push(item);
Zotero.Connector.callMethod("saveItems", {"items":[item]}, function(success) {}); var me = this;
Zotero.Connector.callMethod("saveItems", {"items":[item]}, function(success) {
if(success === false && !Zotero.isFx) {
// attempt to save to server on a timer
if(me._timeoutID) clearTimeout(me._timeoutID);
me._itemsToSaveToServer.push(item);
setTimeout(function() { me._saveToServer() }, 2000);
}
});
},
"_saveToServer":function() {
const IGNORE_FIELDS = ["seeAlso", "attachments", "complete"];
// clear timeout, since saving has begin
this._timeoutID = null;
var newItems = new Array(this._itemsToSaveToServer.length);
for(var i in this._itemsToSaveToServer) {
var item = this._itemsToSaveToServer[i];
var newItem = newItems[i] = {};
var typeID = Zotero.ItemTypes.getID(item.itemType);
var fieldID;
for(var field in item) {
if(IGNORE_FIELDS.indexOf(field) !== -1) continue;
var val = item[field];
if(field === "itemType") {
newItem[field] = val;
} else if(field === "creators") {
// TODO normalize
newItem[field] = val;
} else if(field === "tags") {
// TODO normalize
newItem[field] = val;
} else if(field === "notes") {
// TODO normalize
newItem[field] = val;
} else if(fieldID = Zotero.ItemFields.getID(field)) {
// if content is not a string, either stringify it or delete it
if(typeof val !== "string") {
if(val || val === 0) {
val = val.toString();
} else {
continue;
}
}
// map from base field if possible
var itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID);
if(itemFieldID) {
newItem[Zotero.ItemFields.getName(itemFieldID)] = val;
continue; // already know this is valid
}
// if field is valid for this type, set field
if(Zotero.ItemFields.isValidForType(fieldID, typeID)) {
newItem[field] = val;
} else {
Zotero.debug("Translate: Discarded field "+field+": field not valid for type "+item.itemType, 3);
}
} else if(field !== "complete") {
Zotero.debug("Translate: Discarded unknown field "+field, 3);
}
}
}
var url = 'users/%%USERID%%/items?key=%%APIKEY%%';
var payload = JSON.stringify({"items":newItems});
this._itemsToSaveToServer = [];
Zotero.OAuth.doAuthenticatedPost(url, payload, function(status, message) {
if(!status) {
Zotero.Messaging.sendMessage("saveDialog_error", status);
throw new Error("Translate: Save to server failed: "+message);
}
}, true);
} }
}; };

View file

@ -36,15 +36,28 @@ Zotero.Translators = new function() {
/** /**
* Initializes translator cache, loading all relevant translators into memory * 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() { this.init = function(translators) {
if(!translators) {
translators = [];
if(!Zotero.isFx && localStorage["translatorMetadata"]) {
try {
translators = JSON.parse(localStorage["translatorMetadata"]);
if(typeof translators !== "object") {
translators = [];
}
} catch(e) {}
}
}
_cache = {"import":[], "export":[], "web":[], "search":[]}; _cache = {"import":[], "export":[], "web":[], "search":[]};
_translators = {}; _translators = {};
_initialized = true; _initialized = true;
// Build caches // Build caches
var translators = Zotero.Connector.data.translators; for(var i in translators) {
for(var i=0; i<translators.length; i++) {
var translator = new Zotero.Translator(translators[i]); var translator = new Zotero.Translator(translators[i]);
_translators[translator.translatorID] = translator; _translators[translator.translatorID] = translator;
@ -185,24 +198,104 @@ Zotero.Translators = new function() {
/** /**
* Converts translators to JSON-serializable objects * Converts translators to JSON-serializable objects
*/ */
this.serialize = function(translator) { this.serialize = function(translator, properties) {
// handle translator arrays // handle translator arrays
if(translator.length !== undefined) { if(translator.length !== undefined) {
var newTranslators = new Array(translator.length); var newTranslators = new Array(translator.length);
for(var i in translator) { for(var i in translator) {
newTranslators[i] = Zotero.Translators.serialize(translator[i]); newTranslators[i] = Zotero.Translators.serialize(translator[i], properties);
} }
return newTranslators; return newTranslators;
} }
// handle individual translator // handle individual translator
var newTranslator = {}; var newTranslator = {};
for(var i in PRESERVE_PROPERTIES) { for(var i in properties) {
var property = PRESERVE_PROPERTIES[i]; var property = properties[i];
newTranslator[property] = translator[property]; newTranslator[property] = translator[property];
} }
return newTranslator; return newTranslator;
} }
/**
* Saves all translator data to localStorage
*/
this.update = function(newMetadata, reset) {
if(!_initialized) Zotero.Translators.init();
if(!newMetadata.length) return;
if(reset) {
var serializedTranslators = newMetadata;
if(!Zotero.isFx) {
// clear cached translatorCode
Zotero.debug("Translators: Resetting translators");
for(var i in localStorage) {
if(i.substr(0, TRANSLATOR_CODE_PREFIX.length) === TRANSLATOR_CODE_PREFIX) {
delete localStorage[i];
}
}
}
} else {
var serializedTranslators = [];
var hasChanged = false;
// Update translators with new metadata
for(var i in newMetadata) {
var newTranslator = new Zotero.Translator(newMetadata[i]);
if(_translators.hasOwnProperty(newTranslator.translatorID)) {
if(_translators[newTranslator.translatorID].lastUpdated !== newTranslator.lastUpdated) {
if(!Zotero.isFx) {
// if lastUpdated does not match between old and new translator
// invalidate translator code cache
delete localStorage["translatorCode-"+newTranslator.translatorID];
}
Zotero.debug("Translators: Updating "+newTranslator.label);
hasChanged = true;
}
} else {
Zotero.debug("Translators: Adding "+newTranslator.label);
hasChanged = true;
}
_translators[newTranslator.translatorID] = newTranslator;
}
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.isFx) {
localStorage["translatorMetadata"] = JSON.stringify(serializedTranslators);
}
// 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;
}
} }
/** /**
@ -242,10 +335,11 @@ Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
} }
} }
const TRANSLATOR_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", const TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
"priority", "browserSupport"]; "priority"];
var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions", var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["displayOptions", "configOptions",
"code", "runMode"]); "browserSupport", "code", "runMode"]);
var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]);
/** /**
* @class Represents an individual translator * @class Represents an individual translator
* @constructor * @constructor
@ -270,8 +364,8 @@ var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "confi
*/ */
Zotero.Translator = function(info) { Zotero.Translator = function(info) {
// make sure we have all the properties // make sure we have all the properties
for(var i in TRANSLATOR_PROPERTIES) { for(var i in TRANSLATOR_REQUIRED_PROPERTIES) {
var property = TRANSLATOR_PROPERTIES[i]; var property = TRANSLATOR_REQUIRED_PROPERTIES[i];
if(info[property] === undefined) { if(info[property] === undefined) {
this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label); this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label);
haveMetadata = false; haveMetadata = false;
@ -281,7 +375,9 @@ Zotero.Translator = function(info) {
} }
} }
if(info["browserSupport"].indexOf(Zotero.browser) !== -1) { this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
if(this.browserSupport.indexOf(Zotero.browser) !== -1) {
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER; this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
} else { } else {
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE; this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
@ -313,12 +409,13 @@ Zotero.Translator.prototype.getCode = function(callback) {
callback(true); callback(true);
} else { } else {
var me = this; var me = this;
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID}, Zotero.Repo.getTranslatorCode(this.translatorID,
function(code) { function(code) {
if(!code) { if(!code) {
callback(false); callback(false);
} else { } else {
me.code = me.preprocessCode(code); // cache code for session only (we have standalone anyway)
me.code = code;
callback(true); callback(true);
} }
} }
@ -326,20 +423,6 @@ Zotero.Translator.prototype.getCode = function(callback) {
} }
} }
/**
* Preprocesses code for this translator
*/
Zotero.Translator.prototype.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);
Zotero.debug(code);
}
return code;
}
Zotero.Translator.prototype.__defineGetter__("displayOptions", function() { Zotero.Translator.prototype.__defineGetter__("displayOptions", function() {
return Zotero.Utilities.deepCopy(this._displayOptions); return Zotero.Utilities.deepCopy(this._displayOptions);
}); });
@ -356,7 +439,7 @@ Zotero.Translator.prototype.__defineGetter__("configOptions", function() {
* @param {Integer} colNumber * @param {Integer} colNumber
*/ */
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) { Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
Zotero.log(message, type ? type : "error", this.label); Zotero.logError(message);
} }
Zotero.Translator.RUN_MODE_IN_BROWSER = 1; Zotero.Translator.RUN_MODE_IN_BROWSER = 1;

File diff suppressed because one or more lines are too long

View file

@ -23,7 +23,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
const CONNECTOR_SERVER_API_VERSION = 1; const CONNECTOR_SERVER_API_VERSION = 2;
Zotero.Server.Connector = function() {}; Zotero.Server.Connector = function() {};
Zotero.Server.Connector._waitingForSelection = {}; Zotero.Server.Connector._waitingForSelection = {};
@ -196,30 +196,13 @@ Zotero.Server.Connector.CookieManager.prototype = {
* Lists all available translators, including code for translators that should be run on every page * Lists all available translators, including code for translators that should be run on every page
* *
* Accepts: * Accepts:
* browser - one-letter code of the current browser * Nothing
* g = Gecko (Firefox)
* c = Google Chrome (WebKit & V8)
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
* i = Internet Explorer
* Returns: * Returns:
* translators - Zotero.Translator objects * Array of 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.Connector.GetTranslators = function() {};
Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData; Zotero.Server.Endpoints["/connector/getTranslators"] = Zotero.Server.Connector.GetTranslators;
Zotero.Server.Connector.GetData.prototype = { Zotero.Server.Connector.GetTranslators.prototype = {
"supportedMethods":["POST"], "supportedMethods":["POST"],
"supportedDataTypes":["application/json"], "supportedDataTypes":["application/json"],
@ -229,12 +212,8 @@ Zotero.Server.Connector.GetData.prototype = {
* @param {Function} sendResponseCallback function to send HTTP response * @param {Function} sendResponseCallback function to send HTTP response
*/ */
"init":function(data, sendResponseCallback) { "init":function(data, sendResponseCallback) {
if(data.apiVersion !== CONNECTOR_SERVER_API_VERSION) {
sendResponseCallback(412);
}
// Translator data // Translator data
var responseData = {"preferences":{}, "translators":[]}; var responseData = [];
// TODO only send necessary translators // TODO only send necessary translators
var translators = Zotero.Translators.getAll(); var translators = Zotero.Translators.getAll();
@ -247,68 +226,10 @@ Zotero.Server.Connector.GetData.prototype = {
// Do not pass targetless translators that do not support this browser (since that // Do not pass targetless translators that do not support this browser (since that
// would mean passing each page back to Zotero) // would mean passing each page back to Zotero)
responseData.translators.push(serializableTranslator); responseData.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)); 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;
} }
} }
@ -611,3 +532,28 @@ Zotero.Server.Connector.GetTranslatorCode.prototype = {
sendResponseCallback(200, "application/javascript", translator.code); sendResponseCallback(200, "application/javascript", translator.code);
} }
} }
/**
* Test connection
*
* Accepts:
* Nothing
* Returns:
* Nothing (200 OK response)
*/
Zotero.Server.Connector.Ping = function() {};
Zotero.Server.Endpoints["/connector/ping"] = Zotero.Server.Connector.Ping;
Zotero.Server.Connector.Ping.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) {
sendResponseCallback(200);
}
}

View file

@ -1633,7 +1633,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) {
* translation fails * translation fails
*/ */
Zotero.Translate.Search.prototype.complete = function(returnValue, error) { Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
if(this._currentState == "translate" && !this.newItems.length) { if(this._currentState == "translate" && (!this.newItems || !this.newItems.length)) {
Zotero.debug("Translate: Could not find a result using "+this.translator[0].label+": \n" Zotero.debug("Translate: Could not find a result using "+this.translator[0].label+": \n"
+this._generateErrorString(error), 3); +this._generateErrorString(error), 3);
if(this.translator.length > 1) { if(this.translator.length > 1) {

View file

@ -538,7 +538,7 @@ Zotero.Utilities = {
* @return {Object} * @return {Object}
*/ */
"deepCopy":function(obj) { "deepCopy":function(obj) {
var obj2 = {}; var obj2 = (obj instanceof Array ? [] : {});
for(var i in obj) { for(var i in obj) {
if(typeof obj[i] === "object") { if(typeof obj[i] === "object") {
obj2[i] = Zotero.Utilities.deepCopy(obj[i]); obj2[i] = Zotero.Utilities.deepCopy(obj[i]);

View file

@ -424,7 +424,16 @@ if(appInfo.platformVersion[0] >= 2) {
// Load additional info for connector or not // Load additional info for connector or not
if(Zotero.isConnector) { if(Zotero.isConnector) {
Zotero.debug("Loading in connector mode"); Zotero.debug("Loading in connector mode");
Zotero.Connector.init(); Zotero.Connector_Types.init();
if(!Zotero.isFirstLoadThisSession) {
// wait for initComplete message if we switched to connector because standalone was
// started
_waitingForInitComplete = true;
while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
}
Zotero.Repo.init();
} else { } else {
Zotero.debug("Loading in full mode"); Zotero.debug("Loading in full mode");
_initFull(); _initFull();
@ -444,13 +453,6 @@ if(appInfo.platformVersion[0] >= 2) {
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms"); Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
if(!Zotero.isFirstLoadThisSession) { 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 // trigger zotero-reloaded event
Zotero.debug('Triggering "zotero-reloaded" event'); Zotero.debug('Triggering "zotero-reloaded" event');
observerService.notifyObservers(Zotero, "zotero-reloaded", null); observerService.notifyObservers(Zotero, "zotero-reloaded", null);

View file

@ -114,7 +114,9 @@ const xpcomFilesConnector = [
'connector/translate_item', 'connector/translate_item',
'connector/translator', 'connector/translator',
'connector/connector', 'connector/connector',
'connector/cachedTypes' 'connector/cachedTypes',
'connector/repo',
'connector/typeSchemaData'
]; ];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

View file

@ -153,3 +153,7 @@ pref("extensions.zotero.pane.persist", '');
// Domains allowed to import, separated by a semicolon // Domains allowed to import, separated by a semicolon
pref("extensions.zotero.ingester.allowedSites", ""); pref("extensions.zotero.ingester.allowedSites", "");
// Connector
pref("extensions.zotero.connector.repo.lastCheck.localTime", 0);
pref("extensions.zotero.connector.repo.lastCheck.repoTime", 0);