updateBundledStyles() asyncification and related changes

- Use async DB and OS.File for bundled file updates
- Remove support for translator/style ZIP files -- the two options are
  now non-unpacked XPIs with subfolders or unpacked source installations
- Now that we have async file access, don't store translator code in
  database cache -- just store metadata so that it's available without
  reading each translator file
- Change the (previously partially asyncified) Zotero.Styles/Translators
  APIs a bit -- while the getAll/getVisible methods are asynchronous and
  will wait for loading, the get() methods are synchronous and require
  styles/translators to be initialized before they're called. Most
  places that end up calling get() probably call getAll/getVisible first
  and should therefore be async, but if there's any way to trigger a
  get() first, that will need to be adjusted.
- Asyncify various other style/translator-related code

XPI support is untested, as is style/translator usage, so there are
almost certainly bugs. The latter depends on updated export format
support (), since toArray() no longer exists on this branch.

Addresses  and 
This commit is contained in:
Dan Stillman 2015-03-22 02:06:40 -04:00
parent 842082f818
commit d8f3be4bee
11 changed files with 1030 additions and 946 deletions

View File

@ -134,8 +134,7 @@ Zotero_Preferences.Advanced = {
if (Zotero_Preferences.Export) { if (Zotero_Preferences.Export) {
Zotero_Preferences.Export.populateQuickCopyList(); Zotero_Preferences.Export.populateQuickCopyList();
} }
}) });
.done();
} }
}, },
@ -160,8 +159,7 @@ Zotero_Preferences.Advanced = {
if (Zotero_Preferences.Export) { if (Zotero_Preferences.Export) {
Zotero_Preferences.Export.populateQuickCopyList(); Zotero_Preferences.Export.populateQuickCopyList();
} }
}) });
.done();
} }
}, },
@ -186,8 +184,7 @@ Zotero_Preferences.Advanced = {
if (Zotero_Preferences.Export) { if (Zotero_Preferences.Export) {
Zotero_Preferences.Export.populateQuickCopyList(); Zotero_Preferences.Export.populateQuickCopyList();
} }
}) });
.done();
} }
}, },

View File

