diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 8257f1656..8a2fa135a 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -333,44 +333,44 @@ Zotero.Translate.Sandbox = { var translator = translation.translator[0]; (typeof translator === "object" ? Q(translator) : Zotero.Translators.get(translator)). then(function(translator) { - translation._loadTranslator(translator, function() { - if(Zotero.isFx && !Zotero.isBookmarklet) { - // do same origin check - var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"] - .getService(Components.interfaces.nsIScriptSecurityManager); - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - - var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ? - translate._sandboxLocation.location : translate._sandboxLocation, null, null); - var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ? - translation._sandboxLocation.location : translation._sandboxLocation, null, null); - - try { - secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false); - } catch(e) { - throw new Error("getTranslatorObject() may not be called from web or search "+ - "translators to web or search translators from different origins."); - return; - } - } + return translation._loadTranslator(translator); + }).then(function() { + if(Zotero.isFx && !Zotero.isBookmarklet) { + // do same origin check + var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"] + .getService(Components.interfaces.nsIScriptSecurityManager); + var ioService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); - translation._prepareTranslation(); - setDefaultHandlers(translate, translation); - sandbox = translation._sandboxManager.sandbox; - if(!Zotero.Utilities.isEmpty(sandbox.exports)) { - sandbox.exports.Zotero = sandbox.Zotero; - sandbox = sandbox.exports; - } else { - translate._debug("COMPAT WARNING: "+translation.translator[0].label+" does "+ - "not export any properties. Only detect"+translation._entryFunctionSuffix+ - " and do"+translation._entryFunctionSuffix+" will be available in "+ - "connectors."); - } + var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ? + translate._sandboxLocation.location : translate._sandboxLocation, null, null); + var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ? + translation._sandboxLocation.location : translation._sandboxLocation, null, null); - callback(sandbox); - translate.decrementAsyncProcesses("safeTranslator#getTranslatorObject()"); - }); + try { + secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false); + } catch(e) { + throw new Error("getTranslatorObject() may not be called from web or search "+ + "translators to web or search translators from different origins."); + return; + } + } + + translation._prepareTranslation(); + setDefaultHandlers(translate, translation); + sandbox = translation._sandboxManager.sandbox; + if(!Zotero.Utilities.isEmpty(sandbox.exports)) { + sandbox.exports.Zotero = sandbox.Zotero; + sandbox = sandbox.exports; + } else { + translate._debug("COMPAT WARNING: "+translation.translator[0].label+" does "+ + "not export any properties. Only detect"+translation._entryFunctionSuffix+ + " and do"+translation._entryFunctionSuffix+" will be available in "+ + "connectors."); + } + + callback(sandbox); + translate.decrementAsyncProcesses("safeTranslator#getTranslatorObject()"); }).fail(function(e) { translate.complete(false, e); return; @@ -1112,13 +1112,13 @@ Zotero.Translate.Base.prototype = { var me = this; if(typeof this.translator[0] === "object") { // already have a translator object, so use it - this._loadTranslator(this.translator[0], function() { me._translateTranslatorLoaded() }); + this._loadTranslator(this.translator[0]).then(function() { me._translateTranslatorLoaded() }); } else { // need to get translator first Zotero.Translators.get(this.translator[0]). then(function(translator) { me.translator[0] = translator; - me._loadTranslator(translator, function() { me._translateTranslatorLoaded() }); + me._loadTranslator(translator).then(function() { me._translateTranslatorLoaded() }); }); } }, @@ -1127,12 +1127,6 @@ Zotero.Translate.Base.prototype = { * Called when translator has been retrieved and loaded */ "_translateTranslatorLoaded":function() { - if(!this.translator[0].code) { - this.complete(false, - new Error("Translator "+this.translator[0].label+" is unsupported within this environment")); - return; - } - // set display options to default if they don't exist if(!this._displayOptions) this._displayOptions = this._translatorInfo.displayOptions || {}; @@ -1405,8 +1399,8 @@ Zotero.Translate.Base.prototype = { } var me = this; - this._loadTranslator(this._potentialTranslators[0], - function() { me._detectTranslatorLoaded() }); + this._loadTranslator(this._potentialTranslators[0]). + then(function() { me._detectTranslatorLoaded() }); }, /** @@ -1442,7 +1436,7 @@ Zotero.Translate.Base.prototype = { * @param {Zotero.Translator} translator * @return {Boolean} Whether the translator could be successfully loaded */ - "_loadTranslator":function(translator, callback) { + "_loadTranslator":function(translator) { var sandboxLocation = this._getSandboxLocation(); if(!this._sandboxLocation || sandboxLocation !== this._sandboxLocation) { this._sandboxLocation = sandboxLocation; @@ -1455,20 +1449,17 @@ Zotero.Translate.Base.prototype = { this._aborted = false; this.saveQueue = []; - Zotero.debug("Translate: Parsing code for "+translator.label, 4); - - try { - this._sandboxManager.eval("var exports = {}, ZOTERO_TRANSLATOR_INFO = "+translator.code, - ["detect"+this._entryFunctionSuffix, "do"+this._entryFunctionSuffix, "exports", + var me = this; + return translator.getCode().then(function(code) { + Zotero.debug("Translate: Parsing code for "+translator.label, 4); + me._sandboxManager.eval("var exports = {}, ZOTERO_TRANSLATOR_INFO = "+code, + ["detect"+me._entryFunctionSuffix, "do"+me._entryFunctionSuffix, "exports", "ZOTERO_TRANSLATOR_INFO"], (translator.file ? translator.file.path : translator.label)); - } catch(e) { - this.complete(false, e); - return; - } - this._translatorInfo = this._sandboxManager.sandbox.ZOTERO_TRANSLATOR_INFO; - - if(callback) callback(); + me._translatorInfo = me._sandboxManager.sandbox.ZOTERO_TRANSLATOR_INFO; + }).fail(function(e) { + me.complete(false, e); + }); }, /** @@ -1923,7 +1914,8 @@ Zotero.Translate.Import.prototype.getTranslators = function() { Zotero.Translate.Import.prototype._loadTranslator = function(translator, callback) { // call super var me = this; - Zotero.Translate.Base.prototype._loadTranslator.call(this, translator, function() { + return Zotero.Translate.Base.prototype._loadTranslator.call(this, translator). + then(function() { me._loadTranslatorPrepareIO(translator, callback); }); } diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js index e28f14fff..97654b237 100644 --- a/chrome/content/zotero/xpcom/translation/translator.js +++ b/chrome/content/zotero/xpcom/translation/translator.js @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright © 2009 Center for History and New Media + Copyright © 2013 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://zotero.org @@ -23,385 +23,22 @@ ***** END LICENSE BLOCK ***** */ -// Enumeration of types of translators -const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; - -/** - * Singleton to handle loading and caching of translators - * @namespace - */ -Zotero.Translators = new function() { - var _cache, _translators; - var _initialized = false; - - /** - * Initializes translator cache, loading all relevant translators into memory - */ - this.reinit = Q.async(function() { - var start = (new Date()).getTime(); - var transactionStarted = false; - - _cache = {"import":[], "export":[], "web":[], "search":[]}; - _translators = {}; - - var dbCacheResults = yield Zotero.DB.queryAsync("SELECT leafName, translatorJSON, "+ - "code, lastModifiedTime FROM translatorCache"); - var dbCache = {}; - for each(var cacheEntry in dbCacheResults) { - dbCache[cacheEntry.leafName] = cacheEntry; - } - - var i = 0; - var filesInCache = {}; - var contents = Zotero.getTranslatorsDirectory().directoryEntries; - while(contents.hasMoreElements()) { - var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); - var leafName = file.leafName; - if(!(/^[^.].*\.js$/.test(leafName))) continue; - var lastModifiedTime = file.lastModifiedTime; - - var dbCacheEntry = false; - if(dbCache[leafName]) { - filesInCache[leafName] = true; - if(dbCache[leafName].lastModifiedTime == lastModifiedTime) { - dbCacheEntry = dbCache[file.leafName]; - } - } - - // TODO: use async load method instead of constructor - if(dbCacheEntry) { - // get JSON from cache if possible - var translator = new Zotero.Translator(file, dbCacheEntry.translatorJSON, dbCacheEntry.code); - filesInCache[leafName] = true; - } else { - // otherwise, load from file - var translator = new Zotero.Translator(file); - } - - if(translator.translatorID) { - if(_translators[translator.translatorID]) { - // same translator is already cached - translator.logError('Translator with ID '+ - translator.translatorID+' already loaded from "'+ - _translators[translator.translatorID].file.leafName+'"'); - } else { - // add to cache - _translators[translator.translatorID] = translator; - for(var type in TRANSLATOR_TYPES) { - if(translator.translatorType & TRANSLATOR_TYPES[type]) { - _cache[type].push(translator); - } - } - - if(!dbCacheEntry) { - yield Zotero.Translators.cacheInDB( - leafName, - translator.metadataString, - translator.cacheCode ? translator.code : null, - lastModifiedTime - ); - delete translator.metadataString; - } - } - } - - i++; - } - - // Remove translators from DB as necessary - for(var leafName in dbCache) { - if(!filesInCache[leafName]) { - yield Zotero.DB.queryAsync( - "DELETE FROM translatorCache WHERE leafName = ?", [leafName] - ); - } - } - - // Sort by priority - var collation = Zotero.getLocaleCollation(); - var cmp = function (a, b) { - if (a.priority > b.priority) { - return 1; - } - else if (a.priority < b.priority) { - return -1; - } - return collation.compareString(1, a.label, b.label); - } - for(var type in _cache) { - _cache[type].sort(cmp); - } - - Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms"); - }); - this.init = Zotero.lazy(this.reinit); - - /** - * Gets the translator that corresponds to a given ID - * @param {String} id The ID of the translator - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. - */ - this.get = function(id) { - return this.init().then(function() { - return _translators[id] ? _translators[id] : false - }); - } - - /** - * Gets all translators for a specific type of translation - * @param {String} type The type of translators to get (import, export, web, or search) - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. - */ - this.getAllForType = function(type) { - return this.init().then(function() { - return _cache[type].slice(); - }); - } - - /** - * Gets all translators for a specific type of translation - */ - this.getAll = function() { - return this.init().then(function() { - return [translator for each(translator in _translators)]; - }); - } - - /** - * Gets web translators for a specific location - * @param {String} uri The URI for which to look for translators - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. The callback is passed a set of functions for - * converting URLs from proper to proxied forms as the second - * argument. - */ - this.getWebTranslatorsForLocation = function(uri, callback) { - return this.getAllForType("web").then(function(allTranslators) { - var potentialTranslators = []; - - var properHosts = []; - var proxyHosts = []; - - var properURI = Zotero.Proxies.proxyToProper(uri); - var knownProxy = properURI !== uri; - if(knownProxy) { - // if we know this proxy, just use the proper URI for detection - var searchURIs = [properURI]; - } else { - var searchURIs = [uri]; - - // if there is a subdomain that is also a TLD, also test against URI with the domain - // dropped after the TLD - // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com) - var m = /^(https?:\/\/)([^\/]+)/i.exec(uri); - if(m) { - // First, drop the 0- if it exists (this is an III invention) - var host = m[2]; - if(host.substr(0, 2) === "0-") host = host.substr(2); - var hostnames = host.split("."); - for(var i=1; i} - */ - this.save = function(metadata, code) { - if (!metadata.translatorID) { - throw ("metadata.translatorID not provided in Zotero.Translators.save()"); - } - - if (!metadata.translatorType) { - var found = false; - for each(var type in TRANSLATOR_TYPES) { - if (metadata.translatorType & type) { - found = true; - break; - } - } - if (!found) { - throw ("Invalid translatorType '" + metadata.translatorType + "' in Zotero.Translators.save()"); - } - } - - if (!metadata.label) { - throw ("metadata.label not provided in Zotero.Translators.save()"); - } - - if (!metadata.priority) { - throw ("metadata.priority not provided in Zotero.Translators.save()"); - } - - if (!metadata.lastUpdated) { - throw ("metadata.lastUpdated not provided in Zotero.Translators.save()"); - } - - if (!code) { - throw ("code not provided in Zotero.Translators.save()"); - } - - var fileName = Zotero.Translators.getFileNameFromLabel( - metadata.label, metadata.translatorID - ); - var destFile = Zotero.getTranslatorsDirectory(); - destFile.append(fileName); - - // JSON.stringify has the benefit of indenting JSON - var metadataJSON = JSON.stringify(metadata, null, "\t"); - - var str = metadataJSON + "\n\n" + code, - translator; - - return Zotero.Translators.get(metadata.translatorID) - .then(function(gTranslator) { - translator = gTranslator; - var sameFile = translator && destFile.equals(translator.file); - if (sameFile) return; - - return Q(OS.File.exists(destFile.path)) - .then(function (exists) { - if (exists) { - var msg = "Overwriting translator with same filename '" - + fileName + "'"; - Zotero.debug(msg, 1); - Zotero.debug(metadata, 1); - Components.utils.reportError(msg); - } - }); - }) - .then(function () { - if (!translator) return; - - return Q(OS.File.exists(translator.file.path)) - .then(function (exists) { - translator.file.remove(false); - }); - }) - .then(function () { - Zotero.debug("Saving translator '" + metadata.label + "'"); - Zotero.debug(str); - return Zotero.File.putContentsAsync(destFile, str) - .thenResolve(destFile); - }); - } - - this.cacheInDB = function(fileName, metadataJSON, code, lastModifiedTime) { - return Zotero.DB.queryAsync( - "REPLACE INTO translatorCache VALUES (?, ?, ?, ?)", - [fileName, metadataJSON, code, lastModifiedTime] - ); - } -} +// Properties required for every translator +var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", + "target", "priority", "lastUpdated"]; +// Properties that are preserved if present +var TRANSLATOR_OPTIONAL_PROPERTIES = ["browserSupport", "minVersion", "maxVersion", + "inRepository", "configOptions", "displayOptions", + "hiddenPrefs"]; +// Properties that are passed from background to inject page in connector +var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES. + concat(["browserSupport", "code", "runMode"]); +// Properties that are saved in connector if set but not required +var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]); /** * @class Represents an individual translator * @constructor - * @param {nsIFile} file File from which to generate a translator object * @property {String} translatorID Unique GUID of the translator * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read) * @property {String} label Human-readable name of the translator @@ -423,124 +60,118 @@ Zotero.Translators = new function() { * @property {Boolean} inRepository Whether the translator may be found in the repository * @property {String} lastUpdated SQL-style date and time of translator's last update * @property {String} code The executable JavaScript for the translator + * @property {Boolean} cacheCode Whether to cache code for this session (non-connector only) + * @property {nsIFile} [file] File corresponding to this translator (non-connector only) */ -Zotero.Translator = function(file, json, code) { - const codeGetterFunction = function() { return Zotero.File.getContents(this.file); } - // Maximum length for the info JSON in a translator - const MAX_INFO_LENGTH = 4096; - const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/; - - this.file = file; - - var fStream, cStream; - if(json) { - var info = JSON.parse(json); - } else { - fStream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - cStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fStream.init(file, -1, -1, 0); - cStream.init(fStream, "UTF-8", 8192, - Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - var str = {}; - cStream.readString(MAX_INFO_LENGTH, str); - - var m = infoRe.exec(str.value); - if (!m) { - this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); - fStream.close(); - return; - } - - this.metadataString = m[0]; - - try { - var info = JSON.parse(this.metadataString); - } catch(e) { - this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); - fStream.close(); - return; - } - } - - var haveMetadata = true; +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 each(var property in ["translatorID", "translatorType", "label", "creator", "target", "minVersion", "maxVersion", "priority", "lastUpdated", "inRepository"]) { + for(var i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +// Enumeration of types of translators +const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; + +/** + * Singleton to handle loading and caching of translators + * @namespace + */ +Zotero.Translators = new function() { + var _cache, _translators; + var _initialized = false; + + /** + * Initializes translator cache, loading all relevant translators into memory + */ + this.reinit = Q.async(function() { + var start = (new Date()).getTime(); + var transactionStarted = false; + + _cache = {"import":[], "export":[], "web":[], "search":[]}; + _translators = {}; + + var dbCacheResults = yield Zotero.DB.queryAsync("SELECT leafName, translatorJSON, "+ + "code, lastModifiedTime FROM translatorCache"); + var dbCache = {}; + for each(var cacheEntry in dbCacheResults) { + dbCache[cacheEntry.leafName] = cacheEntry; + } + + var i = 0; + var filesInCache = {}; + var contents = Zotero.getTranslatorsDirectory().directoryEntries; + while(contents.hasMoreElements()) { + var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); + var leafName = file.leafName; + if(!(/^[^.].*\.js$/.test(leafName))) continue; + var lastModifiedTime = file.lastModifiedTime; + + var dbCacheEntry = false; + if(dbCache[leafName]) { + filesInCache[leafName] = true; + if(dbCache[leafName].lastModifiedTime == lastModifiedTime) { + dbCacheEntry = dbCache[file.leafName]; + } + } + + if(dbCacheEntry) { + // get JSON from cache if possible + var translator = Zotero.Translators.load(file, dbCacheEntry.translatorJSON, dbCacheEntry.code); + filesInCache[leafName] = true; + } else { + // otherwise, load from file + var translator = yield Zotero.Translators.loadFromFile(file); + } + + if(translator.translatorID) { + if(_translators[translator.translatorID]) { + // same translator is already cached + translator.logError('Translator with ID '+ + translator.translatorID+' already loaded from "'+ + _translators[translator.translatorID].file.leafName+'"'); + } else { + // add to cache + _translators[translator.translatorID] = translator; + for(var type in TRANSLATOR_TYPES) { + if(translator.translatorType & TRANSLATOR_TYPES[type]) { + _cache[type].push(translator); + } + } + + if(!dbCacheEntry) { + var code = yield translator.getCode(); + yield Zotero.Translators.cacheInDB( + leafName, + translator.serialize(TRANSLATOR_REQUIRED_PROPERTIES. + concat(TRANSLATOR_OPTIONAL_PROPERTIES)), + translator.cacheCode ? translator.code : null, + lastModifiedTime + ); + delete translator.metadataString; + } + } + } + + i++; + } + + // Remove translators from DB as necessary + for(var leafName in dbCache) { + if(!filesInCache[leafName]) { + yield Zotero.DB.queryAsync( + "DELETE FROM translatorCache WHERE leafName = ?", [leafName] + ); + } + } + + // Sort by priority + var collation = Zotero.getLocaleCollation(); + var cmp = function (a, b) { + if (a.priority > b.priority) { + return 1; + } + else if (a.priority < b.priority) { + return -1; + } + return collation.compareString(1, a.label, b.label); + } + for(var type in _cache) { + _cache[type].sort(cmp); + } + + Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms"); + }); + this.init = Zotero.lazy(this.reinit); + + /** + * Loads a translator from JSON, with optional code + */ + this.load = function(file, json, code) { + var info = JSON.parse(json); + info.file = file; + info.code = code; + return new Zotero.Translator(info); + } + + /** + * Loads a translator from the disk + */ + this.loadFromDisk = function(file) { + const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/; + Zotero.File.getContentsAsync(this.file).then(function(source) { + return Zotero.Translators.load(file, infoRe.exec(source)[0], source); + }).fail(function() { + throw "Invalid or missing translator metadata JSON object in " + file.leafName; + }); + } + + /** + * Gets the translator that corresponds to a given ID + * @param {String} id The ID of the translator + * @param {Function} [callback] An optional callback to be executed when translators have been + * retrieved. If no callback is specified, translators are + * returned. + */ + this.get = function(id) { + return this.init().then(function() { + return _translators[id] ? _translators[id] : false + }); + } + + /** + * Gets all translators for a specific type of translation + * @param {String} type The type of translators to get (import, export, web, or search) + * @param {Function} [callback] An optional callback to be executed when translators have been + * retrieved. If no callback is specified, translators are + * returned. + */ + this.getAllForType = function(type) { + return this.init().then(function() { + return _cache[type].slice(); + }); + } + + /** + * Gets all translators for a specific type of translation + */ + this.getAll = function() { + return this.init().then(function() { + return [translator for each(translator in _translators)]; + }); + } + + /** + * Gets web translators for a specific location + * @param {String} uri The URI for which to look for translators + * @param {Function} [callback] An optional callback to be executed when translators have been + * retrieved. If no callback is specified, translators are + * returned. The callback is passed a set of functions for + * converting URLs from proper to proxied forms as the second + * argument. + */ + this.getWebTranslatorsForLocation = function(uri, callback) { + return this.getAllForType("web").then(function(allTranslators) { + var potentialTranslators = []; + + var properHosts = []; + var proxyHosts = []; + + var properURI = Zotero.Proxies.proxyToProper(uri); + var knownProxy = properURI !== uri; + if(knownProxy) { + // if we know this proxy, just use the proper URI for detection + var searchURIs = [properURI]; + } else { + var searchURIs = [uri]; + + // if there is a subdomain that is also a TLD, also test against URI with the domain + // dropped after the TLD + // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com) + var m = /^(https?:\/\/)([^\/]+)/i.exec(uri); + if(m) { + // First, drop the 0- if it exists (this is an III invention) + var host = m[2]; + if(host.substr(0, 2) === "0-") host = host.substr(2); + var hostnames = host.split("."); + for(var i=1; i} + */ + this.save = function(metadata, code) { + if (!metadata.translatorID) { + throw ("metadata.translatorID not provided in Zotero.Translators.save()"); + } + + if (!metadata.translatorType) { + var found = false; + for each(var type in TRANSLATOR_TYPES) { + if (metadata.translatorType & type) { + found = true; + break; + } + } + if (!found) { + throw ("Invalid translatorType '" + metadata.translatorType + "' in Zotero.Translators.save()"); + } + } + + if (!metadata.label) { + throw ("metadata.label not provided in Zotero.Translators.save()"); + } + + if (!metadata.priority) { + throw ("metadata.priority not provided in Zotero.Translators.save()"); + } + + if (!metadata.lastUpdated) { + throw ("metadata.lastUpdated not provided in Zotero.Translators.save()"); + } + + if (!code) { + throw ("code not provided in Zotero.Translators.save()"); + } + + var fileName = Zotero.Translators.getFileNameFromLabel( + metadata.label, metadata.translatorID + ); + var destFile = Zotero.getTranslatorsDirectory(); + destFile.append(fileName); + + // JSON.stringify has the benefit of indenting JSON + var metadataJSON = JSON.stringify(metadata, null, "\t"); + + var str = metadataJSON + "\n\n" + code, + translator; + + return Zotero.Translators.get(metadata.translatorID) + .then(function(gTranslator) { + translator = gTranslator; + var sameFile = translator && destFile.equals(translator.file); + if (sameFile) return; + + return Q(OS.File.exists(destFile.path)) + .then(function (exists) { + if (exists) { + var msg = "Overwriting translator with same filename '" + + fileName + "'"; + Zotero.debug(msg, 1); + Zotero.debug(metadata, 1); + Components.utils.reportError(msg); + } + }); + }) + .then(function () { + if (!translator) return; + + return Q(OS.File.exists(translator.file.path)) + .then(function (exists) { + translator.file.remove(false); + }); + }) + .then(function () { + Zotero.debug("Saving translator '" + metadata.label + "'"); + Zotero.debug(str); + return Zotero.File.putContentsAsync(destFile, str) + .thenResolve(destFile); + }); + } + + this.cacheInDB = function(fileName, metadataJSON, code, lastModifiedTime) { + return Zotero.DB.queryAsync( + "REPLACE INTO translatorCache VALUES (?, ?, ?, ?)", + [fileName, metadataJSON, code, lastModifiedTime] + ); + } +} diff --git a/components/zotero-service.js b/components/zotero-service.js index c3270846f..4569b3e0f 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -44,6 +44,7 @@ const xpcomFilesAll = [ 'progressWindow', 'translation/translate', 'translation/translate_firefox', + 'translation/translator', 'translation/tlds', 'utilities', 'utilities_internal', @@ -104,7 +105,7 @@ const xpcomFilesLocal = [ 'timeline', 'uri', 'translation/translate_item', - 'translation/translator', + 'translation/translators', 'server_connector' ];