www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 4c9b5935e8d0b731242ae058d194f00932c73b28
parent 55c331e68b1c44c7824694568a66017edb0b6284
Author: Simon Kornblith <simon@simonster.com>
Date:   Sat, 16 Jul 2011 20:47:17 +0000

- Improvements to server.js for translation-server
- Optimizations. The biggest of these is to simplify our mechanism of wrapping functions for Fx 4+, which gives us roughly a 3x speed boost in RIS import. However, zotero-node is still ~20% faster than translation-server, and RDF import/export may still be too slow for very large numbers of references. A large part of the RDF overhead seems to come from the number of function calls we make, which numbers in the hundreds of thousands for a 2.5 MB file.


Diffstat:
Mchrome/content/zotero/xpcom/server.js | 74++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mchrome/content/zotero/xpcom/translation/translate.js | 41++++++++++++++++++++++++++++++-----------
Mchrome/content/zotero/xpcom/translation/translate_firefox.js | 46++++++++++++++++++++++++++++++++++------------
Mchrome/content/zotero/xpcom/utilities.js | 52++++++++++++++++++++++++++++++----------------------
4 files changed, 144 insertions(+), 69 deletions(-)

diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js @@ -39,7 +39,7 @@ Zotero.Server = new function() { /** * initializes a very rudimentary web server */ - this.init = function() { + this.init = function(port, bindAllAddr, maxConcurrentConnections) { if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline -- not initializing HTTP server'); _registerOnlineObserver(); @@ -51,10 +51,10 @@ Zotero.Server = new function() { .createInstance(Components.interfaces.nsIServerSocket); try { // bind to a random port on loopback only - serv.init(Zotero.Prefs.get('httpServer.port'), true, -1); + serv.init(port ? port : Zotero.Prefs.get('httpServer.port'), !bindAllAddr, -1); serv.asyncListen(Zotero.Server.SocketListener); - Zotero.debug("HTTP server listening on 127.0.0.1:"+serv.port); + Zotero.debug("HTTP server listening on "+(bindAllAddr ? "*": " 127.0.0.1")+":"+serv.port); } catch(e) { Zotero.debug("Not initializing HTTP server"); } @@ -82,6 +82,20 @@ Zotero.Server = new function() { return response; } + /** + * Parses a query string into a key => value object + * @param {String} queryString Query string + */ + this.decodeQueryString = function(queryString) { + var splitData = queryString.split("&"); + var decodedData = {}; + for each(var variable in splitData) { + var splitIndex = variable.indexOf("="); + decodedData[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1)); + } + return decodedData; + } + function _registerOnlineObserver() { if (_onlineObserverRegistered) { return; @@ -218,7 +232,7 @@ Zotero.Server.DataListener.prototype.onDataAvailable = function(request, context Zotero.Server.DataListener.prototype._headerFinished = function() { this.headerFinished = true; - Zotero.debug(this.header); + Zotero.debug(this.header, 5); const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/; const contentTypeRe = /[\r\n]Content-Type: +([^ \r\n]+)/i; @@ -233,33 +247,35 @@ Zotero.Server.DataListener.prototype._headerFinished = function() { } if(!method) { - this._requestFinished(Zotero.Server.generateResponse(400)); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid method specified\n")); return; } if(!Zotero.Server.Endpoints[method[2]]) { - this._requestFinished(Zotero.Server.generateResponse(404)); + this._requestFinished(Zotero.Server.generateResponse(404, "text/plain", "No endpoint found\n")); return; } + this.pathname = method[2]; this.endpoint = Zotero.Server.Endpoints[method[2]]; + this.query = method[3]; if(method[1] == "HEAD" || method[1] == "OPTIONS") { this._requestFinished(Zotero.Server.generateResponse(200)); } else if(method[1] == "GET") { - this._requestFinished(this._processEndpoint("GET", method[3])); + this._processEndpoint("GET", null); } else if(method[1] == "POST") { const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i; // parse content length var m = contentLengthRe.exec(this.header); if(!m) { - this._requestFinished(Zotero.Server.generateResponse(400)); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Content-length not provided\n")); return; } this.bodyLength = parseInt(m[1]); this._bodyData(); } else { - this._requestFinished(Zotero.Server.generateResponse(501)); + this._requestFinished(Zotero.Server.generateResponse(501, "text/plain", "Method not implemented\n")); return; } } @@ -297,29 +313,30 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat var endpoint = new this.endpoint; // check that endpoint supports method - if(endpoint.supportedMethods.indexOf(method) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400)); + if(endpoint.supportedMethods && endpoint.supportedMethods.indexOf(method) === -1) { + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support method\n")); return; } var decodedData = null; if(postData && this.contentType) { // check that endpoint supports contentType - if(endpoint.supportedDataTypes.indexOf(this.contentType) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400)); + var supportedDataTypes = endpoint.supportedDataTypes; + if(supportedDataTypes && supportedDataTypes.indexOf(this.contentType) === -1) { + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support content-type\n")); return; } // decode JSON or urlencoded post data, and pass through anything else - if(this.contentType === "application/json") { - decodedData = JSON.parse(postData); - } else if(this.contentType === "application/x-www-urlencoded") { - var splitData = postData.split("&"); - decodedData = {}; - for each(var variable in splitData) { - var splitIndex = variable.indexOf("="); - data[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1)); + if(supportedDataTypes && this.contentType === "application/json") { + try { + decodedData = JSON.parse(postData); + } catch(e) { + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid JSON provided\n")); + return; } + } else if(supportedDataTypes && this.contentType === "application/x-www-urlencoded") { + decodedData = Zotero.Server.decodeQueryString(postData); } else { decodedData = postData; } @@ -332,10 +349,19 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat } // pass to endpoint - endpoint.init(decodedData, sendResponseCallback); + if((endpoint.init.length ? endpoint.init.length : endpoint.init.arity) === 3) { + var url = { + "pathname":this.pathname, + "query":this.query ? Zotero.Server.decodeQueryString(this.query.substr(1)) : {} + }; + + endpoint.init(url, decodedData, sendResponseCallback); + } else { + endpoint.init(decodedData, sendResponseCallback); + } } catch(e) { Zotero.debug(e); - this._requestFinished(Zotero.Server.generateResponse(500)); + this._requestFinished(Zotero.Server.generateResponse(500), "text/plain", "An error occurred\n"); throw e; } } @@ -356,7 +382,7 @@ Zotero.Server.DataListener.prototype._requestFinished = function(response) { intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0)); // write response - Zotero.debug(response); + Zotero.debug(response, 5); intlStream.writeString(response); } finally { intlStream.close(); diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js @@ -81,7 +81,7 @@ Zotero.Translate.Sandbox = { * @param {SandboxItem} An item created using the Zotero.Item class from the sandbox */ "_itemDone":function(translate, item) { - Zotero.debug("Translate: Saving item"); + //Zotero.debug("Translate: Saving item"); // warn if itemDone called after translation completed if(translate._complete) { @@ -1859,7 +1859,7 @@ Zotero.Translate.IO = { } if(nodes.getElementsByTagName("parsererror").length) { - throw new Error("DOMParser error: loading data into data store failed"); + throw "DOMParser error: loading data into data store failed"; } return nodes; @@ -1901,8 +1901,14 @@ Zotero.Translate.IO.String.prototype = { this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); if(this._string.length) { + try { + var xml = Zotero.Translate.IO.parseDOMXML(this._string); + } catch(e) { + this._xmlInvalid = true; + throw e; + } var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); - parser.parse(Zotero.Translate.IO.parseDOMXML(this._string), this._uri); + parser.parse(xml, this._uri); callback(true); } }, @@ -1931,19 +1937,23 @@ Zotero.Translate.IO.String.prototype = { var oldPointer = this._stringPointer; var lfIndex = this._string.indexOf("\n", this._stringPointer); - if(lfIndex != -1) { + if(lfIndex !== -1) { // in case we have a CRLF this._stringPointer = lfIndex+1; - if(this._string.length > lfIndex && this._string[lfIndex-1] == "\r") { + if(this._string.length > lfIndex && this._string[lfIndex-1] === "\r") { lfIndex--; } return this._string.substr(oldPointer, lfIndex-oldPointer); } - var crIndex = this._string.indexOf("\r", this._stringPointer); - if(crIndex != -1) { - this._stringPointer = crIndex+1; - return this._string.substr(oldPointer, crIndex-oldPointer-1); + if(!this._noCR) { + var crIndex = this._string.indexOf("\r", this._stringPointer); + if(crIndex === -1) { + this._noCR = true; + } else { + this._stringPointer = crIndex+1; + return this._string.substr(oldPointer, crIndex-oldPointer-1); + } } this._stringPointer = this._string.length; @@ -1957,7 +1967,12 @@ Zotero.Translate.IO.String.prototype = { "_getXML":function() { if(this._mode == "xml/dom") { - return Zotero.Translate.IO.parseDOMXML(this._string); + try { + return Zotero.Translate.IO.parseDOMXML(this._string); + } catch(e) { + this._xmlInvalid = true; + throw e; + } } else { return this._string.replace(/<\?xml[^>]+\?>/, ""); } @@ -1965,9 +1980,13 @@ Zotero.Translate.IO.String.prototype = { "init":function(newMode, callback) { this._stringPointer = 0; + this._noCR = undefined; this._mode = newMode; - if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { + if(newMode && (Zotero.Translate.IO.rdfDataModes.indexOf(newMode) !== -1 + || newMode.substr(0, 3) === "xml") && this._xmlInvalid) { + throw "XML known invalid"; + } else if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { this._initRDF(callback); } else { callback(true); diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js @@ -148,22 +148,44 @@ Zotero.Translate.SandboxManager.prototype = { if(newExposedProps) newExposedProps[localKey] = "r"; // magical XPCSafeJSObjectWrappers for sandbox - if(typeof object[localKey] === "function" || typeof object[localKey] === "object") { - if(attachTo == this.sandbox) Zotero.debug(localKey); - attachTo[localKey] = function() { - var args = (passAsFirstArgument ? [passAsFirstArgument] : []); - for(var i=0; i<arguments.length; i++) { - args.push((typeof arguments[i] === "object" && arguments[i] !== null) - || typeof arguments[i] === "function" - ? new XPCSafeJSObjectWrapper(arguments[i]) : arguments[i]); + var type = typeof object[localKey]; + var isFunction = type === "function"; + var isObject = typeof object[localKey] === "object"; + if(isFunction || isObject) { + if(isFunction) { + if(Zotero.isFx4) { + if(passAsFirstArgument) { + attachTo[localKey] = object[localKey].bind(object, passAsFirstArgument); + } else { + attachTo[localKey] = object[localKey].bind(object); + } + } else { + attachTo[localKey] = function() { + if(passAsFirstArgument) { + var args = new Array(arguments.length+1); + args[0] = passAsFirstArgument; + var offset = 1; + } else { + var args = new Array(arguments.length); + var offset = 0; + } + + for(var i=0, nArgs=arguments.length; i<nArgs; i++) { + args[i+offset] = (((typeof arguments[i] === "object" && arguments[i] !== null) + || typeof arguments[i] === "function") + ? new XPCSafeJSObjectWrapper(arguments[i]) : arguments[i]); + } + + return object[localKey].apply(object, args); + }; } - - return object[localKey].apply(object, args); - }; + } else { + attachTo[localKey] = {}; + } // attach members if(!(object instanceof Components.interfaces.nsISupports)) { - this.importObject(object[localKey], passAsFirstArgument ? passAsFirstArgument : null, attachTo[localKey]); + this.importObject(object[localKey], passAsFirstArgument, attachTo[localKey]); } } else { attachTo[localKey] = object[localKey]; diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js @@ -218,12 +218,17 @@ Zotero.Utilities = { * @type String */ "unescapeHTML":function(/**String*/ str) { + // If no tags, no need to unescape + if(str.indexOf("<") === -1 && str.indexOf("&") === -1) return str; + if(Zotero.isFx) { - var nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"] - .getService(Components.interfaces.nsIScriptableUnescapeHTML); - return nsISUHTML.unescape(str); + if(!Zotero.Utilities._nsISUHTML) { + Zotero.Utilities._nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"] + .getService(Components.interfaces.nsIScriptableUnescapeHTML); + } + return Zotero.Utilities._nsISUHTML.unescape(str); } else if(Zotero.isNode) { - var doc = require('jsdom').jsdom(str, null, { + /*var doc = require('jsdom').jsdom(str, null, { "features":{ "FetchExternalResources":false, "ProcessExternalResources":false, @@ -232,7 +237,8 @@ Zotero.Utilities = { } }); if(!doc.documentElement) return str; - return doc.documentElement.textContent; + return doc.documentElement.textContent;*/ + return Zotero.Utilities.cleanTags(str); } else { var node = document.createElement("div"); node.innerHTML = str; @@ -847,7 +853,6 @@ Zotero.Utilities = { * Converts an item from toArray() format to content=json format used by the server */ "itemToServerJSON":function(item) { - const IGNORE_FIELDS = ["seeAlso", "attachments", "complete"]; var newItem = {}; var typeID = Zotero.ItemTypes.getID(item.itemType); @@ -857,9 +862,10 @@ Zotero.Utilities = { typeID = Zotero.ItemTypes.getID(item.itemType); } - var fieldID; + var fieldID, itemFieldID; for(var field in item) { - if(IGNORE_FIELDS.indexOf(field) !== -1) continue; + if(field === "complete" || field === "itemID" || field === "attachments" + || field === "seeAlso") continue; var val = item[field]; @@ -867,8 +873,9 @@ Zotero.Utilities = { newItem[field] = val; } else if(field === "creators") { // normalize creators - var newCreators = newItem.creators = []; - for(var j in val) { + var n = val.length; + var newCreators = newItem.creators = new Array(n); + for(var j=0; j<n; j++) { var creator = val[j]; // Single-field mode @@ -886,7 +893,6 @@ Zotero.Utilities = { } // ensure creatorType is present and valid - newCreator.creatorType = "author"; if(creator.creatorType) { if(Zotero.CreatorTypes.getID(creator.creatorType)) { newCreator.creatorType = creator.creatorType; @@ -894,13 +900,15 @@ Zotero.Utilities = { Zotero.debug("Translate: Invalid creator type "+creator.creatorType+"; falling back to author"); } } + if(!newCreator.creatorType) newCreator.creatorType = "author"; - newCreators.push(newCreator); + newCreators[j] = newCreator; } } else if(field === "tags") { // normalize tags - var newTags = newItem.tags = []; - for(var j in val) { + var n = val.length; + var newTags = newItem.tags = new Array(n); + for(var j=0; j<n; j++) { var tag = val[j]; if(typeof tag === "object") { if(tag.tag) { @@ -912,12 +920,13 @@ Zotero.Utilities = { continue; } } - newTags.push({"tag":tag.toString(), "type":1}) + newTags[j] = {"tag":tag.toString(), "type":1}; } } else if(field === "notes") { // normalize notes - var newNotes = newItem.notes = []; - for(var j in val) { + var n = val.length; + var newNotes = newItem.notes = new Array(n); + for(var j=0; j<n; j++) { var note = val[j]; if(typeof note === "object") { if(!note.note) { @@ -926,9 +935,9 @@ Zotero.Utilities = { } note = note.note; } - newNotes.push({"itemType":"note", "note":note.toString()}); + newNotes[j] = {"itemType":"note", "note":note.toString()}; } - } else if(fieldID = Zotero.ItemFields.getID(field)) { + } 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) { @@ -939,8 +948,7 @@ Zotero.Utilities = { } // map from base field if possible - var itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID); - if(itemFieldID) { + if((itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID))) { newItem[Zotero.ItemFields.getName(itemFieldID)] = val; continue; // already know this is valid } @@ -951,7 +959,7 @@ Zotero.Utilities = { } else { Zotero.debug("Translate: Discarded field "+field+": field not valid for type "+item.itemType, 3); } - } else if(field !== "complete") { + } else { Zotero.debug("Translate: Discarded unknown field "+field, 3); } }