@ -26,10 +26,10 @@
"use strict"; "use strict";
Zotero_Preferences.Cite = { Zotero_Preferences.Cite = {
init: function () { init: Zotero.Promise.coroutine(function* () {
this.updateWordProcessorInstructions(); this.updateWordProcessorInstructions();
this.refreshStylesList(); yield this.refreshStylesList();
}, }),
/** /**
@ -48,8 +48,9 @@ Zotero_Preferences.Cite = {
/** /**
* Refreshes the list of styles in the styles pane * Refreshes the list of styles in the styles pane
* @param {String} cslID Style to select * @param {String} cslID Style to select
* @return {Promise}
*/ */
refreshStylesList: function (cslID) { refreshStylesList: Zotero.Promise.coroutine(function* (cslID) {
Zotero.debug("Refreshing styles list"); Zotero.debug("Refreshing styles list");
var treechildren = document.getElementById('styleManager-rows'); var treechildren = document.getElementById('styleManager-rows');
@ -57,11 +58,9 @@ Zotero_Preferences.Cite = {
treechildren.removeChild(treechildren.firstChild); treechildren.removeChild(treechildren.firstChild);
} }
var styles = Zotero.Styles.getVisible(); var styles = yield Zotero.Styles.getVisible();
var selectIndex = false; var selectIndex = false;
var i = 0; styles.forEach(function (style, i) {
for each(var style in styles) {
var treeitem = document.createElement('treeitem'); var treeitem = document.createElement('treeitem');
var treerow = document.createElement('treerow'); var treerow = document.createElement('treerow');
var titleCell = document.createElement('treecell'); var titleCell = document.createElement('treecell');
@ -86,9 +85,8 @@ Zotero_Preferences.Cite = {
if (cslID == style.styleID) { if (cslID == style.styleID) {
document.getElementById('styleManager').view.selection.select(i); document.getElementById('styleManager').view.selection.select(i);
} }
i++; });
} }),
},
/** /**
@ -112,7 +110,7 @@ Zotero_Preferences.Cite = {
/** /**
* Deletes selected styles from the styles pane * Deletes selected styles from the styles pane
**/ **/
deleteStyle: function () { deleteStyle: Zotero.Promise.coroutine(function* () {
// get selected cslIDs // get selected cslIDs
var tree = document.getElementById('styleManager'); var tree = document.getElementById('styleManager');
var treeItems = tree.lastChild.childNodes; var treeItems = tree.lastChild.childNodes;
@ -141,17 +139,17 @@ Zotero_Preferences.Cite = {
if(ps.confirm(null, '', text)) { if(ps.confirm(null, '', text)) {
// delete if requested // delete if requested
if(cslIDs.length == 1) { if(cslIDs.length == 1) {
selectedStyle.remove(); yield selectedStyle.remove();
} else { } else {
for(var i=0; i<cslIDs.length; i++) { for(var i=0; i<cslIDs.length; i++) {
Zotero.Styles.get(cslIDs[i]).remove(); yield Zotero.Styles.get(cslIDs[i]).remove();
} }
} }
this.refreshStylesList(); yield this.refreshStylesList();
document.getElementById('styleManager-delete').disabled = true; document.getElementById('styleManager-delete').disabled = true;
} }
}, }),
/** /**

View File

@ -41,24 +41,24 @@ Zotero_Preferences.Export = {
/* /*
* Builds the main Quick Copy drop-down from the current global pref * Builds the main Quick Copy drop-down from the current global pref
*/ */
populateQuickCopyList: function () { populateQuickCopyList: Zotero.Promise.coroutine(function* () {
// Initialize default format drop-down // Initialize default format drop-down
var format = Zotero.Prefs.get("export.quickCopy.setting"); var format = Zotero.Prefs.get("export.quickCopy.setting");
var menulist = document.getElementById("zotero-quickCopy-menu"); var menulist = document.getElementById("zotero-quickCopy-menu");
menulist.setAttribute('preference', "pref-quickCopy-setting"); menulist.setAttribute('preference', "pref-quickCopy-setting");
this.buildQuickCopyFormatDropDown(menulist, Zotero.QuickCopy.getContentType(format), format); yield this.buildQuickCopyFormatDropDown(menulist, Zotero.QuickCopy.getContentType(format), format);
this.updateQuickCopyHTMLCheckbox(document); this.updateQuickCopyHTMLCheckbox(document);
if (!Zotero.isStandalone) { if (!Zotero.isStandalone) {
this.refreshQuickCopySiteList(); yield this.refreshQuickCopySiteList();
} }
}, }),
/* /*
* Builds a Quick Copy drop-down * Builds a Quick Copy drop-down
*/ */
buildQuickCopyFormatDropDown: function (menulist, contentType, currentFormat) { buildQuickCopyFormatDropDown: Zotero.Promise.coroutine(function* (menulist, contentType, currentFormat) {
if (!currentFormat) { if (!currentFormat) {
currentFormat = menulist.value; currentFormat = menulist.value;
} }
@ -84,8 +84,8 @@ Zotero_Preferences.Export = {
popup.appendChild(itemNode); popup.appendChild(itemNode);
// add styles to list // add styles to list
var styles = Zotero.Styles.getVisible(); var styles = yield Zotero.Styles.getVisible();
for each(var style in styles) { styles.forEach(function (style) {
var baseVal = 'bibliography=' + style.styleID; var baseVal = 'bibliography=' + style.styleID;
var val = 'bibliography' + (contentType == 'html' ? '/html' : '') + '=' + style.styleID; var val = 'bibliography' + (contentType == 'html' ? '/html' : '') + '=' + style.styleID;
var itemNode = document.createElement("menuitem"); var itemNode = document.createElement("menuitem");
@ -97,7 +97,7 @@ Zotero_Preferences.Export = {
if (baseVal == currentFormat) { if (baseVal == currentFormat) {
menulist.selectedItem = itemNode; menulist.selectedItem = itemNode;
} }
} });
var itemNode = document.createElement("menuitem"); var itemNode = document.createElement("menuitem");
itemNode.setAttribute("label", Zotero.getString('zotero.preferences.export.quickCopy.exportFormats')); itemNode.setAttribute("label", Zotero.getString('zotero.preferences.export.quickCopy.exportFormats'));
@ -106,31 +106,28 @@ Zotero_Preferences.Export = {
// add export formats to list // add export formats to list
var translation = new Zotero.Translate("export"); var translation = new Zotero.Translate("export");
translation.getTranslators() var translators = yield translation.getTranslators();
.then(function (translators) { translators.forEach(function (translator) {
for (var i=0; i<translators.length; i++) { // Skip RDF formats
// Skip RDF formats switch (translator.translatorID) {
switch (translators[i].translatorID) { case '6e372642-ed9d-4934-b5d1-c11ac758ebb7':
case '6e372642-ed9d-4934-b5d1-c11ac758ebb7': case '14763d24-8ba0-45df-8f52-b8d1108e7ac9':
case '14763d24-8ba0-45df-8f52-b8d1108e7ac9': return;
continue;
}
var val = 'export=' + translators[i].translatorID;
var itemNode = document.createElement("menuitem");
itemNode.setAttribute("value", val);
itemNode.setAttribute("label", translators[i].label);
itemNode.setAttribute("oncommand", 'Zotero_Preferences.Export.updateQuickCopyHTMLCheckbox(document)');
popup.appendChild(itemNode);
if (val == currentFormat) {
menulist.selectedItem = itemNode;
}
} }
var val = 'export=' + translator.translatorID;
var itemNode = document.createElement("menuitem");
itemNode.setAttribute("value", val);
itemNode.setAttribute("label", translator.label);
itemNode.setAttribute("oncommand", 'Zotero_Preferences.Export.updateQuickCopyHTMLCheckbox(document)');
popup.appendChild(itemNode);
menulist.click(); if (val == currentFormat) {
}) menulist.selectedItem = itemNode;
.done(); }
}, });
menulist.click();
}),
updateQuickCopyHTMLCheckbox: function (doc) { updateQuickCopyHTMLCheckbox: function (doc) {
@ -219,7 +216,7 @@ Zotero_Preferences.Export = {
var domainPath = treeitem.firstChild.firstChild.getAttribute('label'); var domainPath = treeitem.firstChild.firstChild.getAttribute('label');
yield Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='quickCopySite' AND key=?", [domainPath]); yield Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='quickCopySite' AND key=?", [domainPath]);
yield Zotero.QuickCopy.loadSiteSettings(); yield Zotero.QuickCopy.loadSiteSettings();
this.refreshQuickCopySiteList(); yield this.refreshQuickCopySiteList();
}), }),

View File

@ -39,27 +39,24 @@ Zotero_Preferences.General = {
}, },
updateTranslators: function () { updateTranslators: Zotero.Promise.coroutine(function* () {
Zotero.Schema.updateFromRepository(true) var updated = yield Zotero.Schema.updateFromRepository(true);
.then(function (updated) { var button = document.getElementById('updateButton');
var button = document.getElementById('updateButton'); if (button) {
if (button) { if (updated===-1) {
if (updated===-1) { var label = Zotero.getString('zotero.preferences.update.upToDate');
var label = Zotero.getString('zotero.preferences.update.upToDate');
}
else if (updated) {
var label = Zotero.getString('zotero.preferences.update.updated');
}
else {
var label = Zotero.getString('zotero.preferences.update.error');
}
button.setAttribute('label', label);
if (updated && Zotero_Preferences.Cite) {
Zotero_Preferences.Cite.refreshStylesList();
}
} }
}) else if (updated) {
.done(); var label = Zotero.getString('zotero.preferences.update.updated');
} }
else {
var label = Zotero.getString('zotero.preferences.update.error');
}
button.setAttribute('label', label);
if (updated && Zotero_Preferences.Cite) {
yield Zotero_Preferences.Cite.refreshStylesList();
}
}
})
} }

View File

