diff --git a/chrome/content/zotero/xpcom/connector/repo.js b/chrome/content/zotero/xpcom/connector/repo.js index a12d27397d..4f93710232 100644 --- a/chrome/content/zotero/xpcom/connector/repo.js +++ b/chrome/content/zotero/xpcom/connector/repo.js @@ -27,6 +27,7 @@ const TRANSLATOR_CODE_PREFIX = "translatorCode-"; Zotero.Repo = new function() { var _nextCheck; var _timeoutID; + const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/; /** * Try to retrieve translator metadata from Zotero Standalone and initialize repository check @@ -82,12 +83,43 @@ Zotero.Repo = new function() { */ function _haveCode(code, translatorID, callback) { if(!code) { + Zotero.logError(new Error("Code could not be retrieved for " + translatorID)); callback(false); return; } if(!Zotero.isFx) { - localStorage["translatorCode-"+translatorID] = Zotero.Translators.preprocessCode(code); + code = Zotero.Translators.preprocessCode(code); + + // make sure the version of the translator we retrieved actually matches what's in the + // repo. if not (because it's from a different source), we won't save it. + var lastUpdatedIndex = code.indexOf('"lastUpdated"'); + if (lastUpdatedIndex == -1) { + callback(false); + Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID)); + return; + } + + // Add 50 characters to clear lastUpdated timestamp and final "}" + var header = code.substr(0, lastUpdatedIndex + 50); + var m = infoRe.exec(header); + if (!m) { + Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID)); + return; + } + + var metadata = JSON.parse(m[0]); + var translator = Zotero.Translators.getWithoutCode(translatorID); + + if(metadata.lastUpdated === translator.lastUpdated) { + localStorage["translatorCode-"+translatorID] = code; + } else 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]); + localStorage["translatorCode-"+translatorID] = code; + } else { + Zotero.debug("Repo: Retrieved code for "+metadata.label+" older than stored metadata; not caching"); + } } callback(code); } diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js index a352a65478..72796dee94 100644 --- a/chrome/content/zotero/xpcom/connector/translator.js +++ b/chrome/content/zotero/xpcom/connector/translator.js @@ -82,6 +82,18 @@ Zotero.Translators = new function() { } } + /** + * Gets the translator that corresponds to a given ID, without attempting to retrieve code + * @param {String} id The ID of the translator + * @param {Function} [callback] An optional callback to be executed when translators have been + * retrieved. If no callback is specified, translators are + * returned. + */ + this.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 @@ -219,6 +231,9 @@ Zotero.Translators = new function() { /** * Saves all translator data 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(); @@ -241,17 +256,17 @@ Zotero.Translators = new function() { // Update translators with new metadata for(var i in newMetadata) { - var newTranslator = new Zotero.Translator(newMetadata[i]); + var newTranslator = newMetadata[i]; if(_translators.hasOwnProperty(newTranslator.translatorID)) { - var oldLastUpdated = _translators[newTranslator.translatorID].lastUpdated; + var oldTranslator = _translators[newTranslator.translatorID]; // check whether translator has changed - if(oldLastUpdated !== newTranslator.lastUpdated) { + 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(oldLastUpdated)) { - Zotero.debug("Translators: Received older version of "+newTranslator.label+" from repo ("+newTranslator.lastUpdated+" vs. "+oldLastUpdated+")"); + if(Zotero.Date.sqlToDate(newTranslator.lastUpdated) < Zotero.Date.sqlToDate(oldTranslator.lastUpdated)) { + Zotero.debug("Translators: Received older version of "+newTranslator.label+" from repo ("+newTranslator.lastUpdated+" vs. "+oldTranslator.lastUpdated+")"); continue; } @@ -262,14 +277,14 @@ Zotero.Translators = new function() { } 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; } - - _translators[newTranslator.translatorID] = newTranslator; } if(!hasChanged) return; @@ -373,6 +388,13 @@ var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browser * @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]; @@ -399,15 +421,21 @@ Zotero.Translator = function(info) { 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 = this.target ? new RegExp(this.target, "i") : null; + } else if(this.hasOwnProperty("webRegexp")) { + delete this.webRegexp; } if(info.code) { this.code = preprocessCode(info.code); + } else if(this.hasOwnProperty("code")) { + delete this.code; } }