commit b114266fb314fa2837417670712836ee6bdc0d7d parent 2bee3ecf1e5867a1cf2637cc448fe840560175f2 Author: Simon Kornblith <simon@simonster.com> Date: Thu, 30 Jun 2011 01:08:30 +0000 - 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 Diffstat:
13 files changed, 511 insertions(+), 308 deletions(-)
diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js @@ -30,42 +30,49 @@ /** * @namespace */ -if(!Zotero.Connector) Zotero.Connector = {}; -Zotero.Connector.Types = new function() { +Zotero.Connector_Types = new function() { /** * Initializes types * @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema */ - this.init = function(typeSchema) { + 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] = typeSchema[schemaType]; + this[schemaType] = Zotero.Utilities.deepCopy(Zotero.Connector_Types.schema[schemaType]); for(var id in this[schemaType]) { var entry = this[schemaType][id]; - entry.id = id; + entry.id = parseInt(id); 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() { this.getID = function(idOrName) { - if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; - return Zotero.Connector.Types[this.schemaType][idOrName].id; + if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; + return Zotero.Connector_Types[this.schemaType][idOrName].id; } this.getName = function(idOrName) { - if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; - return Zotero.Connector.Types[this.schemaType][idOrName].name; + if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; + return Zotero.Connector_Types[this.schemaType][idOrName].name; } this.getLocalizedString = function(idOrName) { - if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false; - return Zotero.Connector.Types[this.schemaType][idOrName].localizedString; + if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; + return Zotero.Connector_Types[this.schemaType][idOrName].localizedString; } } @@ -74,14 +81,14 @@ Zotero.ItemTypes = new function() { Zotero.CachedTypes.call(this); this.getImageSrc = function(idOrName) { - if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false; + if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false; 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) { - 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) { - 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); this.getTypesForItemType = function(idOrName) { - if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false; - var itemType = Zotero.Connector.Types["itemTypes"][idOrName]; + if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false; + var itemType = Zotero.Connector_Types["itemTypes"][idOrName]; var creatorTypes = []; for(var i=0; i<itemType.creatorTypes.length; i++) { - creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]); + creatorTypes.push(Zotero.Connector_Types["creatorTypes"][itemType.creatorTypes[i]]); } return creatorTypes; } @@ -107,10 +114,29 @@ Zotero.ItemFields = new function() { this.isValidForType = function(fieldIdOrName, typeIdOrName) { // mimics itemFields.js - if(!Zotero.Connector.Types["fields"][fieldIdOrName] - || !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID"; + if(!Zotero.Connector_Types["fields"][fieldIdOrName] + || !Zotero.Connector_Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID"; + + return Zotero.Connector_Types["itemTypes"][typeIdOrName].fields.indexOf( + Zotero.Connector_Types["fields"][fieldIdOrName].id) !== -1; + } + + 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 Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf( - Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1; + return false; } } \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/connector.js b/chrome/content/zotero/xpcom/connector/connector.js @@ -27,63 +27,16 @@ Zotero.Connector = new function() { const CONNECTOR_URI = "http://127.0.0.1:23119/"; const CONNECTOR_SERVER_API_VERSION = 1; - this.isOnline = true; - this.haveRefreshedData = false; - this.data = null; - - /** - * Called to initialize Zotero - */ - this.init = function() { - Zotero.Connector.getData(); - } + this.isOnline = null; /** * Checks if Zotero is online and passes current status to callback * @param {Function} callback */ this.checkIsOnline = function(callback) { - if(Zotero.Connector.isOnline) { - callback(true); - } 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); + Zotero.Connector.callMethod("ping", {}, function(status) { + callback(status !== false); + }); } // saner descriptions of some HTTP error codes @@ -96,115 +49,48 @@ Zotero.Connector = new function() { this.EXCEPTION_CODES = [0, 400, 404, 412, 500, 501]; /** - * Updates Zotero's status depending on the success or failure of a request - * - * @param {Boolean} isOnline Whether or not Zotero was online - * @param {Function} successCallback Function to be called after loading new data if - * Zotero is online - * @param {Function} failureCallback Function to be called if Zotero is offline - * - * Calls Zotero.Connector.Browser.onStateChange(isOnline, method, context) if status has changed - */ - function _checkState(isOnline, callback) { - if(isOnline) { - if(Zotero.Connector.haveRefreshedData) { - if(callback) callback(true); - } else { - Zotero.Connector.getData(callback); - } - } else { - if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE); - } - - if(Zotero.Connector.isOnline !== isOnline) { - Zotero.Connector.isOnline = isOnline; - if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) { - Zotero.Connector_Browser.onStateChange(isOnline); - } - } - - return isOnline; - } - - /** - * Loads list of translators and other relevant data from local Zotero instance - * - * @param {Function} successCallback Function to be called after loading new data if - * Zotero is online - * @param {Function} failureCallback Function to be called if Zotero is offline - */ - this.getData = function(callback) { - Zotero.HTTP.doPost(CONNECTOR_URI+"connector/getData", - JSON.stringify({"browser":Zotero.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. + * @param {Function} callback Function to be called when requests complete. */ 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) { - callback(false, req.status); + var newCallback = function(req) { + try { + var isOnline = req.status !== Zotero.Connector.EXCEPTION_NOT_AVAILABLE + && req.status !== Zotero.Connector.EXCEPTION_INCOMPATIBLE_VERSION; + + if(Zotero.Connector.isOnline !== isOnline) { + Zotero.Connector.isOnline = isOnline; + if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) { + Zotero.Connector_Browser.onStateChange(isOnline); + } + } + + if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) { + Zotero.debug("Connector: Method "+method+" failed"); + if(callback) callback(false, req.status); + } else { + Zotero.debug("Connector: Method "+method+" succeeded"); + var val = null; + if(req.responseText) { + if(req.getResponseHeader("Content-Type") === "application/json") { + val = JSON.parse(req.responseText); } else { - var val = null; - if(req.responseText) { - if(req.getResponseHeader("Content-Type") === "application/json") { - val = JSON.parse(req.responseText); - } else { - val = req.responseText; - } - } - callback(val, req.status); + val = req.responseText; } - }); - }, {"Content-Type":"application/json"}); + } + if(callback) callback(val, req.status); + } + } catch(e) { + Zotero.logError(e); + return; + } + }; + var uri = CONNECTOR_URI+"connector/"+method; + + Zotero.HTTP.doPost(uri, JSON.stringify(data), + newCallback, {"Content-Type":"application/json"}); } } \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/connector_debug.js b/chrome/content/zotero/xpcom/connector/connector_debug.js @@ -81,6 +81,6 @@ Zotero.Connector_Debug = new function() { var reportID = reported[0].getAttribute('reportID'); callback(true, reportID); - }, {"Content-Type":"application/octet-stream"}); + }, {"Content-Type":"text/plain"}); } } \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/repo.js b/chrome/content/zotero/xpcom/connector/repo.js @@ -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); + } +} +\ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js @@ -25,6 +25,9 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { this.newItems = []; + + this._itemsToSaveToServer = []; + this._timeoutID = null; } Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; @@ -43,6 +46,84 @@ Zotero.Translate.ItemSaver.prototype = { // save items 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); } }; \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js @@ -36,15 +36,28 @@ Zotero.Translators = new function() { /** * 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":[]}; _translators = {}; _initialized = true; // Build caches - var translators = Zotero.Connector.data.translators; - for(var i=0; i<translators.length; i++) { + for(var i in translators) { var translator = new Zotero.Translator(translators[i]); _translators[translator.translatorID] = translator; @@ -185,24 +198,104 @@ Zotero.Translators = new function() { /** * Converts translators to JSON-serializable objects */ - this.serialize = function(translator) { + 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]); + newTranslators[i] = Zotero.Translators.serialize(translator[i], properties); } return newTranslators; } // handle individual translator var newTranslator = {}; - for(var i in PRESERVE_PROPERTIES) { - var property = PRESERVE_PROPERTIES[i]; + for(var i in properties) { + var property = properties[i]; newTranslator[property] = translator[property]; } 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", - "priority", "browserSupport"]; -var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions", - "code", "runMode"]); +const TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", + "priority"]; +var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["displayOptions", "configOptions", + "browserSupport", "code", "runMode"]); +var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]); /** * @class Represents an individual translator * @constructor @@ -270,8 +364,8 @@ var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "confi */ Zotero.Translator = function(info) { // make sure we have all the properties - for(var i in TRANSLATOR_PROPERTIES) { - var property = TRANSLATOR_PROPERTIES[i]; + for(var i in TRANSLATOR_REQUIRED_PROPERTIES) { + var property = TRANSLATOR_REQUIRED_PROPERTIES[i]; if(info[property] === undefined) { this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label); 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; } else { this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE; @@ -313,12 +409,13 @@ Zotero.Translator.prototype.getCode = function(callback) { callback(true); } else { var me = this; - Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID}, + Zotero.Repo.getTranslatorCode(this.translatorID, function(code) { if(!code) { callback(false); } else { - me.code = me.preprocessCode(code); + // cache code for session only (we have standalone anyway) + me.code = code; 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() { return Zotero.Utilities.deepCopy(this._displayOptions); }); @@ -356,7 +439,7 @@ Zotero.Translator.prototype.__defineGetter__("configOptions", function() { * @param {Integer} 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; diff --git a/chrome/content/zotero/xpcom/connector/typeSchemaData.js b/chrome/content/zotero/xpcom/connector/typeSchemaData.js @@ -0,0 +1 @@ +Zotero.Connector_Types.schema = {"itemTypes":{"1":{"name":"note","localizedString":"Note","creatorTypes":[],"fields":[],"baseFields":{},"icon":"treeitem-note.png"},"2":{"name":"book","localizedString":"Book","creatorTypes":[1,2,3,5,4],"fields":[110,90,3,30,4,45,6,7,8,14,118,87,11,116,1,27,123,19,62,18,2,22],"baseFields":{},"icon":"treeitem-book.png"},"3":{"name":"bookSection","localizedString":"Book Section","creatorTypes":[1,29,2,3,5,4],"fields":[110,90,115,3,30,4,45,6,7,8,14,10,87,11,116,1,27,123,19,62,18,2,22],"baseFields":{"115":false},"icon":"treeitem-bookSection.png"},"4":{"name":"journalArticle","localizedString":"Journal Article","creatorTypes":[1,2,3,27,4],"fields":[110,90,12,4,5,10,14,3,28,29,25,87,26,13,116,1,27,123,19,62,18,2,22],"baseFields":{},"icon":"treeitem-journalArticle.png"},"5":{"name":"magazineArticle","localizedString":"Magazine Article","creatorTypes":[1,2,27,4],"fields":[110,90,12,4,5,14,10,87,13,116,1,27,123,19,62,18,2,22],"baseFields":{},"icon":"treeitem-magazineArticle.png"},"6":{"name":"newspaperArticle","localizedString":"Newspaper Article","creatorTypes":[1,2,27,4],"fields":[110,90,12,7,6,14,15,10,87,116,13,1,27,123,19,62,18,2,22],"baseFields":{},"icon":"treeitem-newspaperArticle.png"},"7":{"name":"thesis","localizedString":"Thesis","creatorTypes":[1,2],"fields":[110,90,69,89,7,14,118,87,116,1,27,123,19,62,18,2,22],"baseFields":{"69":false,"89":false},"icon":"treeitem-thesis.png"},"8":{"name":"letter","localizedString":"Letter","creatorTypes":[1,2,16],"fields":[110,90,65,14,87,116,1,27,123,19,62,18,2,22],"baseFields":{"65":false},"icon":"treeitem-letter.png"},"9":{"name":"manuscript","localizedString":"Manuscript","creatorTypes":[1,2,4],"fields":[110,90,66,7,14,118,87,116,1,27,123,19,62,18,2,22],"baseFields":{"66":false},"icon":"treeitem-manuscript.png"},"10":{"name":"interview","localizedString":"Interview","creatorTypes":[6,2,7,4],"fields":[110,90,14,64,87,116,1,27,123,19,62,18,2,22],"baseFields":{"64":false},"icon":"treeitem-interview.png"},"11":{"name":"film","localizedString":"Film","creatorTypes":[8,2,10,9],"fields":[110,90,21,14,122,63,77,87,116,1,27,123,19,62,18,2,22],"baseFields":{"21":false,"122":false,"63":false},"icon":"treeitem-film.png"},"12":{"name":"artwork","localizedString":"Artwork","creatorTypes":[22,2],"fields":[110,90,59,61,14,87,116,123,19,62,18,1,27,2,22],"baseFields":{"59":false},"icon":"treeitem-artwork.png"},"13":{"name":"webpage","localizedString":"Web Page","creatorTypes":[1,2,4],"fields":[110,90,91,70,14,116,1,27,87,2,22],"baseFields":{"91":false,"70":false},"icon":"treeitem-webpage.png"},"14":{"name":"attachment","localizedString":"Attachment","creatorTypes":[],"fields":[110,27,1],"baseFields":{},"icon":"treeitem.png"},"15":{"name":"report","localizedString":"Report","creatorTypes":[1,2,5,4],"fields":[110,90,92,32,28,7,31,14,10,87,116,1,27,123,19,62,18,2,22],"baseFields":{"92":false,"32":false,"31":false},"icon":"treeitem-report.png"},"16":{"name":"bill","localizedString":"Bill","creatorTypes":[12,2,28],"fields":[110,90,93,36,94,15,95,41,40,42,14,87,1,27,116,2,22],"baseFields":{"93":false,"94":false,"95":false},"icon":"treeitem.png"},"17":{"name":"case","localizedString":"Case","creatorTypes":[1,2,13],"fields":[111,90,43,97,44,117,98,42,96,87,116,1,27,2,22],"baseFields":{"111":false,"97":false,"117":false,"98":false,"96":false},"icon":"treeitem.png"},"18":{"name":"hearing","localizedString":"Hearing","creatorTypes":[2],"fields":[110,90,46,7,8,45,99,10,41,40,42,14,87,116,1,27,2,22],"baseFields":{"99":false},"icon":"treeitem.png"},"19":{"name":"patent","localizedString":"Patent","creatorTypes":[14,15,2],"fields":[110,90,7,102,48,120,50,121,10,103,51,52,53,54,87,116,1,27,2,22],"baseFields":{"50":false,"52":false},"icon":"treeitem.png"},"20":{"name":"statute","localizedString":"Statute","creatorTypes":[1,2],"fields":[112,90,36,55,101,100,10,15,40,42,87,116,1,27,2,22],"baseFields":{"112":false,"101":false,"100":false},"icon":"treeitem.png"},"21":{"name":"email","localizedString":"E-mail","creatorTypes":[1,2,16],"fields":[113,90,14,116,1,27,87,2,22],"baseFields":{"113":false},"icon":"treeitem-email.png"},"22":{"name":"map","localizedString":"Map","creatorTypes":[20,2,5],"fields":[110,90,67,68,28,6,7,8,14,87,11,116,1,27,123,19,62,18,2,22],"baseFields":{"67":false},"icon":"treeitem-map.png"},"23":{"name":"blogPost","localizedString":"Blog Post","creatorTypes":[1,23,2],"fields":[110,90,107,70,14,1,27,87,116,2,22],"baseFields":{"107":false,"70":false},"icon":"treeitem-blogPost.png"},"24":{"name":"instantMessage","localizedString":"Instant Message","creatorTypes":[1,2,16],"fields":[110,90,14,87,116,1,27,2,22],"baseFields":{},"icon":"treeitem.png"},"25":{"name":"forumPost","localizedString":"Forum Post","creatorTypes":[1,2],"fields":[110,90,104,79,14,87,116,1,27,2,22],"baseFields":{"104":false,"79":false},"icon":"treeitem-forumPost.png"},"26":{"name":"audioRecording","localizedString":"Audio Recording","creatorTypes":[17,18,2,19],"fields":[110,90,71,28,4,45,7,72,14,77,87,11,116,123,19,62,18,1,27,2,22],"baseFields":{"71":false,"72":false},"icon":"treeitem-audioRecording.png"},"27":{"name":"presentation","localizedString":"Presentation","creatorTypes":[24,2],"fields":[110,90,74,14,7,75,1,27,87,116,2,22],"baseFields":{"74":false},"icon":"treeitem.png"},"28":{"name":"videoRecording","localizedString":"Video Recording","creatorTypes":[8,11,2,10,9],"fields":[110,90,63,28,4,45,7,76,14,77,87,11,116,1,27,123,19,62,18,2,22],"baseFields":{"63":false,"76":false},"icon":"treeitem-videoRecording.png"},"29":{"name":"tvBroadcast","localizedString":"TV Broadcast","creatorTypes":[8,11,2,25,10,9],"fields":[110,90,119,105,63,7,78,14,77,87,116,1,27,123,19,62,18,2,22],"baseFields":{"119":false,"105":false,"63":false,"78":false},"icon":"treeitem-tvBroadcast.png"},"30":{"name":"radioBroadcast","localizedString":"Radio Broadcast","creatorTypes":[8,11,2,25,10,9],"fields":[110,90,119,105,71,7,78,14,77,87,116,1,27,123,19,62,18,2,22],"baseFields":{"119":false,"105":false,"71":false,"78":false},"icon":"treeitem-radioBroadcast.png"},"31":{"name":"podcast","localizedString":"Podcast","creatorTypes":[26,2,25],"fields":[110,90,28,105,80,77,1,27,87,116,2,22],"baseFields":{"105":false,"80":false},"icon":"treeitem-podcast.png"},"32":{"name":"computerProgram","localizedString":"Computer Program","creatorTypes":[21,2],"fields":[110,90,28,81,14,82,7,83,88,11,116,1,2,123,19,62,18,27,22],"baseFields":{"83":false},"icon":"treeitem-computerProgram.png"},"33":{"name":"conferencePaper","localizedString":"Conference Paper","creatorTypes":[1,2,3,5,4],"fields":[110,90,14,114,84,7,8,4,10,3,87,26,11,116,1,27,123,19,62,18,2,22],"baseFields":{"114":false},"icon":"treeitem-conferencePaper.png"},"34":{"name":"document","localizedString":"Document","creatorTypes":[1,2,3,27,4],"fields":[110,90,8,14,87,116,1,27,123,19,62,18,2,22],"baseFields":{},"icon":"treeitem.png"},"35":{"name":"encyclopediaArticle","localizedString":"Encyclopedia Article","creatorTypes":[1,2,3,5,4],"fields":[110,90,85,3,30,4,45,6,7,8,14,10,11,116,1,27,87,123,19,62,18,2,22],"baseFields":{"85":false},"icon":"treeitem.png"},"36":{"name":"dictionaryEntry","localizedString":"Dictionary Entry","creatorTypes":[1,2,3,5,4],"fields":[110,90,86,3,30,4,45,6,7,8,14,10,87,11,116,1,27,123,19,62,18,2,22],"baseFields":{"86":false},"icon":"treeitem.png"}},"creatorTypes":{"1":{"name":"author","localizedString":"Author"},"2":{"name":"contributor","localizedString":"Contributor"},"3":{"name":"editor","localizedString":"Editor"},"4":{"name":"translator","localizedString":"Translator"},"5":{"name":"seriesEditor","localizedString":"Series Editor"},"6":{"name":"interviewee","localizedString":"Interview With"},"7":{"name":"interviewer","localizedString":"Interviewer"},"8":{"name":"director","localizedString":"Director"},"9":{"name":"scriptwriter","localizedString":"Scriptwriter"},"10":{"name":"producer","localizedString":"Producer"},"11":{"name":"castMember","localizedString":"Cast Member"},"12":{"name":"sponsor","localizedString":"Sponsor"},"13":{"name":"counsel","localizedString":"Counsel"},"14":{"name":"inventor","localizedString":"Inventor"},"15":{"name":"attorneyAgent","localizedString":"Attorney/Agent"},"16":{"name":"recipient","localizedString":"Recipient"},"17":{"name":"performer","localizedString":"Performer"},"18":{"name":"composer","localizedString":"Composer"},"19":{"name":"wordsBy","localizedString":"Words By"},"20":{"name":"cartographer","localizedString":"Cartographer"},"21":{"name":"programmer","localizedString":"Programmer"},"22":{"name":"artist","localizedString":"Artist"},"23":{"name":"commenter","localizedString":"Commenter"},"24":{"name":"presenter","localizedString":"Presenter"},"25":{"name":"guest","localizedString":"Guest"},"26":{"name":"podcaster","localizedString":"Podcaster"},"27":{"name":"reviewedAuthor","localizedString":"Reviewed Author"},"28":{"name":"cosponsor","localizedString":"Cosponsor"},"29":{"name":"bookAuthor","localizedString":"Book Author"}},"fields":{"1":{"name":"url","localizedString":"URL"},"2":{"name":"rights","localizedString":"Rights"},"3":{"name":"series","localizedString":"Series"},"4":{"name":"volume","localizedString":"Volume"},"5":{"name":"issue","localizedString":"Issue"},"6":{"name":"edition","localizedString":"Edition"},"7":{"name":"place","localizedString":"Place"},"8":{"name":"publisher","localizedString":"Publisher"},"10":{"name":"pages","localizedString":"Pages"},"11":{"name":"ISBN","localizedString":"ISBN"},"12":{"name":"publicationTitle","localizedString":"Publication"},"13":{"name":"ISSN","localizedString":"ISSN"},"14":{"name":"date","localizedString":"Date"},"15":{"name":"section","localizedString":"Section"},"18":{"name":"callNumber","localizedString":"Call Number"},"19":{"name":"archiveLocation","localizedString":"Loc. in Archive"},"21":{"name":"distributor","localizedString":"Distributor"},"22":{"name":"extra","localizedString":"Extra"},"25":{"name":"journalAbbreviation","localizedString":"Journal Abbr"},"26":{"name":"DOI","localizedString":"DOI"},"27":{"name":"accessDate","localizedString":"Accessed"},"28":{"name":"seriesTitle","localizedString":"Series Title"},"29":{"name":"seriesText","localizedString":"Series Text"},"30":{"name":"seriesNumber","localizedString":"Series Number"},"31":{"name":"institution","localizedString":"Institution"},"32":{"name":"reportType","localizedString":"Report Type"},"36":{"name":"code","localizedString":"Code"},"40":{"name":"session","localizedString":"Session"},"41":{"name":"legislativeBody","localizedString":"Legislative Body"},"42":{"name":"history","localizedString":"History"},"43":{"name":"reporter","localizedString":"Reporter"},"44":{"name":"court","localizedString":"Court"},"45":{"name":"numberOfVolumes","localizedString":"# of Volumes"},"46":{"name":"committee","localizedString":"Committee"},"48":{"name":"assignee","localizedString":"Assignee"},"50":{"name":"patentNumber","localizedString":"Patent Number"},"51":{"name":"priorityNumbers","localizedString":"Priority Numbers"},"52":{"name":"issueDate","localizedString":"Issue Date"},"53":{"name":"references","localizedString":"References"},"54":{"name":"legalStatus","localizedString":"Legal Status"},"55":{"name":"codeNumber","localizedString":"Code Number"},"59":{"name":"artworkMedium","localizedString":"Medium"},"60":{"name":"number","localizedString":"Number"},"61":{"name":"artworkSize","localizedString":"Artwork Size"},"62":{"name":"libraryCatalog","localizedString":"Library Catalog"},"63":{"name":"videoRecordingFormat","localizedString":"Format"},"64":{"name":"interviewMedium","localizedString":"Medium"},"65":{"name":"letterType","localizedString":"Type"},"66":{"name":"manuscriptType","localizedString":"Type"},"67":{"name":"mapType","localizedString":"Type"},"68":{"name":"scale","localizedString":"Scale"},"69":{"name":"thesisType","localizedString":"Type"},"70":{"name":"websiteType","localizedString":"Website Type"},"71":{"name":"audioRecordingFormat","localizedString":"Format"},"72":{"name":"label","localizedString":"Label"},"74":{"name":"presentationType","localizedString":"Type"},"75":{"name":"meetingName","localizedString":"Meeting Name"},"76":{"name":"studio","localizedString":"Studio"},"77":{"name":"runningTime","localizedString":"Running Time"},"78":{"name":"network","localizedString":"Network"},"79":{"name":"postType","localizedString":"Post Type"},"80":{"name":"audioFileType","localizedString":"File Type"},"81":{"name":"version","localizedString":"Version"},"82":{"name":"system","localizedString":"System"},"83":{"name":"company","localizedString":"Company"},"84":{"name":"conferenceName","localizedString":"Conference Name"},"85":{"name":"encyclopediaTitle","localizedString":"Encyclopedia Title"},"86":{"name":"dictionaryTitle","localizedString":"Dictionary Title"},"87":{"name":"language","localizedString":"Language"},"88":{"name":"programmingLanguage","localizedString":"Language"},"89":{"name":"university","localizedString":"University"},"90":{"name":"abstractNote","localizedString":"Abstract"},"91":{"name":"websiteTitle","localizedString":"Website Title"},"92":{"name":"reportNumber","localizedString":"Report Number"},"93":{"name":"billNumber","localizedString":"Bill Number"},"94":{"name":"codeVolume","localizedString":"Code Volume"},"95":{"name":"codePages","localizedString":"Code Pages"},"96":{"name":"dateDecided","localizedString":"Date Decided"},"97":{"name":"reporterVolume","localizedString":"Reporter Volume"},"98":{"name":"firstPage","localizedString":"First Page"},"99":{"name":"documentNumber","localizedString":"Document Number"},"100":{"name":"dateEnacted","localizedString":"Date Enacted"},"101":{"name":"publicLawNumber","localizedString":"Public Law Number"},"102":{"name":"country","localizedString":"Country"},"103":{"name":"applicationNumber","localizedString":"Application Number"},"104":{"name":"forumTitle","localizedString":"Forum/Listserv Title"},"105":{"name":"episodeNumber","localizedString":"Episode Number"},"107":{"name":"blogTitle","localizedString":"Blog Title"},"108":{"name":"type"},"109":{"name":"medium","localizedString":"Medium"},"110":{"name":"title","localizedString":"Title"},"111":{"name":"caseName","localizedString":"Case Name"},"112":{"name":"nameOfAct","localizedString":"Name of Act"},"113":{"name":"subject","localizedString":"Subject"},"114":{"name":"proceedingsTitle","localizedString":"Proceedings Title"},"115":{"name":"bookTitle","localizedString":"Book Title"},"116":{"name":"shortTitle","localizedString":"Short Title"},"117":{"name":"docketNumber","localizedString":"Docket Number"},"118":{"name":"numPages","localizedString":"# of Pages"},"119":{"name":"programTitle","localizedString":"Program Title"},"120":{"name":"issuingAuthority","localizedString":"Issuing Authority"},"121":{"name":"filingDate","localizedString":"Filing Date"},"122":{"name":"genre","localizedString":"Genre"},"123":{"name":"archive","localizedString":"Archive"}}} +\ No newline at end of file diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js @@ -23,7 +23,7 @@ ***** END LICENSE BLOCK ***** */ -const CONNECTOR_SERVER_API_VERSION = 1; +const CONNECTOR_SERVER_API_VERSION = 2; Zotero.Server.Connector = function() {}; 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 * * Accepts: - * browser - one-letter code of the current browser - * g = Gecko (Firefox) - * c = Google Chrome (WebKit & V8) - * s = Safari (WebKit & Nitro/Squirrelfish Extreme) - * i = Internet Explorer + * Nothing * Returns: - * translators - Zotero.Translator objects - * schema - Some information about the database. Currently includes: - * itemTypes - * name - * localizedString - * creatorTypes - * fields - * baseFields - * creatorTypes - * name - * localizedString - * fields - * name - * localizedString + * Array of Zotero.Translator objects */ -Zotero.Server.Connector.GetData = function() {}; -Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData; -Zotero.Server.Connector.GetData.prototype = { +Zotero.Server.Connector.GetTranslators = function() {}; +Zotero.Server.Endpoints["/connector/getTranslators"] = Zotero.Server.Connector.GetTranslators; +Zotero.Server.Connector.GetTranslators.prototype = { "supportedMethods":["POST"], "supportedDataTypes":["application/json"], @@ -229,12 +212,8 @@ Zotero.Server.Connector.GetData.prototype = { * @param {Function} sendResponseCallback function to send HTTP response */ "init":function(data, sendResponseCallback) { - if(data.apiVersion !== CONNECTOR_SERVER_API_VERSION) { - sendResponseCallback(412); - } - // Translator data - var responseData = {"preferences":{}, "translators":[]}; + var responseData = []; // TODO only send necessary translators 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 // would mean passing each page back to Zotero) - responseData.translators.push(serializableTranslator); - } - - // Various DB data (only sending what is required at the moment) - var systemVersion = Zotero.Schema.getDBVersion("system"); - if(systemVersion != data.systemVersion) { - responseData.schema = this._generateTypeSchema(); - } - - // Preferences - var prefs = Zotero.Prefs.prefBranch.getChildList("", {}, {}); - for each(var pref in prefs) { - responseData.preferences[pref] = Zotero.Prefs.get(pref); + responseData.push(serializableTranslator); } 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; } } @@ -610,4 +531,29 @@ Zotero.Server.Connector.GetTranslatorCode.prototype = { var translator = Zotero.Translators.get(postData.translatorID); 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); + } } \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js @@ -1633,7 +1633,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) { * translation fails */ 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" +this._generateErrorString(error), 3); if(this.translator.length > 1) { diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js @@ -538,7 +538,7 @@ Zotero.Utilities = { * @return {Object} */ "deepCopy":function(obj) { - var obj2 = {}; + var obj2 = (obj instanceof Array ? [] : {}); for(var i in obj) { if(typeof obj[i] === "object") { obj2[i] = Zotero.Utilities.deepCopy(obj[i]); diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js @@ -424,7 +424,16 @@ if(appInfo.platformVersion[0] >= 2) { // Load additional info for connector or not if(Zotero.isConnector) { 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 { Zotero.debug("Loading in full mode"); _initFull(); @@ -444,13 +453,6 @@ if(appInfo.platformVersion[0] >= 2) { Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms"); if(!Zotero.isFirstLoadThisSession) { - if(Zotero.isConnector) { - // wait for initComplete message if we switched to connector because standalone was - // started - _waitingForInitComplete = true; - while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true); - } - // trigger zotero-reloaded event Zotero.debug('Triggering "zotero-reloaded" event'); observerService.notifyObservers(Zotero, "zotero-reloaded", null); diff --git a/components/zotero-service.js b/components/zotero-service.js @@ -114,7 +114,9 @@ const xpcomFilesConnector = [ 'connector/translate_item', 'connector/translator', 'connector/connector', - 'connector/cachedTypes' + 'connector/cachedTypes', + 'connector/repo', + 'connector/typeSchemaData' ]; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js @@ -152,4 +152,8 @@ pref("extensions.zotero.purge.tags", false); pref("extensions.zotero.pane.persist", ''); // Domains allowed to import, separated by a semicolon -pref("extensions.zotero.ingester.allowedSites", ""); -\ No newline at end of file +pref("extensions.zotero.ingester.allowedSites", ""); + +// Connector +pref("extensions.zotero.connector.repo.lastCheck.localTime", 0); +pref("extensions.zotero.connector.repo.lastCheck.repoTime", 0); +\ No newline at end of file