@ -28,7 +28,6 @@
* @namespace * @namespace
*/ */
Zotero.File = new function(){ Zotero.File = new function(){
//Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/NetUtil.jsm"); Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm"); Components.utils.import("resource://gre/modules/FileUtils.jsm");
@ -44,15 +43,20 @@ Zotero.File = new function(){
this.pathToFile = function (pathOrFile) { this.pathToFile = function (pathOrFile) {
if (typeof pathOrFile == 'string') { if (typeof pathOrFile == 'string') {
let nsIFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); return new FileUtils.File(pathOrFile);
nsIFile.initWithPath(pathOrFile);
return nsIFile;
} }
else if (pathOrFile instanceof Ci.nsIFile) { else if (pathOrFile instanceof Ci.nsIFile) {
return pathOrFile; return pathOrFile;
} }
throw new Error('Unexpected value provided to Zotero.File.pathToFile() (' + pathOrFile + ')');
throw new Error('Unexpected value provided to Zotero.MIME.pathToFile() (' + pathOrFile + ')'); }
this.pathToFileURI = function (path) {
var file = new FileUtils.File(path);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
return ios.newFileURI(file).spec;
} }
@ -133,7 +137,7 @@ Zotero.File = new function(){
/** /**
* Get the contents of a file or input stream * Get the contents of a file or input stream
* @param {nsIFile|nsIInputStream} file The file to read * @param {nsIFile|nsIInputStream|string path} file The file to read
* @param {String} [charset] The character set; defaults to UTF-8 * @param {String} [charset] The character set; defaults to UTF-8
* @param {Integer} [maxLength] The maximum number of bytes to read * @param {Integer} [maxLength] The maximum number of bytes to read
* @return {String} The contents of the file * @return {String} The contents of the file
@ -141,6 +145,11 @@ Zotero.File = new function(){
*/ */
this.getContents = function (file, charset, maxLength){ this.getContents = function (file, charset, maxLength){
var fis; var fis;
if (typeof file == 'string') {
file = new FileUtils.File(file);
}
if(file instanceof Components.interfaces.nsIInputStream) { if(file instanceof Components.interfaces.nsIInputStream) {
fis = file; fis = file;
} else if(file instanceof Components.interfaces.nsIFile) { } else if(file instanceof Components.interfaces.nsIFile) {
@ -282,7 +291,7 @@ Zotero.File = new function(){
* Return a promise for the contents of a URL as a string * Return a promise for the contents of a URL as a string
*/ */
this.getContentsFromURLAsync = function (url) { this.getContentsFromURLAsync = function (url) {
return Zotero.HTTP.promise("GET", url, { responseType: "text" }) return Zotero.HTTP.request("GET", url, { responseType: "text" })
.then(function (xmlhttp) { .then(function (xmlhttp) {
return xmlhttp.response; return xmlhttp.response;
}); });
@ -364,16 +373,16 @@ Zotero.File = new function(){
/** /**
* Delete a file if it exists, asynchronously * Delete a file if it exists, asynchronously
* *
* @return {Promise<Boolean>} A Q promise for TRUE if file was deleted, * @return {Promise<Boolean>} A promise for TRUE if file was deleted, FALSE if missing
* FALSE if missing
*/ */
this.deleteIfExists = function deleteIfExists(path) { this.removeIfExists = function (path) {
return Zotero.Promise.resolve(OS.File.remove(path)) return Zotero.Promise.resolve(OS.File.remove(path))
.thenResolve(true) .return(true)
.catch(function (e) { .catch(function (e) {
if (e instanceof OS.File.Error && e.becauseNoSuchFile) { if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
return false; return false;
} }
Zotero.debug(path, 1);
throw e; throw e;
}); });
} }
@ -575,6 +584,19 @@ Zotero.File = new function(){
} }
this.createDirectoryIfMissingAsync = function (path) {
return Zotero.Promise.resolve(
OS.File.makeDir(
path,
{
ignoreExisting: true,
unixMode: 0755
}
)
);
}
/** /**
* Check whether a directory is an ancestor directory of another directory/file * Check whether a directory is an ancestor directory of another directory/file
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -30,28 +30,50 @@
*/ */
Zotero.Styles = new function() { Zotero.Styles = new function() {
var _initialized = false; var _initialized = false;
var _styles, _visibleStyles, _cacheTranslatorData; var _styles, _visibleStyles;
var _renamedStyles = null; var _renamedStyles = null;
//Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
this.xsltProcessor = null; this.xsltProcessor = null;
this.ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
this.ns = { this.ns = {
"csl":"http://purl.org/net/xbiblio/csl" "csl":"http://purl.org/net/xbiblio/csl"
}; };
// TEMP
// Until we get asynchronous style loading, load renamed styles at startup, since the /**
// synchronous call we were using breaks the first drag of the session (on OS X, at least) * Initializes styles cache, loading metadata for styles into memory
this.preinit = function () { */
this.reinit = Zotero.Promise.coroutine(function* () {
Zotero.debug("Initializing styles");
var start = new Date;
_initialized = true;
_styles = {};
_visibleStyles = [];
this.lastCSL = null;
// main dir
var dir = Zotero.getStylesDirectory().path;
var num = yield _readStylesFromDirectory(dir, false);
// hidden dir
var hiddenDir = OS.Path.join(dir, 'hidden');
if (yield OS.File.exists(hiddenDir)) {
num += yield _readStylesFromDirectory(hiddenDir, true);
}
Zotero.debug("Cached " + num + " styles in " + (new Date - start) + " ms");
_renamedStyles = {}; _renamedStyles = {};
Zotero.HTTP.promise( yield Zotero.HTTP.request(
"GET", "resource://zotero/schema/renamed-styles.json", { responseType: 'json' } "GET",
"resource://zotero/schema/renamed-styles.json",
{
responseType: 'json'
}
) )
.then(function (xmlhttp) { .then(function (xmlhttp) {
// Map some obsolete styles to current ones // Map some obsolete styles to current ones
@ -59,87 +81,70 @@ Zotero.Styles = new function() {
_renamedStyles = xmlhttp.response; _renamedStyles = xmlhttp.response;
} }
}) })
.done(); });
} this.init = Zotero.lazy(this.reinit);
/**
* Initializes styles cache, loading metadata for styles into memory
*/
this.init = function() {
_initialized = true;
var start = (new Date()).getTime()
_styles = {};
_visibleStyles = [];
_cacheTranslatorData = Zotero.Prefs.get("cacheTranslatorData");
this.lastCSL = null;
// main dir
var dir = Zotero.getStylesDirectory();
var i = _readStylesFromDirectory(dir, false);
// hidden dir
dir.append("hidden");
if(dir.exists()) i += _readStylesFromDirectory(dir, true);
Zotero.debug("Cached "+i+" styles in "+((new Date()).getTime() - start)+" ms");
}
/** /**
* Reads all styles from a given directory and caches their metadata * Reads all styles from a given directory and caches their metadata
* @private * @private
*/ */
function _readStylesFromDirectory(dir, hidden) { var _readStylesFromDirectory = Zotero.Promise.coroutine(function* (dir, hidden) {
var i = 0; var numCached = 0;
var contents = dir.directoryEntries;
while(contents.hasMoreElements()) { var iterator = new OS.File.DirectoryIterator(dir);
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile), try {
filename = file.leafName; while (true) {
if(!filename || filename[0] === "." let entries = yield iterator.nextBatch(10); // TODO: adjust as necessary
|| filename.substr(-4).toLowerCase() !== ".csl" if (!entries.length) break;
|| file.isDirectory()) continue;
for (let i = 0; i < entries.length; i++) {
try { let entry = entries[i];
var style = new Zotero.Style(file); let path = entry.path;
} let fileName = entry.name;
catch (e) { if (!fileName || fileName[0] === "."
Zotero.log( || fileName.substr(-4).toLowerCase() !== ".csl"
"Error loading style '" + file.leafName + "': " + e.message, || entry.isDir) continue;
"error",
file.path, try {
null, let code = yield Zotero.File.getContentsAsync(path);
e.lineNumber var style = new Zotero.Style(code, path);
); }
continue; catch (e) {
} Components.utils.reportError(e);
if(style.styleID) { Zotero.debug(e, 1);
if(_styles[style.styleID]) { continue;
// same style is already cached }
Zotero.log('Style with ID '+style.styleID+' already loaded from "'+ if(style.styleID) {
_styles[style.styleID].file.leafName+'"', "error", // same style is already cached
Zotero.Styles.ios.newFileURI(style.file).spec); if (_styles[style.styleID]) {
} else { Components.utils.reportError('Style with ID ' + style.styleID
// add to cache + ' already loaded from ' + _styles[style.styleID].fileName);
_styles[style.styleID] = style; } else {
_styles[style.styleID].hidden = hidden; // add to cache
if(!hidden) _visibleStyles.push(style); _styles[style.styleID] = style;
_styles[style.styleID].hidden = hidden;
if(!hidden) _visibleStyles.push(style);
}
}
numCached++;
} }
} }
i++;
} }
return i; finally {
} iterator.close();
}
return numCached;
});
/** /**
* Gets a style with a given ID * Gets a style with a given ID
* @param {String} id * @param {String} id
* @param {Boolean} skipMappings Don't automatically return renamed style * @param {Boolean} skipMappings Don't automatically return renamed style
*/ */
this.get = function(id, skipMappings) { this.get = function (id, skipMappings) {
if(!_initialized) this.init(); if (!_initialized) {
throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
// TODO: With asynchronous style loading, move renamedStyles call back here }
if(!skipMappings) { if(!skipMappings) {
var prefix = "http://www.zotero.org/styles/"; var prefix = "http://www.zotero.org/styles/";
@ -156,20 +161,24 @@ Zotero.Styles = new function() {
/** /**
* Gets all visible styles * Gets all visible styles
* @return {Zotero.Style[]} An array of Zotero.Style objects * @return {Promise<Zotero.Style[]>} A promise for an array of Zotero.Style objects
*/ */
this.getVisible = function() { this.getVisible = function () {
if(!_initialized || !_cacheTranslatorData) this.init(); return this.init().then(function () {
return _visibleStyles.slice(0); return _visibleStyles.slice(0);
});
} }
/** /**
* Gets all styles * Gets all styles
* @return {Object} An object whose keys are style IDs, and whose values are Zotero.Style objects *
* @return {Promise<Object>} A promise for an object with style IDs for keys and
* Zotero.Style objects for values
*/ */
this.getAll = function() { this.getAll = function () {
if(!_initialized || !_cacheTranslatorData) this.init(); return this.init().then(function () {
return _styles; return _styles;
});
} }
/** /**
@ -200,8 +209,9 @@ Zotero.Styles = new function() {
* @param {String} origin The origin of the style, either a filename or URL, to be * @param {String} origin The origin of the style, either a filename or URL, to be
* displayed in dialogs referencing the style * displayed in dialogs referencing the style
*/ */
this.install = function(style, origin) { this.install = Zotero.Promise.coroutine(function* (style, origin) {
var styleInstalled; var styleInstalled;
if(style instanceof Components.interfaces.nsIFile) { if(style instanceof Components.interfaces.nsIFile) {
// handle nsIFiles // handle nsIFiles
var url = Services.io.newFileURI(style); var url = Services.io.newFileURI(style);
@ -224,7 +234,7 @@ Zotero.Styles = new function() {
origin, "styles.install.title", error)).present(); origin, "styles.install.title", error)).present();
} }
}).done(); }).done();
} });
/** /**
* Installs a style * Installs a style
@ -234,176 +244,183 @@ Zotero.Styles = new function() {
* @param {Boolean} [hidden] Whether style is to be hidden. * @param {Boolean} [hidden] Whether style is to be hidden.
* @return {Promise} * @return {Promise}
*/ */
function _install(style, origin, hidden) { var _install = Zotero.Promise.coroutine(function* (style, origin, hidden) {
if(!_initialized || !_cacheTranslatorData) Zotero.Styles.init(); if (!_initialized) yield Zotero.Styles.init();
var existingFile, destFile, source, styleID var existingFile, destFile, source, styleID
return Zotero.Promise.try(function() {
// First, parse style and make sure it's valid XML // First, parse style and make sure it's valid XML
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser), .createInstance(Components.interfaces.nsIDOMParser),
doc = parser.parseFromString(style, "application/xml"); doc = parser.parseFromString(style, "application/xml");
styleID = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:id[1]', styleID = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:id[1]',
Zotero.Styles.ns), Zotero.Styles.ns),
// Get file name from URL // Get file name from URL
m = /[^\/]+$/.exec(styleID), m = /[^\/]+$/.exec(styleID),
fileName = Zotero.File.getValidFileName(m ? m[0] : styleID), fileName = Zotero.File.getValidFileName(m ? m[0] : styleID),
title = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:title[1]', title = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:title[1]',
Zotero.Styles.ns);
if(!styleID || !title) {
// If it's not valid XML, we'll return a promise that immediately resolves
// to an error
throw new Zotero.Exception.Alert("styles.installError", origin,
"styles.install.title", "Style is not valid XML, or the styleID or title is missing");
}
// look for a parent
source = Zotero.Utilities.xpathText(doc,
'/csl:style/csl:info[1]/csl:link[@rel="source" or @rel="independent-parent"][1]/@href',
Zotero.Styles.ns); Zotero.Styles.ns);
if(source == styleID) {
throw new Zotero.Exception.Alert("styles.installError", origin, if(!styleID || !title) {
"styles.install.title", "Style references itself as source"); // If it's not valid XML, we'll return a promise that immediately resolves
// to an error
throw new Zotero.Exception.Alert("styles.installError", origin,
"styles.install.title", "Style is not valid XML, or the styleID or title is missing");
}
// look for a parent
source = Zotero.Utilities.xpathText(doc,
'/csl:style/csl:info[1]/csl:link[@rel="source" or @rel="independent-parent"][1]/@href',
Zotero.Styles.ns);
if(source == styleID) {
throw new Zotero.Exception.Alert("styles.installError", origin,
"styles.install.title", "Style references itself as source");
}
// ensure csl extension
if(fileName.substr(-4).toLowerCase() != ".csl") fileName += ".csl";
destFile = Zotero.getStylesDirectory();
var destFileHidden = destFile.clone();
destFile.append(fileName);
destFileHidden.append("hidden");
if(hidden) Zotero.File.createDirectoryIfMissing(destFileHidden);
destFileHidden.append(fileName);
// look for an existing style with the same styleID or filename
var existingTitle;
if(_styles[styleID]) {
existingFile = _styles[styleID].file;
existingTitle = _styles[styleID].title;
} else {
if(destFile.exists()) {
existingFile = destFile;
} else if(destFileHidden.exists()) {
existingFile = destFileHidden;
} }
// ensure csl extension if(existingFile) {
if(fileName.substr(-4).toLowerCase() != ".csl") fileName += ".csl"; // find associated style
for each(var existingStyle in _styles) {
destFile = Zotero.getStylesDirectory(); if(destFile.equals(existingStyle.file)) {
var destFileHidden = destFile.clone();
destFile.append(fileName);
destFileHidden.append("hidden");
if(hidden) Zotero.File.createDirectoryIfMissing(destFileHidden);
destFileHidden.append(fileName);
// look for an existing style with the same styleID or filename
var existingTitle;
if(_styles[styleID]) {
existingFile = _styles[styleID].file;
existingTitle = _styles[styleID].title;
} else {
if(destFile.exists()) {
existingFile = destFile;
} else if(destFileHidden.exists()) {
existingFile = destFileHidden;
}
if(existingFile) {
// find associated style
for each(var existingStyle in _styles) {
if(destFile.equals(existingStyle.file)) {
existingTitle = existingStyle.title;
break;
}
}
}
}
// also look for an existing style with the same title
if(!existingFile) {
for each(var existingStyle in Zotero.Styles.getAll()) {
if(title === existingStyle.title) {
existingFile = existingStyle.file;
existingTitle = existingStyle.title; existingTitle = existingStyle.title;
break; break;
} }
} }
} }
}
// display a dialog to tell the user we're about to install the style
if(hidden) {
destFile = destFileHidden;
} else {
if(existingTitle) {
var text = Zotero.getString('styles.updateStyle', [existingTitle, title, origin]);
} else {
var text = Zotero.getString('styles.installStyle', [title, origin]);
}
var index = Services.prompt.confirmEx(null, Zotero.getString('styles.install.title'),
text,
((Services.prompt.BUTTON_POS_0) * (Services.prompt.BUTTON_TITLE_IS_STRING)
+ (Services.prompt.BUTTON_POS_1) * (Services.prompt.BUTTON_TITLE_CANCEL)),
Zotero.getString('general.install'), null, null, null, {}
);
if(index !== 0) {
throw new Zotero.Exception.UserCancelled("style installation");
}
}
return Zotero.Styles.validate(style).catch(function(validationErrors) { // also look for an existing style with the same title
Zotero.logError("Style from "+origin+" failed to validate:\n\n"+validationErrors); if(!existingFile) {
let styles = yield Zotero.Styles.getAll();
// If validation fails on the parent of a dependent style, ignore it (for now) for (let i in styles) {
if(hidden) return; let existingStyle = styles[i];
if(title === existingStyle.title) {
// If validation fails on a different style, we ask the user if s/he really existingFile = existingStyle.file;
// wants to install it existingTitle = existingStyle.title;
Components.utils.import("resource://gre/modules/Services.jsm"); break;
var shouldInstall = Services.prompt.confirmEx(null,
Zotero.getString('styles.install.title'),
Zotero.getString('styles.validationWarning', origin),
(Services.prompt.BUTTON_POS_0) * (Services.prompt.BUTTON_TITLE_OK)
+ (Services.prompt.BUTTON_POS_1) * (Services.prompt.BUTTON_TITLE_CANCEL)
+ Services.prompt.BUTTON_POS_1_DEFAULT + Services.prompt.BUTTON_DELAY_ENABLE,
null, null, null, null, {}
);
if(shouldInstall !== 0) {
throw new Zotero.Exception.UserCancelled("style installation");
}
});
}).then(function() {
// User wants to install/update
if(source && !_styles[source]) {
// Need to fetch source
if(source.substr(0, 7) === "http://" || source.substr(0, 8) === "https://") {
return Zotero.HTTP.promise("GET", source).then(function(xmlhttp) {
return _install(xmlhttp.responseText, origin, true);
}).catch(function(error) {
if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
throw new Zotero.Exception.Alert("styles.installSourceError", [origin, source],
"styles.install.title", error);
} else {
throw error;
}
});
} else {
throw new Zotero.Exception.Alert("styles.installSourceError", [origin, source],
"styles.install.title", "Source CSL URI is invalid");
} }
} }
}).then(function() { }
// Dependent style has been retrieved if there was one, so we're ready to
// continue // display a dialog to tell the user we're about to install the style
if(hidden) {
destFile = destFileHidden;
} else {
if(existingTitle) {
var text = Zotero.getString('styles.updateStyle', [existingTitle, title, origin]);
} else {
var text = Zotero.getString('styles.installStyle', [title, origin]);
}
// Remove any existing file with a different name var index = Services.prompt.confirmEx(null, Zotero.getString('styles.install.title'),
if(existingFile) existingFile.remove(false); text,
((Services.prompt.BUTTON_POS_0) * (Services.prompt.BUTTON_TITLE_IS_STRING)
+ (Services.prompt.BUTTON_POS_1) * (Services.prompt.BUTTON_TITLE_CANCEL)),
Zotero.getString('general.install'), null, null, null, {}
);
return Zotero.File.putContentsAsync(destFile, style); if(index !== 0) {
}).then(function() { throw new Zotero.Exception.UserCancelled("style installation");
// Cache }
Zotero.Styles.init(); }
yield Zotero.Styles.validate(style)
.catch(function(validationErrors) {
Zotero.logError("Style from " + origin + " failed to validate:\n\n" + validationErrors);
// Refresh preferences windows // If validation fails on the parent of a dependent style, ignore it (for now)
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]. if(hidden) return;
getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator("zotero:pref"); // If validation fails on a different style, we ask the user if s/he really
while(enumerator.hasMoreElements()) { // wants to install it
var win = enumerator.getNext(); Components.utils.import("resource://gre/modules/Services.jsm");
if(win.Zotero_Preferences.Cite) { var shouldInstall = Services.prompt.confirmEx(null,
win.Zotero_Preferences.Cite.refreshStylesList(styleID); Zotero.getString('styles.install.title'),
} Zotero.getString('styles.validationWarning', origin),
(Services.prompt.BUTTON_POS_0) * (Services.prompt.BUTTON_TITLE_OK)
+ (Services.prompt.BUTTON_POS_1) * (Services.prompt.BUTTON_TITLE_CANCEL)
+ Services.prompt.BUTTON_POS_1_DEFAULT + Services.prompt.BUTTON_DELAY_ENABLE,
null, null, null, null, {}
);
if(shouldInstall !== 0) {
throw new Zotero.Exception.UserCancelled("style installation");
} }
}); });
}
// User wants to install/update
if(source && !_styles[source]) {
// Need to fetch source
if(source.substr(0, 7) === "http://" || source.substr(0, 8) === "https://") {
try {
let xmlhttp = yield Zotero.HTTP.request("GET", source);
yield _install(xmlhttp.responseText, origin, true);
}
catch (e) {
if (typeof e === "object" && e instanceof Zotero.Exception.Alert) {
throw new Zotero.Exception.Alert(
"styles.installSourceError",
[origin, source],
"styles.install.title",
e
);
}
throw e;
}
} else {
throw new Zotero.Exception.Alert("styles.installSourceError", [origin, source],
"styles.install.title", "Source CSL URI is invalid");
}
}
// Dependent style has been retrieved if there was one, so we're ready to
// continue
// Remove any existing file with a different name
if(existingFile) existingFile.remove(false);
yield Zotero.File.putContentsAsync(destFile, style);
yield Zotero.Styles.reinit();
// Refresh preferences windows
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator("zotero:pref");
while(enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if(win.Zotero_Preferences.Cite) {
yield win.Zotero_Preferences.Cite.refreshStylesList(styleID);
}
}
});
} }
/** /**
* @class Represents a style file and its metadata * @class Represents a style file and its metadata
* @property {nsIFile} file The path to the style file * @property {String} path The path to the style file
* @property {String} fileName The name of the style file
* @property {String} styleID * @property {String} styleID
* @property {String} url The URL where the style can be found (rel="self") * @property {String} url The URL where the style can be found (rel="self")
* @property {String} type "csl" for CSL styles * @property {String} type "csl" for CSL styles
@ -416,25 +433,25 @@ Zotero.Styles = new function() {
* @property {Boolean} hidden True if this style is hidden in style selection dialogs, false if it * @property {Boolean} hidden True if this style is hidden in style selection dialogs, false if it
* is not * is not
*/ */
Zotero.Style = function(arg) { Zotero.Style = function (style, path) {
if(typeof arg === "string") { if (typeof style != "string") {
this.string = arg; throw new Error("Style code must be a string");
} else if(typeof arg === "object") {
this.file = arg;
} else {
throw "Invalid argument passed to Zotero.Style";
} }
this.type = "csl"; this.type = "csl";
var style = typeof arg === "string" ? arg : Zotero.File.getContents(arg), var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser), .createInstance(Components.interfaces.nsIDOMParser),
doc = parser.parseFromString(style, "application/xml"); doc = parser.parseFromString(style, "application/xml");
if(doc.documentElement.localName === "parsererror") { if(doc.documentElement.localName === "parsererror") {
throw new Error("File is not valid XML"); throw new Error("File is not valid XML");
} }
if (path) {
this.path = path;
this.fileName = OS.Path.basename(path);
}
this.styleID = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:id[1]', this.styleID = Zotero.Utilities.xpathText(doc, '/csl:style/csl:info[1]/csl:id[1]',
Zotero.Styles.ns); Zotero.Styles.ns);
this.url = Zotero.Utilities.xpathText(doc, this.url = Zotero.Utilities.xpathText(doc,
@ -494,8 +511,10 @@ Zotero.Style.prototype.getCiteProc = function(automaticJournalAbbreviations) {
if(this.source) { if(this.source) {
var parentStyle = Zotero.Styles.get(this.source); var parentStyle = Zotero.Styles.get(this.source);
if(!parentStyle) { if(!parentStyle) {
throw(new Error('Style references '+this.source+', but this style is not installed', throw new Error(
Zotero.Styles.ios.newFileURI(this.file).spec, null)); 'Style references ' + this.source + ', but this style is not installed',
Zotero.Utilities.pathToFileURI(this.path)
);
} }
var version = parentStyle._version; var version = parentStyle._version;
@ -552,11 +571,6 @@ Zotero.Style.prototype.getCiteProc = function(automaticJournalAbbreviations) {
} }
}; };
Zotero.Style.prototype.__defineGetter__("csl", function() {
Zotero.logError("Zotero.Style.csl is deprecated. Use Zotero.Style.getCiteProc()");
return this.getCiteProc();
});
Zotero.Style.prototype.__defineGetter__("class", Zotero.Style.prototype.__defineGetter__("class",
/** /**
* Retrieves the style class, either from the metadata that's already loaded or by loading the file * Retrieves the style class, either from the metadata that's already loaded or by loading the file
@ -578,8 +592,7 @@ function() {
// use hasBibliography from source style // use hasBibliography from source style
var parentStyle = Zotero.Styles.get(this.source); var parentStyle = Zotero.Styles.get(this.source);
if(!parentStyle) { if(!parentStyle) {
throw(new Error('Style references '+this.source+', but this style is not installed', throw new Error('Style references missing parent ' + this.source);
Zotero.Styles.ios.newFileURI(this.file).spec, null));
} }
return parentStyle.hasBibliography; return parentStyle.hasBibliography;
} }
@ -610,12 +623,11 @@ function() {
// parent/child // parent/child
var formatCSL = Zotero.Styles.get(this.source); var formatCSL = Zotero.Styles.get(this.source);
if(!formatCSL) { if(!formatCSL) {
throw(new Error('Style references '+this.source+', but this style is not installed', throw new Error('Style references missing parent ' + this.source);
Zotero.Styles.ios.newFileURI(this.file).spec, null));
} }
return formatCSL.file; return formatCSL.path;
} else if(this.file) { } else if (this.path) {
return this.file; return this.path;
} }
return null; return null;
}); });
@ -633,15 +645,16 @@ Zotero.Style.prototype.getXML = function() {
/** /**
* Deletes a style * Deletes a style
*/ */
Zotero.Style.prototype.remove = function() { Zotero.Style.prototype.remove = Zotero.Promise.coroutine(function* () {
if(!this.file) { if (!this.path) {
throw "Cannot delete a style with no associated file." throw new Error("Cannot delete a style with no associated file")
} }
// make sure no styles depend on this one // make sure no styles depend on this one
var dependentStyles = false; var dependentStyles = false;
var styles = Zotero.Styles.getAll(); var styles = yield Zotero.Styles.getAll();
for each(var style in styles) { for (let i in styles) {
let style = styles[i];
if(style.source == this.styleID) { if(style.source == this.styleID) {
dependentStyles = true; dependentStyles = true;
break; break;
@ -650,13 +663,12 @@ Zotero.Style.prototype.remove = function() {
if(dependentStyles) { if(dependentStyles) {
// copy dependent styles to hidden directory // copy dependent styles to hidden directory
var hiddenDir = Zotero.getStylesDirectory(); let hiddenDir = OS.Path.join(Zotero.getStylesDirectory().path, 'hidden');
hiddenDir.append("hidden"); yield Zotero.File.createDirectoryIfMissingAsync(hiddenDir);
Zotero.File.createDirectoryIfMissing(hiddenDir); yield OS.File.move(this.path, OS.Path.join(hiddenDir, OS.Path.basename(this.path)));
this.file.moveTo(hiddenDir, null);
} else { } else {
// remove defunct files // remove defunct files
this.file.remove(false); yield OS.File.remove(this.path);
} }
// check to see if this style depended on a hidden one // check to see if this style depended on a hidden one
@ -666,7 +678,9 @@ Zotero.Style.prototype.remove = function() {
var deleteSource = true; var deleteSource = true;
// check to see if any other styles depend on the hidden one // check to see if any other styles depend on the hidden one
for each(var style in Zotero.Styles.getAll()) { let styles = yield Zotero.Styles.getAll();
for (let i in styles) {
let style = styles[i];
if(style.source == this.source && style.styleID != this.styleID) { if(style.source == this.source && style.styleID != this.styleID) {
deleteSource = false; deleteSource = false;
break; break;
@ -675,10 +689,10 @@ Zotero.Style.prototype.remove = function() {
// if it was only this style with the dependency, delete the source // if it was only this style with the dependency, delete the source
if(deleteSource) { if(deleteSource) {
source.remove(); yield source.remove();
} }
} }
} }
Zotero.Styles.init(); return Zotero.Styles.reinit();
} });

View File

@ -61,7 +61,8 @@ var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browser
* @property {String} lastUpdated SQL-style date and time of translator's last update * @property {String} lastUpdated SQL-style date and time of translator's last update
* @property {String} code The executable JavaScript for the translator * @property {String} code The executable JavaScript for the translator
* @property {Boolean} cacheCode Whether to cache code for this session (non-connector only) * @property {Boolean} cacheCode Whether to cache code for this session (non-connector only)
* @property {nsIFile} [file] File corresponding to this translator (non-connector only) * @property {String} [path] File path corresponding to this translator (non-connector only)
* @property {String} [fileName] File name corresponding to this translator (non-connector only)
*/ */
Zotero.Translator = function(info) { Zotero.Translator = function(info) {
this.init(info); this.init(info);
@ -119,7 +120,10 @@ Zotero.Translator.prototype.init = function(info) {
delete this.webRegexp; delete this.webRegexp;
} }
if(info.file) this.file = info.file; if (info.path) {
this.path = info.path;
this.fileName = OS.Path.basename(info.path);
}
if(info.code && this.cacheCode) { if(info.code && this.cacheCode) {
this.code = info.code; this.code = info.code;
} else if(this.hasOwnProperty("code")) { } else if(this.hasOwnProperty("code")) {
@ -148,7 +152,7 @@ Zotero.Translator.prototype.getCode = function() {
return code; return code;
}); });
} else { } else {
var promise = Zotero.File.getContentsAsync(this.file); var promise = Zotero.File.getContentsAsync(this.path);
if(this.cacheCode) { if(this.cacheCode) {
// Cache target-less web translators for session, since we // Cache target-less web translators for session, since we
// will use them a lot // will use them a lot
@ -168,7 +172,7 @@ Zotero.Translator.prototype.serialize = function(properties) {
var info = {}; var info = {};
for(var i in properties) { for(var i in properties) {
var property = properties[i]; var property = properties[i];
info[property] = translator[property]; info[property] = this[property];
} }
return info; return info;
} }
@ -182,10 +186,12 @@ Zotero.Translator.prototype.serialize = function(properties) {
* @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) {
if(Zotero.isFx && this.file) { if (Zotero.isFx && this.path) {
Components.utils.import("resource://gre/modules/FileUtils.jsm");
var file = new FileUtils.File(this.path);
var ios = Components.classes["@mozilla.org/network/io-service;1"]. var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService); getService(Components.interfaces.nsIIOService);
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec); Zotero.log(message, type ? type : "error", ios.newFileURI(file).spec);
} else { } else {
Zotero.logError(message); Zotero.logError(message);
} }

View File

@ -35,85 +35,113 @@ Zotero.Translators = new function() {
var _initialized = false; var _initialized = false;
/** /**
* Initializes translator cache, loading all relevant translators into memory * Initializes translator cache, loading all translator metadata into memory
*/ */
this.reinit = Zotero.Promise.coroutine(function* () { this.reinit = Zotero.Promise.coroutine(function* () {
var start = (new Date()).getTime(); if (_initialized) {
var transactionStarted = false; Zotero.debug("Translators already initialized", 2);
return;
}
Zotero.debug("Initializing translators");
var start = new Date;
_initialized = true;
_cache = {"import":[], "export":[], "web":[], "search":[]}; _cache = {"import":[], "export":[], "web":[], "search":[]};
_translators = {}; _translators = {};
var dbCacheResults = yield Zotero.DB.queryAsync("SELECT leafName, translatorJSON, "+ var sql = "SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache";
"code, lastModifiedTime FROM translatorCache"); var dbCacheResults = yield Zotero.DB.queryAsync(sql);
var dbCache = {}; var dbCache = {};
for each(var cacheEntry in dbCacheResults) { for (let i = 0; i < dbCacheResults.length; i++) {
dbCache[cacheEntry.leafName] = cacheEntry; let entry = dbCacheResults[i];
dbCache[entry.fileName] = entry;
} }
var i = 0; var numCached = 0;
var filesInCache = {}; var filesInCache = {};
var contents = Zotero.getTranslatorsDirectory().directoryEntries; var translatorsDir = Zotero.getTranslatorsDirectory().path;
while(contents.hasMoreElements()) { var iterator = new OS.File.DirectoryIterator(translatorsDir);
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); try {
var leafName = file.leafName; while (true) {
if(!(/^[^.].*\.js$/.test(leafName))) continue; let entries = yield iterator.nextBatch(5); // TODO: adjust as necessary
var lastModifiedTime = file.lastModifiedTime; if (!entries.length) break;
for (let i = 0; i < entries.length; i++) {
var dbCacheEntry = false; let entry = entries[i];
if(dbCache[leafName]) { let path = entry.path;
filesInCache[leafName] = true; let fileName = entry.name;
if(dbCache[leafName].lastModifiedTime == lastModifiedTime) {
dbCacheEntry = dbCache[file.leafName]; if (!(/^[^.].*\.js$/.test(fileName))) continue;
}
} let lastModifiedTime;
if ('winLastWriteDate' in entry) {
if(dbCacheEntry) { lastModifiedTime = entry.winLastWriteDate.getTime();
// get JSON from cache if possible }
var translator = Zotero.Translators.load(file, dbCacheEntry.translatorJSON, dbCacheEntry.code); else {
filesInCache[leafName] = true; lastModifiedTime = (yield OS.File.stat(path)).lastModificationDate.getTime();
} else { }
// otherwise, load from file let lastModified
var translator = yield Zotero.Translators.loadFromDisk(file);
} var dbCacheEntry = false;
if (dbCache[fileName]) {
if(translator.translatorID) { filesInCache[fileName] = true;
if(_translators[translator.translatorID]) { if (dbCache[fileName].lastModifiedTime == lastModifiedTime) {
// same translator is already cached dbCacheEntry = dbCache[fileName];
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) { if(dbCacheEntry) {
var code = yield translator.getCode(); // get JSON from cache if possible
yield Zotero.Translators.cacheInDB( var translator = Zotero.Translators.load(dbCacheEntry.metadataJSON, path);
leafName, filesInCache[fileName] = true;
translator.serialize(TRANSLATOR_REQUIRED_PROPERTIES. } else {
concat(TRANSLATOR_OPTIONAL_PROPERTIES)), // otherwise, load from file
translator.cacheCode ? translator.code : null, var translator = yield Zotero.Translators.loadFromFile(path);
lastModifiedTime
);
delete translator.metadataString;
} }
// When can this happen?
if (!translator.translatorID) {
Zotero.debug("Translator ID for " + path + " not found");
continue;
}
if (_translators[translator.translatorID]) {
// same translator is already cached
translator.logError('Translator with ID '+
translator.translatorID+' already loaded from "'+
_translators[translator.translatorID].fileName + '"');
} 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(
fileName,
translator.serialize(TRANSLATOR_REQUIRED_PROPERTIES.
concat(TRANSLATOR_OPTIONAL_PROPERTIES)),
lastModifiedTime
);
}
}
numCached++;
} }
} }
}
i++; finally {
iterator.close();
} }
// Remove translators from DB as necessary // Remove translators from DB as necessary
for(var leafName in dbCache) { for (let fileName in dbCache) {
if(!filesInCache[leafName]) { if (!filesInCache[fileName]) {
yield Zotero.DB.queryAsync( yield Zotero.DB.queryAsync(
"DELETE FROM translatorCache WHERE leafName = ?", [leafName] "DELETE FROM translatorCache WHERE fileName = ?", fileName
); );
} }
} }
@ -133,56 +161,55 @@ Zotero.Translators = new function() {
_cache[type].sort(cmp); _cache[type].sort(cmp);
} }
Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms"); Zotero.debug("Cached " + numCached + " translators in " + ((new Date) - start) + " ms");
}); });
this.init = Zotero.lazy(this.reinit); this.init = Zotero.lazy(this.reinit);
/** /**
* Loads a translator from JSON, with optional code * Loads a translator from JSON, with optional code
*/ */
this.load = function(file, json, code) { this.load = function (json, path, code) {
var info = JSON.parse(json); var info = JSON.parse(json);
info.file = file; info.path = path;
info.code = code; info.code = code;
return new Zotero.Translator(info); return new Zotero.Translator(info);
} }
/** /**
* Loads a translator from the disk * Loads a translator from the disk
*
* @param {String} file - Path to translator file
*/ */
this.loadFromDisk = function(file) { this.loadFromFile = function(path) {
const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/; const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/;
return Zotero.File.getContentsAsync(file) return Zotero.File.getContentsAsync(path)
.then(function(source) { .then(function(source) {
return Zotero.Translators.load(file, infoRe.exec(source)[0], source); return Zotero.Translators.load(infoRe.exec(source)[0], path, source);
}) })
.catch(function() { .catch(function() {
throw "Invalid or missing translator metadata JSON object in " + file.leafName; throw "Invalid or missing translator metadata JSON object in " + OS.Path.basename(path);
}); });
} }
/** /**
* Gets the translator that corresponds to a given ID * Gets the translator that corresponds to a given ID
*
* @param {String} id The ID of the translator * @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) { this.get = function(id) {
return this.init().then(function() { if (!_initialized) {
return _translators[id] ? _translators[id] : false throw new Zotero.Exception.UnloadedDataException("Translators not yet loaded", 'translators');
}); }
return _translators[id] ? _translators[id] : false
} }
/** /**
* Gets all translators for a specific type of translation * Gets all translators for a specific type of translation
*
* @param {String} type The type of translators to get (import, export, web, or search) * @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) { this.getAllForType = function(type) {
return this.init().then(function() { return this.init().then(function () {
return _cache[type].slice(); return _cache[type].slice();
}); });
} }
@ -191,21 +218,16 @@ Zotero.Translators = new function() {
* Gets all translators for a specific type of translation * Gets all translators for a specific type of translation
*/ */
this.getAll = function() { this.getAll = function() {
return this.init().then(function() { return this.init().then(function () {
return [translator for each(translator in _translators)]; return Object.keys(_translators);
}); });
} }
/** /**
* Gets web translators for a specific location * Gets web translators for a specific location
* @param {String} uri The URI for which to look for translators * @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) { this.getWebTranslatorsForLocation = function(uri) {
return this.getAllForType("web").then(function(allTranslators) { return this.getAllForType("web").then(function(allTranslators) {
var potentialTranslators = []; var potentialTranslators = [];
@ -415,10 +437,10 @@ Zotero.Translators = new function() {
}); });
} }
this.cacheInDB = function(fileName, metadataJSON, code, lastModifiedTime) { this.cacheInDB = function(fileName, metadataJSON, lastModifiedTime) {
return Zotero.DB.queryAsync( return Zotero.DB.queryAsync(
"REPLACE INTO translatorCache VALUES (?, ?, ?, ?)", "REPLACE INTO translatorCache VALUES (?, ?, ?)",
[fileName, metadataJSON, code, lastModifiedTime] [fileName, JSON.stringify(metadataJSON), lastModifiedTime]
); );
} }
} }

View File

@ -485,7 +485,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
// Recreate database with no quick start guide // Recreate database with no quick start guide
Zotero.Schema.skipDefaultData = true; Zotero.Schema.skipDefaultData = true;
Zotero.Schema.updateSchema(); yield Zotero.Schema.updateSchema();
Zotero.restoreFromServer = true; Zotero.restoreFromServer = true;
} }
@ -577,7 +577,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.locked = false; Zotero.locked = false;
// Initialize various services // Initialize various services
Zotero.Styles.preinit();
Zotero.Integration.init(); Zotero.Integration.init();
if(Zotero.Prefs.get("httpServer.enabled")) { if(Zotero.Prefs.get("httpServer.enabled")) {
@ -605,8 +604,8 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.Searches.init(); Zotero.Searches.init();
Zotero.Groups.init(); Zotero.Groups.init();
// TODO: Delay until after UI is shown
yield Zotero.QuickCopy.init(); yield Zotero.QuickCopy.init();
Zotero.Items.startEmptyTrashTimer(); Zotero.Items.startEmptyTrashTimer();
} }
catch (e) { catch (e) {

View File

@ -411,8 +411,7 @@ CREATE INDEX customBaseFieldMappings_baseFieldID ON customBaseFieldMappings(base
CREATE INDEX customBaseFieldMappings_customFieldID ON customBaseFieldMappings(customFieldID); CREATE INDEX customBaseFieldMappings_customFieldID ON customBaseFieldMappings(customFieldID);
CREATE TABLE translatorCache ( CREATE TABLE translatorCache (
leafName TEXT PRIMARY KEY, fileName TEXT PRIMARY KEY,
translatorJSON TEXT, metadataJSON TEXT,
code TEXT, lastModifiedTime INT
lastModifiedTime INT
); );