From 97ed9ccbfe91e7af593646b0c9a0009e73e5f968 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Fri, 10 Feb 2012 16:10:10 -0500 Subject: [PATCH 1/5] Serialize RDF to string on export --- chrome/content/zotero/xpcom/translation/translate.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 4db52ca9dc..6eb6825478 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -1552,7 +1552,7 @@ Zotero.Translate.Import.prototype.setString = function(string) { Zotero.Translate.Import.prototype.complete = function(returnValue, error) { if(this._io) { this._progress = null; - this._io.close(); + this._io.close(false); } // call super @@ -1732,7 +1732,7 @@ Zotero.Translate.Export.prototype.setDisplayOptions = function(displayOptions) { Zotero.Translate.Export.prototype.complete = function(returnValue, error) { if(this._io) { this._progress = null; - this._io.close(); + this._io.close(true); if(this._io instanceof Zotero.Translate.IO.String) { this.string = this._io.string; } @@ -2068,7 +2068,13 @@ Zotero.Translate.IO.String.prototype = { } }, - "close":function() {} + "close":function(serialize) { + // if we are writing in RDF data mode and no string is set, serialize current RDF to the + // string + if(serialize && Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && this.string === "") { + this.string = this.RDF.serialize(); + } + } } /****** RDF DATA MODE ******/ From 6a4bcc683e1b1231f36786d497eda615ab430380 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Feb 2012 18:25:21 -0500 Subject: [PATCH 2/5] Update repotime --- repotime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repotime.txt b/repotime.txt index e54aade7fc..1a2ff2586d 100644 --- a/repotime.txt +++ b/repotime.txt @@ -1 +1 @@ -2012-01-31 04:00:00 +2012-02-10 23:24:58 From 56195558667e0dc9a6dda85f7bf2213b58e45162 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Fri, 10 Feb 2012 19:10:43 -0500 Subject: [PATCH 3/5] Improve our chances of getting attachments from the connector. We attach cookies sent from the connector to attachment downloads, which should get most proxies working, provided that the user is logged in. We also use WeakMaps to keep track of active CookieSandboxes, rather than manually removing them from a list. --- chrome/content/zotero/xpcom/attachments.js | 6 +- .../zotero/xpcom/connector/translate_item.js | 19 ++- chrome/content/zotero/xpcom/cookieSandbox.js | 130 ++++++------------ chrome/content/zotero/xpcom/http.js | 8 +- chrome/content/zotero/xpcom/mime.js | 4 +- .../content/zotero/xpcom/server_connector.js | 10 +- .../zotero/xpcom/translation/translate.js | 3 +- .../xpcom/translation/translate_item.js | 10 +- translators | 2 +- 9 files changed, 81 insertions(+), 111 deletions(-) diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 0b0796cc2a..a7ab632128 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -200,7 +200,7 @@ Zotero.Attachments = new function(){ function importFromURL(url, sourceItemID, forceTitle, forceFileBaseName, parentCollectionIDs, - mimeType, libraryID, callback) { + mimeType, libraryID, callback, cookieSandbox) { Zotero.debug('Importing attachment from URL'); if (sourceItemID && parentCollectionIDs) { @@ -222,6 +222,7 @@ Zotero.Attachments = new function(){ // Save using a hidden browser var nativeHandlerImport = function () { var browser = Zotero.Browser.createHiddenBrowser(); + if(cookieSandbox) cookieSandbox.attachToBrowser(browser); var imported = false; var onpageshow = function() { // ignore spurious about:blank loads @@ -263,6 +264,7 @@ Zotero.Attachments = new function(){ .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(nsIWBP); wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(wbp); var encodingFlags = false; Zotero.DB.beginTransaction(); @@ -396,7 +398,7 @@ Zotero.Attachments = new function(){ else { Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) { process(mimeType, hasNativeHandler); - }); + }, cookieSandbox); } } diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js index febac63e29..e24c65caa4 100644 --- a/chrome/content/zotero/xpcom/connector/translate_item.js +++ b/chrome/content/zotero/xpcom/connector/translate_item.js @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright © 2009 Center for History and New Media + Copyright © 2012 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://zotero.org @@ -23,10 +23,15 @@ ***** END LICENSE BLOCK ***** */ -Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { +Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document, + cookieSandbox) { this.newItems = []; - this._timeoutID = null; + + if(document) { + this._uri = document.location.toString(); + this._cookie = document.cookie; + } } Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; @@ -40,7 +45,13 @@ Zotero.Translate.ItemSaver.prototype = { "saveItems":function(items, callback) { var me = this; // first try to save items via connector - Zotero.Connector.callMethod("saveItems", {"items":items}, function(success, status) { + var payload = {"items":items}; + if(this._uri && this._cookie) { + payload.uri = this._uri; + payload.cookie = this._cookie; + } + + Zotero.Connector.callMethod("saveItems", payload, function(success, status) { if(success !== false) { Zotero.debug("Translate: Save via Standalone succeeded"); callback(true, items); diff --git a/chrome/content/zotero/xpcom/cookieSandbox.js b/chrome/content/zotero/xpcom/cookieSandbox.js index 6682ff93c2..2f68f1853c 100755 --- a/chrome/content/zotero/xpcom/cookieSandbox.js +++ b/chrome/content/zotero/xpcom/cookieSandbox.js @@ -27,16 +27,12 @@ * Manage cookies in a sandboxed fashion * * @constructor - * @param {browser} browser Hidden browser object + * @param {browser} [browser] Hidden browser object * @param {String|nsIURI} uri URI of page to manage cookies for (cookies for domains that are not * subdomains of this URI are ignored) * @param {String} cookieData Cookies with which to initiate the sandbox */ Zotero.CookieSandbox = function(browser, uri, cookieData) { - this._webNav = browser.webNavigation; - this._browser = browser; - this._watchedBrowsers = [browser]; - this._watchedXHRs = []; this._observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); @@ -58,30 +54,13 @@ Zotero.CookieSandbox = function(browser, uri, cookieData) { } } - // register with observer - Zotero.CookieSandbox.Observer.register(this); + if(browser) { + this.attachToBrowser(browser); + } + Zotero.CookieSandbox.Observer.register(); } Zotero.CookieSandbox.prototype = { - /** - * Check whether we track a browser for this document - */ - "isDocumentTracked":function(doc) { - var i = this._watchedBrowsers.length; - while(i--) { - var browser = this._watchedBrowsers[i]; - if(doc == browser.contentDocument) return true; - } - return false; - }, - - /** - * Check whether we track an XHR for this document - */ - "isXHRTracked":function(xhr) { - return this._watchedXHRs.indexOf(xhr) !== -1; - }, - /** * Adds cookies to this CookieSandbox based on a cookie header * @param {String} cookieString; @@ -108,27 +87,19 @@ Zotero.CookieSandbox.prototype = { }, /** - * Attach CookieSandbox to a specific XMLHttpRequest - * @param {XMLHttpRequest} xhr + * Attach CookieSandbox to a specific browser + * @param {Browser} browser */ "attachToBrowser":function(browser) { - this._watchedBrowsers.push(browser); + Zotero.CookieSandbox.Observer.trackedBrowsers.set(browser, this); }, /** * Attach CookieSandbox to a specific XMLHttpRequest - * @param {XMLHttpRequest} xhr + * @param {nsIInterfaceRequestor} ir */ - "attachToXHR": function(xhr) { - this._watchedXHRs.push(xhr); - }, - - /** - * Destroys this CookieSandbox (intended to be executed when the browser is destroyed) - */ - "destroy": function() { - // unregister with observer - Zotero.CookieSandbox.Observer.unregister(this); + "attachToInterfaceRequestor": function(ir) { + Zotero.CookieSandbox.Observer.trackedInterfaceRequestors.set(ir.QueryInterface(Components.interfaces.nsIInterfaceRequestor), this); } } @@ -144,36 +115,21 @@ Zotero.CookieSandbox.Observer = new function() { var observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService), - observing = false, - cookieSandboxes = []; + observing = false; + + this.trackedBrowsers = new WeakMap(); + this.trackedInterfaceRequestors = new WeakMap(); /** * Registers cookie manager and observer, if necessary */ this.register = function(CookieSandbox) { - cookieSandboxes.push(CookieSandbox); - if(!observing) { Zotero.debug("CookieSandbox: Registering observers"); for each(var topic in observeredTopics) observerService.addObserver(this, topic, false); observing = true; } - } - - /** - * Unregisters cookie manager and observer - */ - this.unregister = function(CookieSandbox) { - // remove cookie manager from list - cookieSandboxes.splice(cookieSandboxes.indexOf(CookieSandbox), 1); - - // remove observer if this is the last and this is not translation-server - if(cookieSandboxes.length === 0 && !Zotero.isServer) { - Zotero.debug("CookieSandbox: Unregistering observers"); - for each(var topic in observeredTopics) observerService.removeObserver(this, topic); - observing = false; - } - } + }; /** * Implements nsIObserver to watch for new cookies and to add sandboxed cookies @@ -185,50 +141,48 @@ Zotero.CookieSandbox.Observer = new function() { } channel.QueryInterface(Components.interfaces.nsIHttpChannel); - var trackedBy, tested, doc, xhr, + var trackedBy, tested, browser, callbacks, channelURI = channel.URI.spec, notificationCallbacks = channel.notificationCallbacks; - // try the document - try { - doc = notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document; - } catch(e) {} - if(doc) { + // try the notification callbacks + trackedBy = this.trackedInterfaceRequestors.get(notificationCallbacks); + if(trackedBy) { tested = true; - for(var i=0, n=cookieSandboxes.length; i we should manage cookies for this request // tested && !trackedBy => we should not manage cookies for this request // !tested && !trackedBy => this request is of a type we couldn't match to this request. @@ -256,7 +210,6 @@ Zotero.CookieSandbox.Observer = new function() { } // add cookies to be sent to this domain - Zotero.debug(trackedBy.cookieString); channel.setRequestHeader("Cookie", trackedBy.cookieString, false); Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5); } else if(topic == "http-on-examine-response") { @@ -276,7 +229,6 @@ Zotero.CookieSandbox.Observer = new function() { } // put new cookies into our sandbox - Zotero.debug(cookieHeader); if(cookieHeader) trackedBy.addCookiesFromHeader(cookieHeader); Zotero.debug("CookieSandbox: Slurped cookies from "+channelURI, 5); diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 59e84aa5e6..67e7cccfb0 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -52,7 +52,7 @@ Zotero.HTTP = new function() { _stateChange(xmlhttp, onDone, responseCharset); }; - if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp); + if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp); xmlhttp.send(null); return xmlhttp; @@ -151,7 +151,7 @@ Zotero.HTTP = new function() { _stateChange(xmlhttp, onDone, responseCharset); }; - if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp); + if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp); xmlhttp.send(body); return xmlhttp; @@ -163,9 +163,10 @@ Zotero.HTTP = new function() { * @param {String} url URL to request * @param {Function} onDone Callback to be executed upon request completion * @param {Object} requestHeaders HTTP headers to include with request + * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object * @return {Boolean} True if the request was sent, or false if the browser is offline */ - this.doHead = function(url, onDone, requestHeaders) { + this.doHead = function(url, onDone, requestHeaders, cookieSandbox) { if (url instanceof Components.interfaces.nsIURI) { // Don't display password in console var disp = url.clone(); @@ -229,6 +230,7 @@ Zotero.HTTP = new function() { _stateChange(xmlhttp, onDone); }; + if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp); xmlhttp.send(null); return xmlhttp; diff --git a/chrome/content/zotero/xpcom/mime.js b/chrome/content/zotero/xpcom/mime.js index f499c377be..87866fe09e 100644 --- a/chrome/content/zotero/xpcom/mime.js +++ b/chrome/content/zotero/xpcom/mime.js @@ -280,7 +280,7 @@ Zotero.MIME = new function(){ } - this.getMIMETypeFromURL = function (url, callback) { + this.getMIMETypeFromURL = function (url, callback, cookieSandbox) { Zotero.HTTP.doHead(url, function(xmlhttp) { if (xmlhttp.status != 200 && xmlhttp.status != 204) { Zotero.debug("Attachment HEAD request returned with status code " @@ -308,7 +308,7 @@ Zotero.MIME = new function(){ var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext) callback(mimeType, hasNativeHandler); - }); + }, undefined, cookieSandbox); } diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js index 7caa1b617c..d259249d0d 100755 --- a/chrome/content/zotero/xpcom/server_connector.js +++ b/chrome/content/zotero/xpcom/server_connector.js @@ -156,7 +156,6 @@ Zotero.Server.Connector.Detect.prototype = { } this.sendResponse(200, "application/json", JSON.stringify(jsons)); - this._translate.cookieSandbox.destroy(); Zotero.Browser.deleteHiddenBrowser(this._browser); } } @@ -225,7 +224,6 @@ Zotero.Server.Connector.SavePage.prototype = { "_translatorsAvailable":function(translate, translators) { // make sure translatorsAvailable succeded if(!translators.length) { - me._translate.cookieSandbox.destroy(); Zotero.Browser.deleteHiddenBrowser(this._browser); this.sendResponse(500); return; @@ -251,7 +249,6 @@ Zotero.Server.Connector.SavePage.prototype = { jsonItems.push(jsonItem); }); translate.setHandler("done", function(obj, item) { - me._translate.cookieSandbox.destroy(); Zotero.Browser.deleteHiddenBrowser(me._browser); if(jsonItems.length || me.selectedItems === false) { me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems})); @@ -296,9 +293,12 @@ Zotero.Server.Connector.SaveItem.prototype = { var collection = zp.getSelectedCollection(); } catch(e) {} + var cookieSandbox = data["uri"] && data["cookie"] ? new Zotero.CookieSandbox(null, data["uri"], + data["cookie"]) : null; + // save items var itemSaver = new Zotero.Translate.ItemSaver(libraryID, - Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1); + Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1, undefined, cookieSandbox); itemSaver.saveItems(data.items, function(returnValue, data) { if(returnValue) { try { @@ -390,8 +390,6 @@ Zotero.Server.Connector.SaveSnapshot.prototype = { // remove browser Zotero.Browser.deleteHiddenBrowser(browser); - // destroy cookieSandbox - cookieSandbox.destroy(); sendResponseCallback(201); } catch(e) { sendResponseCallback(500); diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 6eb6825478..f2e37a3f67 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -1437,7 +1437,8 @@ Zotero.Translate.Web.prototype._getParameters = function() { return [this.docume */ Zotero.Translate.Web.prototype._prepareTranslation = function() { this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID, - Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1); + Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1, + this.document, this._cookieSandbox); this.newItems = []; } diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js index 2def6c55d2..daf0b5a120 100644 --- a/chrome/content/zotero/xpcom/translation/translate_item.js +++ b/chrome/content/zotero/xpcom/translation/translate_item.js @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright © 2009 Center for History and New Media + Copyright © 2012 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://zotero.org @@ -23,7 +23,8 @@ ***** END LICENSE BLOCK ***** */ -Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { +Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document, + cookieSandbox) { // initialize constants this.newItems = []; this.newCollections = []; @@ -65,6 +66,7 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { // force tag types if requested this._forceTagType = forceTagType; + this._cookieSandbox = cookieSandbox; }; Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; @@ -346,7 +348,9 @@ Zotero.Translate.ItemSaver.prototype = { var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID); try { - Zotero.Attachments.importFromURL(attachment.url, parentID, title, fileBaseName); + Zotero.debug('Importing attachment from URL'); + Zotero.Attachments.importFromURL(attachment.url, parentID, title, + fileBaseName, null, mimeType, this._libraryID, null, this._cookieSandbox); } catch(e) { Zotero.debug("Translate: Error adding attachment "+attachment.url, 2); } diff --git a/translators b/translators index 3daf3bdf2c..94bcbbcdc6 160000 --- a/translators +++ b/translators @@ -1 +1 @@ -Subproject commit 3daf3bdf2ce13d7e1a329f5648f18937d8251dee +Subproject commit 94bcbbcdc6c4db94391db64dd4b7270e12401e13 From d5fc04485b3580bcbbb7a2f29a755749ef914d6b Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Sat, 11 Feb 2012 00:34:05 -0500 Subject: [PATCH 4/5] Remove antiquated code --- .../zotero/xpcom/collectionTreeView.js | 4 +- chrome/content/zotero/xpcom/dataServer.js | 297 -------------- chrome/content/zotero/xpcom/zeroconf.js | 377 ------------------ chrome/content/zotero/xpcom/zotero.js | 2 - components/zotero-service.js | 1 - scripts/zoteroconf.sh | 42 -- 6 files changed, 2 insertions(+), 721 deletions(-) delete mode 100644 chrome/content/zotero/xpcom/dataServer.js delete mode 100644 chrome/content/zotero/xpcom/zeroconf.js delete mode 100755 scripts/zoteroconf.sh diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 66977ff85a..6a5a09c9bb 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -182,13 +182,13 @@ Zotero.CollectionTreeView.prototype.refresh = function() } } - var shares = Zotero.Zeroconf.instances; + /*var shares = Zotero.Zeroconf.instances; if (shares.length) { this._showRow(new Zotero.ItemGroup('separator', false)); for each(var share in shares) { this._showRow(new Zotero.ItemGroup('share', share)); } - } + }*/ if (this.hideSources.indexOf('commons') == -1 && Zotero.Commons.enabled) { this._showRow(new Zotero.ItemGroup('separator', false)); diff --git a/chrome/content/zotero/xpcom/dataServer.js b/chrome/content/zotero/xpcom/dataServer.js deleted file mode 100644 index 6f8e1033fd..0000000000 --- a/chrome/content/zotero/xpcom/dataServer.js +++ /dev/null @@ -1,297 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - - -Zotero.DataServer = new function () { - this.init = init; - this.handleHeader = handleHeader; - - // TODO: assign dynamically - this.__defineGetter__('port', function () { - return 22030; - }); - - var _onlineObserverRegistered; - - - /* - * initializes a very rudimentary web server used for SOAP RPC - */ - function init() { - // Use Zeroconf pref for now - if (!Zotero.Prefs.get("zeroconf.server.enabled")) { - Zotero.debug("Not initializing data HTTP server"); - return; - } - - if (Zotero.HTTP.browserIsOffline()) { - Zotero.debug('Browser is offline -- not initializing data HTTP server'); - _registerOnlineObserver() - return; - } - - // start listening on socket - var serv = Components.classes["@mozilla.org/network/server-socket;1"] - .createInstance(Components.interfaces.nsIServerSocket); - try { - serv.init(this.port, false, -1); - serv.asyncListen(Zotero.DataServer.SocketListener); - - Zotero.debug("Data HTTP server listening on 127.0.0.1:" + serv.port); - } - catch(e) { - Zotero.debug("Not initializing data HTTP server"); - } - - _registerOnlineObserver() - } - - /* - * handles an HTTP request - */ - function handleHeader(header) { - // get first line of request (all we care about for now) - var method = header.substr(0, header.indexOf(" ")); - - if (!method) { - return _generateResponse("400 Bad Request"); - } - - if (method != "POST") { - return _generateResponse("501 Method Not Implemented"); - } - - // Parse request URI - var matches = header.match("^[A-Z]+ (\/.*) HTTP/1.[01]"); - if (!matches) { - return _generateResponse("400 Bad Request"); - } - - var response = _handleRequest(matches[1]); - - // return OK - return _generateResponse("200 OK", 'text/xml; charset="UTF-8"', response); - } - - - function _handleRequest(uri) { - var s = new Zotero.Search(); - s.addCondition('noChildren', 'true'); - var ids = s.search(); - - if (!ids) { - ids = []; - } - - var uploadIDs = { - updated: { - items: ids - }, - /* TODO: fix buildUploadXML to ignore missing */ - deleted: {} - }; - return Zotero.Sync.Server.Data.buildUploadXML(uploadIDs); - } - - - /* - * generates the response to an HTTP request - */ - function _generateResponse(status, contentType, body) { - var response = "HTTP/1.0 "+status+"\r\n"; - - if(body) { - if(contentType) { - response += "Content-Type: "+contentType+"\r\n"; - } - response += "\r\n"+body; - } else { - response += "Content-Length: 0\r\n\r\n" - } - - return response; - } - - - function _registerOnlineObserver() { - if (_onlineObserverRegistered) { - return; - } - - // Observer to enable the integration when we go online - var observer = { - observe: function(subject, topic, data) { - if (data == 'online') { - Zotero.Integration.init(); - } - } - }; - - var observerService = - Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.addObserver(observer, "network:offline-status-changed", false); - - _onlineObserverRegistered = true; - } -} - - -Zotero.DataServer.SocketListener = new function() { - this.onSocketAccepted = onSocketAccepted; - - /* - * called when a socket is opened - */ - function onSocketAccepted(socket, transport) { - // get an input stream - var iStream = transport.openInputStream(0, 0, 0); - var oStream = transport.openOutputStream(0, 0, 0); - - var dataListener = new Zotero.DataServer.DataListener(iStream, oStream); - var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"] - .createInstance(Components.interfaces.nsIInputStreamPump); - pump.init(iStream, -1, -1, 0, 0, false); - pump.asyncRead(dataListener, null); - } -} - -/* - * handles the actual acquisition of data - */ -Zotero.DataServer.DataListener = function(iStream, oStream) { - this.header = ""; - this.headerFinished = false; - - this.body = ""; - this.bodyLength = 0; - - this.iStream = iStream; - this.oStream = oStream; - this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"] - .createInstance(Components.interfaces.nsIScriptableInputStream); - this.sStream.init(iStream); - - this.foundReturn = false; -} - -/* - * called when a request begins (although the request should have begun before - * the DataListener was generated) - */ -Zotero.DataServer.DataListener.prototype.onStartRequest = function(request, context) {} - -/* - * called when a request stops - */ -Zotero.DataServer.DataListener.prototype.onStopRequest = function(request, context, status) { - this.iStream.close(); - this.oStream.close(); -} - -/* - * called when new data is available - */ -Zotero.DataServer.DataListener.prototype.onDataAvailable = function(request, context, - inputStream, offset, count) { - var readData = this.sStream.read(count); - - // Read header - if (!this.headerFinished) { - // see if there's a magic double return - var lineBreakIndex = readData.indexOf("\r\n\r\n"); - if (lineBreakIndex != -1) { - if (lineBreakIndex != 0) { - this.header += readData.substr(0, lineBreakIndex+4); - } - - this._headerFinished(); - return; - } - - var lineBreakIndex = readData.indexOf("\n\n"); - if (lineBreakIndex != -1) { - if (lineBreakIndex != 0) { - this.header += readData.substr(0, lineBreakIndex+2); - } - - this._headerFinished(); - return; - } - - if (this.header && this.header[this.header.length-1] == "\n" && - (readData[0] == "\n" || readData[0] == "\r")) { - if (readData.length > 1 && readData[1] == "\n") { - this.header += readData.substr(0, 2); - } - else { - this.header += readData[0]; - } - - this._headerFinished(); - return; - } - - this.header += readData; - } -} - -/* - * processes an HTTP header and decides what to do - */ -Zotero.DataServer.DataListener.prototype._headerFinished = function() { - this.headerFinished = true; - var output = Zotero.DataServer.handleHeader(this.header); - this._requestFinished(output); -} - -/* - * returns HTTP data from a request - */ -Zotero.DataServer.DataListener.prototype._requestFinished = function(response) { - // close input stream - this.iStream.close(); - - // open UTF-8 converter for output stream - var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"] - .createInstance(Components.interfaces.nsIConverterOutputStream); - - // write - try { - intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0)); - - Zotero.debug('Writing response to stream:\n\n' + response); - - // write response - intlStream.writeString(response); - } catch(e) { - Zotero.debug("An error occurred."); - Zotero.debug(e); - } finally { - Zotero.debug('Closing stream'); - intlStream.close(); - } -} - diff --git a/chrome/content/zotero/xpcom/zeroconf.js b/chrome/content/zotero/xpcom/zeroconf.js deleted file mode 100644 index 9f837c911e..0000000000 --- a/chrome/content/zotero/xpcom/zeroconf.js +++ /dev/null @@ -1,377 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - - -Zotero.Zeroconf = new function () { - this.init = init; - this.registerService = registerService; - this.findInstances = findInstances; - this.findInstancesCallback = findInstancesCallback; - this.unregisterService = unregisterService; - this.getScript = getScript; - - this.clientEnabled = true; - this.serverEnabled = true; - - this.__defineGetter__('clientPath', function () { - return '/usr/bin/dns-sd'; - }); - - this.__defineGetter__('displayName', function () { - var dnsService = Components.classes["@mozilla.org/network/dns-service;1"]. - getService(Components.interfaces.nsIDNSService); - var hostname = dnsService.myHostName; - - return hostname; - }); - - this.__defineGetter__('port', function () { - return Zotero.DataServer.port; - }); - - this.__defineGetter__('instances', function () { - var instances = {}; - for (var instance in _instances) { - instances[instance] = new Zotero.Zeroconf.RemoteLibrary(instance); - } - return instances; - }); - - var _instances = []; - var _browseCacheFile = '/tmp/zoteroconf_instances'; - var scriptsLoaded = false; - - function init() { - if (!Zotero.Prefs.get("zeroconf.server.enabled")) { - this.clientEnabled = false; - this.serverEnabled = false; - } - - // OS X only, for now - if (!Zotero.isMac) { - this.clientEnabled = false; - this.serverEnabled = false; - - // TODO: Why is Windows breaking without this? - return; - } - - // Make sure we have the client executable - var file = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - file.initWithPath(this.clientPath); - - if (!file.exists()) { - Zotero.debug('Not enabling Z(ot)eroconf -- executable not found'); - this.clientEnabled = false; - this.serverEnabled = false; - return; - } - - if (!this.serverEnabled) { - Zotero.debug('Not enabling Z(ot)eroconf'); - return; - } - - var registered = this.registerService(); - if (!registered) { - return; - } - - var observerService = Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.addObserver({ - observe: function(subject, topic, data) { - Zotero.Zeroconf.unregisterService(); - } - }, "quit-application", false); - } - - - function registerService() { - var file = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - file.initWithPath(this.clientPath); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - - var args = ["-R", this.displayName, "_zotero._tcp", "local.", this.port]; - - Zotero.debug("Registering Z(ot)eroconf on port " + this.port); - process.run(false, args, args.length); - - return true; - } - - - function findInstances(callback) { - if (!this.clientEnabled) { - return; - } - - Zotero.debug("Browsing for Z(ot)eroconf instances"); - var file = this.getScript('find_instances'); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - var args = ['find_instances']; - process.run(false, args, args.length); - - // Wait half a second for browse before proceeding - setTimeout(function () { - Zotero.Zeroconf.findInstancesCallback(callback); - }, 500); - } - - - function findInstancesCallback(callback) { - var file = Zotero.Zeroconf.getScript('kill_find_instances'); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - var args = ['kill_find_instances']; - process.run(false, args, args.length); - - var file = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - file.initWithPath(_browseCacheFile); - - if (!file.exists()) { - Zotero.debug(_browseCacheFile + " doesn't exist", 2); - _instances = {}; - return; - } - - var browseCache = Zotero.File.getContents(file); - Zotero.debug(browseCache); - file.remove(null); - - // Parse browse output - var lines = browseCache.split(/\n/); - var newInstances = {}; - for each(var line in lines) { - var matches = line.match(/([a-zA-Z\.]+) +_zotero\._tcp\. +(.+)/); - if (matches) { - var domain = matches[1]; - var name = matches[2]; - // Skip local host - if (name == this.displayName) { - continue; - } - newInstances[name] = true; - } - } - - // Remove expired instances - for (var instance in _instances) { - if (!newInstances[instance]) { - delete _instances[instance]; - } - } - - // Add new instances - for (var instance in newInstances) { - _instances[instance] = true; - } - - Zotero.Notifier.trigger('refresh', 'share', 'all'); - - if (callback) { - callback(); - } - } - - - function unregisterService() { - Zotero.debug("Unregistering Zeroconf service"); - var file = Zotero.Zeroconf.getScript('kill_service'); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - var args = ['kill_service']; - var ret = process.run(false, args, args.length); - - if (ret != 0) { - Zotero.debug("Zeroconf client not stopped!", 2); - } - - // Remove any zoteroconf files remaining in tmp directory - var file = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - file.initWithPath('/tmp'); - if (!file.exists() || !file.isDirectory()) { - return; - } - try { - var files = file.directoryEntries; - while (files.hasMoreElements()) { - var tmpFile = files.getNext(); - tmpFile.QueryInterface(Components.interfaces.nsILocalFile); - if (tmpFile.leafName.indexOf('zoteroconf') != -1) { - tmpFile.remove(null); - } - } - } - catch (e) { - Zotero.debug(e); - } - } - - - function getScript() { - var file = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager) - .getInstallLocation(ZOTERO_CONFIG['GUID']) - .getItemLocation(ZOTERO_CONFIG['GUID']); - file.append('scripts'); - file.append('zoteroconf.sh'); - - // The first time we load the script, do some checks - if (!scriptsLoaded) { - if (!file.exists()) { - throw ('zoteroconf.sh not found in Zotero.Zeroconf.getScript()'); - } - - // Make sure the file is executable - if (file.permissions != 33261) { - try { - file.permissions = 33261; - } - catch (e) { - throw ('Cannot make zoteroconf.sh executable in Zotero.Zeroconf.getScript()'); - } - } - } - - return file; - } -} - - - -Zotero.Zeroconf.RemoteLibrary = function (name) { - default xml namespace = ''; - - this.name = name; - - this._host; - this._port; - this._items = []; - this._tmpFile = '/tmp/zoteroconf_info_' + Zotero.randomString(6); - //this.search = new Zotero.Zeroconf.RemoteLibrary.Search(this); -} - -Zotero.Zeroconf.RemoteLibrary.prototype.load = function () { - Zotero.debug("Getting service info for " + this.name); - - var file = Zotero.Zeroconf.getScript('get_info'); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - var args = ['get_info', this.name, this._tmpFile]; - process.run(false, args, args.length); - - var self = this; - - setTimeout(function () { - var file = Zotero.Zeroconf.getScript('kill_get_info'); - - var process = Components.classes["@mozilla.org/process/util;1"]. - createInstance(Components.interfaces.nsIProcess); - process.init(file); - var args = ['kill_get_info']; - process.run(false, args, args.length); - - var file = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - file.initWithPath(self._tmpFile); - - var infoCache = Zotero.File.getContents(file); - Zotero.debug(infoCache); - file.remove(null); - - var lines = infoCache.split(/\n/); - for each(var line in lines) { - var matches = line.match(/can be reached at +([^ ]+) *:([0-9]+)/); - if (matches) { - self._host = matches[1]; - self._port = matches[2]; - break; - } - } - - if (self._host) { - self.loadItems(self); - } - }, 250); -} - -Zotero.Zeroconf.RemoteLibrary.prototype.loadItems = function (self, noNotify) { - var url = "http://" + this._host + ':' + this._port; - Zotero.HTTP.doPost(url, '', function (xmlhttp) { - Zotero.debug(xmlhttp.responseText); - - self._items = []; - var xml = new XML(xmlhttp.responseText); - for each(var xmlNode in xml.items.item) { - var obj = Zotero.Sync.Server.Data.xmlToItem(xmlNode, false, true); - self._items.push(obj); - } - - Zotero.debug("Retrieved " + self._items.length + - " item" + (self._items.length == 1 ? '' : 's')); - - if (!noNotify) { - Zotero.Notifier.trigger('refresh', 'share-items', 'all'); - } - }); -} - -Zotero.Zeroconf.RemoteLibrary.prototype.getAll = function () { - if (!this._host) { - this.load(); - return []; - } - - this.loadItems(this, true); - - return this._items; -} - -/* -Zotero.Zeroconf.RemoteLibrary.Search = function (library) { - this.library = library; -} - -Zotero.Zeroconf.RemoteLibrary.Search.prototype = function () { - -} -*/ diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 19f29ce1e7..5718ee95b6 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -635,8 +635,6 @@ const ZOTERO_CONFIG = { Zotero.Server.init(); } - Zotero.Zeroconf.init(); - Zotero.Sync.init(); Zotero.Sync.Runner.init(); diff --git a/components/zotero-service.js b/components/zotero-service.js index 4bf06bf98f..557004daa1 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -100,7 +100,6 @@ const xpcomFilesLocal = [ 'storage/webdav', 'timeline', 'uri', - 'zeroconf', 'translation/translate_item', 'translation/translator', 'server_connector' diff --git a/scripts/zoteroconf.sh b/scripts/zoteroconf.sh deleted file mode 100755 index 3d87d6bc19..0000000000 --- a/scripts/zoteroconf.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -if [ ! "$1" ]; then - echo "Action not specified" - exit 1 -fi - -if [ $1 = "find_instances" ]; then - dns-sd -B _zotero._tcp local. > /tmp/zoteroconf_instances & - -elif [ $1 = "kill_find_instances" ]; then - PIDs=`ps x | grep "dns-sd -B" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` - if [ "$PIDs" ]; then - kill $PIDs - fi - -elif [ $1 = "get_info" ]; then - if [ ! "$2" ]; then - echo "Service name not specified" - exit 1 - fi - - if [ ! "$3" ]; then - echo "Temp file path not specified" - exit 1 - fi - - #dns-sd -L "$2" _zotero._tcp local. > $3 & - mDNS -L "$2" _zotero._tcp local. > $3 & - -elif [ $1 = "kill_get_info" ]; then - #PIDs=`ps x | grep "dns-sd -L" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` - PIDs=`ps x | grep "mDNS -L" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` - if [ "$PIDs" ]; then - kill $PIDs - fi - -elif [ $1 = "kill_service" ]; then - PIDs=`ps x | grep dns-sd | grep '_zotero._tcp' | sed -E 's/ *([0-9]+).*/\1/' | xargs` - if [ "$PIDs" ]; then - kill $PIDs - fi -fi From ef9040c46d230a76099ee2fa556e4208d85a08cf Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Sat, 11 Feb 2012 00:38:21 -0500 Subject: [PATCH 5/5] Revert "Remove antiquated code" This reverts commit d5fc04485b3580bcbbb7a2f29a755749ef914d6b. --- .../zotero/xpcom/collectionTreeView.js | 4 +- chrome/content/zotero/xpcom/dataServer.js | 297 ++++++++++++++ chrome/content/zotero/xpcom/zeroconf.js | 377 ++++++++++++++++++ chrome/content/zotero/xpcom/zotero.js | 2 + components/zotero-service.js | 1 + scripts/zoteroconf.sh | 42 ++ 6 files changed, 721 insertions(+), 2 deletions(-) create mode 100644 chrome/content/zotero/xpcom/dataServer.js create mode 100644 chrome/content/zotero/xpcom/zeroconf.js create mode 100755 scripts/zoteroconf.sh diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 6a5a09c9bb..66977ff85a 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -182,13 +182,13 @@ Zotero.CollectionTreeView.prototype.refresh = function() } } - /*var shares = Zotero.Zeroconf.instances; + var shares = Zotero.Zeroconf.instances; if (shares.length) { this._showRow(new Zotero.ItemGroup('separator', false)); for each(var share in shares) { this._showRow(new Zotero.ItemGroup('share', share)); } - }*/ + } if (this.hideSources.indexOf('commons') == -1 && Zotero.Commons.enabled) { this._showRow(new Zotero.ItemGroup('separator', false)); diff --git a/chrome/content/zotero/xpcom/dataServer.js b/chrome/content/zotero/xpcom/dataServer.js new file mode 100644 index 0000000000..6f8e1033fd --- /dev/null +++ b/chrome/content/zotero/xpcom/dataServer.js @@ -0,0 +1,297 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2009 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + + +Zotero.DataServer = new function () { + this.init = init; + this.handleHeader = handleHeader; + + // TODO: assign dynamically + this.__defineGetter__('port', function () { + return 22030; + }); + + var _onlineObserverRegistered; + + + /* + * initializes a very rudimentary web server used for SOAP RPC + */ + function init() { + // Use Zeroconf pref for now + if (!Zotero.Prefs.get("zeroconf.server.enabled")) { + Zotero.debug("Not initializing data HTTP server"); + return; + } + + if (Zotero.HTTP.browserIsOffline()) { + Zotero.debug('Browser is offline -- not initializing data HTTP server'); + _registerOnlineObserver() + return; + } + + // start listening on socket + var serv = Components.classes["@mozilla.org/network/server-socket;1"] + .createInstance(Components.interfaces.nsIServerSocket); + try { + serv.init(this.port, false, -1); + serv.asyncListen(Zotero.DataServer.SocketListener); + + Zotero.debug("Data HTTP server listening on 127.0.0.1:" + serv.port); + } + catch(e) { + Zotero.debug("Not initializing data HTTP server"); + } + + _registerOnlineObserver() + } + + /* + * handles an HTTP request + */ + function handleHeader(header) { + // get first line of request (all we care about for now) + var method = header.substr(0, header.indexOf(" ")); + + if (!method) { + return _generateResponse("400 Bad Request"); + } + + if (method != "POST") { + return _generateResponse("501 Method Not Implemented"); + } + + // Parse request URI + var matches = header.match("^[A-Z]+ (\/.*) HTTP/1.[01]"); + if (!matches) { + return _generateResponse("400 Bad Request"); + } + + var response = _handleRequest(matches[1]); + + // return OK + return _generateResponse("200 OK", 'text/xml; charset="UTF-8"', response); + } + + + function _handleRequest(uri) { + var s = new Zotero.Search(); + s.addCondition('noChildren', 'true'); + var ids = s.search(); + + if (!ids) { + ids = []; + } + + var uploadIDs = { + updated: { + items: ids + }, + /* TODO: fix buildUploadXML to ignore missing */ + deleted: {} + }; + return Zotero.Sync.Server.Data.buildUploadXML(uploadIDs); + } + + + /* + * generates the response to an HTTP request + */ + function _generateResponse(status, contentType, body) { + var response = "HTTP/1.0 "+status+"\r\n"; + + if(body) { + if(contentType) { + response += "Content-Type: "+contentType+"\r\n"; + } + response += "\r\n"+body; + } else { + response += "Content-Length: 0\r\n\r\n" + } + + return response; + } + + + function _registerOnlineObserver() { + if (_onlineObserverRegistered) { + return; + } + + // Observer to enable the integration when we go online + var observer = { + observe: function(subject, topic, data) { + if (data == 'online') { + Zotero.Integration.init(); + } + } + }; + + var observerService = + Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + observerService.addObserver(observer, "network:offline-status-changed", false); + + _onlineObserverRegistered = true; + } +} + + +Zotero.DataServer.SocketListener = new function() { + this.onSocketAccepted = onSocketAccepted; + + /* + * called when a socket is opened + */ + function onSocketAccepted(socket, transport) { + // get an input stream + var iStream = transport.openInputStream(0, 0, 0); + var oStream = transport.openOutputStream(0, 0, 0); + + var dataListener = new Zotero.DataServer.DataListener(iStream, oStream); + var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"] + .createInstance(Components.interfaces.nsIInputStreamPump); + pump.init(iStream, -1, -1, 0, 0, false); + pump.asyncRead(dataListener, null); + } +} + +/* + * handles the actual acquisition of data + */ +Zotero.DataServer.DataListener = function(iStream, oStream) { + this.header = ""; + this.headerFinished = false; + + this.body = ""; + this.bodyLength = 0; + + this.iStream = iStream; + this.oStream = oStream; + this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"] + .createInstance(Components.interfaces.nsIScriptableInputStream); + this.sStream.init(iStream); + + this.foundReturn = false; +} + +/* + * called when a request begins (although the request should have begun before + * the DataListener was generated) + */ +Zotero.DataServer.DataListener.prototype.onStartRequest = function(request, context) {} + +/* + * called when a request stops + */ +Zotero.DataServer.DataListener.prototype.onStopRequest = function(request, context, status) { + this.iStream.close(); + this.oStream.close(); +} + +/* + * called when new data is available + */ +Zotero.DataServer.DataListener.prototype.onDataAvailable = function(request, context, + inputStream, offset, count) { + var readData = this.sStream.read(count); + + // Read header + if (!this.headerFinished) { + // see if there's a magic double return + var lineBreakIndex = readData.indexOf("\r\n\r\n"); + if (lineBreakIndex != -1) { + if (lineBreakIndex != 0) { + this.header += readData.substr(0, lineBreakIndex+4); + } + + this._headerFinished(); + return; + } + + var lineBreakIndex = readData.indexOf("\n\n"); + if (lineBreakIndex != -1) { + if (lineBreakIndex != 0) { + this.header += readData.substr(0, lineBreakIndex+2); + } + + this._headerFinished(); + return; + } + + if (this.header && this.header[this.header.length-1] == "\n" && + (readData[0] == "\n" || readData[0] == "\r")) { + if (readData.length > 1 && readData[1] == "\n") { + this.header += readData.substr(0, 2); + } + else { + this.header += readData[0]; + } + + this._headerFinished(); + return; + } + + this.header += readData; + } +} + +/* + * processes an HTTP header and decides what to do + */ +Zotero.DataServer.DataListener.prototype._headerFinished = function() { + this.headerFinished = true; + var output = Zotero.DataServer.handleHeader(this.header); + this._requestFinished(output); +} + +/* + * returns HTTP data from a request + */ +Zotero.DataServer.DataListener.prototype._requestFinished = function(response) { + // close input stream + this.iStream.close(); + + // open UTF-8 converter for output stream + var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Components.interfaces.nsIConverterOutputStream); + + // write + try { + intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0)); + + Zotero.debug('Writing response to stream:\n\n' + response); + + // write response + intlStream.writeString(response); + } catch(e) { + Zotero.debug("An error occurred."); + Zotero.debug(e); + } finally { + Zotero.debug('Closing stream'); + intlStream.close(); + } +} + diff --git a/chrome/content/zotero/xpcom/zeroconf.js b/chrome/content/zotero/xpcom/zeroconf.js new file mode 100644 index 0000000000..9f837c911e --- /dev/null +++ b/chrome/content/zotero/xpcom/zeroconf.js @@ -0,0 +1,377 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2009 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + + +Zotero.Zeroconf = new function () { + this.init = init; + this.registerService = registerService; + this.findInstances = findInstances; + this.findInstancesCallback = findInstancesCallback; + this.unregisterService = unregisterService; + this.getScript = getScript; + + this.clientEnabled = true; + this.serverEnabled = true; + + this.__defineGetter__('clientPath', function () { + return '/usr/bin/dns-sd'; + }); + + this.__defineGetter__('displayName', function () { + var dnsService = Components.classes["@mozilla.org/network/dns-service;1"]. + getService(Components.interfaces.nsIDNSService); + var hostname = dnsService.myHostName; + + return hostname; + }); + + this.__defineGetter__('port', function () { + return Zotero.DataServer.port; + }); + + this.__defineGetter__('instances', function () { + var instances = {}; + for (var instance in _instances) { + instances[instance] = new Zotero.Zeroconf.RemoteLibrary(instance); + } + return instances; + }); + + var _instances = []; + var _browseCacheFile = '/tmp/zoteroconf_instances'; + var scriptsLoaded = false; + + function init() { + if (!Zotero.Prefs.get("zeroconf.server.enabled")) { + this.clientEnabled = false; + this.serverEnabled = false; + } + + // OS X only, for now + if (!Zotero.isMac) { + this.clientEnabled = false; + this.serverEnabled = false; + + // TODO: Why is Windows breaking without this? + return; + } + + // Make sure we have the client executable + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(this.clientPath); + + if (!file.exists()) { + Zotero.debug('Not enabling Z(ot)eroconf -- executable not found'); + this.clientEnabled = false; + this.serverEnabled = false; + return; + } + + if (!this.serverEnabled) { + Zotero.debug('Not enabling Z(ot)eroconf'); + return; + } + + var registered = this.registerService(); + if (!registered) { + return; + } + + var observerService = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + observerService.addObserver({ + observe: function(subject, topic, data) { + Zotero.Zeroconf.unregisterService(); + } + }, "quit-application", false); + } + + + function registerService() { + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(this.clientPath); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + + var args = ["-R", this.displayName, "_zotero._tcp", "local.", this.port]; + + Zotero.debug("Registering Z(ot)eroconf on port " + this.port); + process.run(false, args, args.length); + + return true; + } + + + function findInstances(callback) { + if (!this.clientEnabled) { + return; + } + + Zotero.debug("Browsing for Z(ot)eroconf instances"); + var file = this.getScript('find_instances'); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + var args = ['find_instances']; + process.run(false, args, args.length); + + // Wait half a second for browse before proceeding + setTimeout(function () { + Zotero.Zeroconf.findInstancesCallback(callback); + }, 500); + } + + + function findInstancesCallback(callback) { + var file = Zotero.Zeroconf.getScript('kill_find_instances'); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + var args = ['kill_find_instances']; + process.run(false, args, args.length); + + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(_browseCacheFile); + + if (!file.exists()) { + Zotero.debug(_browseCacheFile + " doesn't exist", 2); + _instances = {}; + return; + } + + var browseCache = Zotero.File.getContents(file); + Zotero.debug(browseCache); + file.remove(null); + + // Parse browse output + var lines = browseCache.split(/\n/); + var newInstances = {}; + for each(var line in lines) { + var matches = line.match(/([a-zA-Z\.]+) +_zotero\._tcp\. +(.+)/); + if (matches) { + var domain = matches[1]; + var name = matches[2]; + // Skip local host + if (name == this.displayName) { + continue; + } + newInstances[name] = true; + } + } + + // Remove expired instances + for (var instance in _instances) { + if (!newInstances[instance]) { + delete _instances[instance]; + } + } + + // Add new instances + for (var instance in newInstances) { + _instances[instance] = true; + } + + Zotero.Notifier.trigger('refresh', 'share', 'all'); + + if (callback) { + callback(); + } + } + + + function unregisterService() { + Zotero.debug("Unregistering Zeroconf service"); + var file = Zotero.Zeroconf.getScript('kill_service'); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + var args = ['kill_service']; + var ret = process.run(false, args, args.length); + + if (ret != 0) { + Zotero.debug("Zeroconf client not stopped!", 2); + } + + // Remove any zoteroconf files remaining in tmp directory + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath('/tmp'); + if (!file.exists() || !file.isDirectory()) { + return; + } + try { + var files = file.directoryEntries; + while (files.hasMoreElements()) { + var tmpFile = files.getNext(); + tmpFile.QueryInterface(Components.interfaces.nsILocalFile); + if (tmpFile.leafName.indexOf('zoteroconf') != -1) { + tmpFile.remove(null); + } + } + } + catch (e) { + Zotero.debug(e); + } + } + + + function getScript() { + var file = Components.classes["@mozilla.org/extensions/manager;1"] + .getService(Components.interfaces.nsIExtensionManager) + .getInstallLocation(ZOTERO_CONFIG['GUID']) + .getItemLocation(ZOTERO_CONFIG['GUID']); + file.append('scripts'); + file.append('zoteroconf.sh'); + + // The first time we load the script, do some checks + if (!scriptsLoaded) { + if (!file.exists()) { + throw ('zoteroconf.sh not found in Zotero.Zeroconf.getScript()'); + } + + // Make sure the file is executable + if (file.permissions != 33261) { + try { + file.permissions = 33261; + } + catch (e) { + throw ('Cannot make zoteroconf.sh executable in Zotero.Zeroconf.getScript()'); + } + } + } + + return file; + } +} + + + +Zotero.Zeroconf.RemoteLibrary = function (name) { + default xml namespace = ''; + + this.name = name; + + this._host; + this._port; + this._items = []; + this._tmpFile = '/tmp/zoteroconf_info_' + Zotero.randomString(6); + //this.search = new Zotero.Zeroconf.RemoteLibrary.Search(this); +} + +Zotero.Zeroconf.RemoteLibrary.prototype.load = function () { + Zotero.debug("Getting service info for " + this.name); + + var file = Zotero.Zeroconf.getScript('get_info'); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + var args = ['get_info', this.name, this._tmpFile]; + process.run(false, args, args.length); + + var self = this; + + setTimeout(function () { + var file = Zotero.Zeroconf.getScript('kill_get_info'); + + var process = Components.classes["@mozilla.org/process/util;1"]. + createInstance(Components.interfaces.nsIProcess); + process.init(file); + var args = ['kill_get_info']; + process.run(false, args, args.length); + + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(self._tmpFile); + + var infoCache = Zotero.File.getContents(file); + Zotero.debug(infoCache); + file.remove(null); + + var lines = infoCache.split(/\n/); + for each(var line in lines) { + var matches = line.match(/can be reached at +([^ ]+) *:([0-9]+)/); + if (matches) { + self._host = matches[1]; + self._port = matches[2]; + break; + } + } + + if (self._host) { + self.loadItems(self); + } + }, 250); +} + +Zotero.Zeroconf.RemoteLibrary.prototype.loadItems = function (self, noNotify) { + var url = "http://" + this._host + ':' + this._port; + Zotero.HTTP.doPost(url, '', function (xmlhttp) { + Zotero.debug(xmlhttp.responseText); + + self._items = []; + var xml = new XML(xmlhttp.responseText); + for each(var xmlNode in xml.items.item) { + var obj = Zotero.Sync.Server.Data.xmlToItem(xmlNode, false, true); + self._items.push(obj); + } + + Zotero.debug("Retrieved " + self._items.length + + " item" + (self._items.length == 1 ? '' : 's')); + + if (!noNotify) { + Zotero.Notifier.trigger('refresh', 'share-items', 'all'); + } + }); +} + +Zotero.Zeroconf.RemoteLibrary.prototype.getAll = function () { + if (!this._host) { + this.load(); + return []; + } + + this.loadItems(this, true); + + return this._items; +} + +/* +Zotero.Zeroconf.RemoteLibrary.Search = function (library) { + this.library = library; +} + +Zotero.Zeroconf.RemoteLibrary.Search.prototype = function () { + +} +*/ diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 5718ee95b6..19f29ce1e7 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -635,6 +635,8 @@ const ZOTERO_CONFIG = { Zotero.Server.init(); } + Zotero.Zeroconf.init(); + Zotero.Sync.init(); Zotero.Sync.Runner.init(); diff --git a/components/zotero-service.js b/components/zotero-service.js index 557004daa1..4bf06bf98f 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -100,6 +100,7 @@ const xpcomFilesLocal = [ 'storage/webdav', 'timeline', 'uri', + 'zeroconf', 'translation/translate_item', 'translation/translator', 'server_connector' diff --git a/scripts/zoteroconf.sh b/scripts/zoteroconf.sh new file mode 100755 index 0000000000..3d87d6bc19 --- /dev/null +++ b/scripts/zoteroconf.sh @@ -0,0 +1,42 @@ +#!/bin/sh +if [ ! "$1" ]; then + echo "Action not specified" + exit 1 +fi + +if [ $1 = "find_instances" ]; then + dns-sd -B _zotero._tcp local. > /tmp/zoteroconf_instances & + +elif [ $1 = "kill_find_instances" ]; then + PIDs=`ps x | grep "dns-sd -B" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` + if [ "$PIDs" ]; then + kill $PIDs + fi + +elif [ $1 = "get_info" ]; then + if [ ! "$2" ]; then + echo "Service name not specified" + exit 1 + fi + + if [ ! "$3" ]; then + echo "Temp file path not specified" + exit 1 + fi + + #dns-sd -L "$2" _zotero._tcp local. > $3 & + mDNS -L "$2" _zotero._tcp local. > $3 & + +elif [ $1 = "kill_get_info" ]; then + #PIDs=`ps x | grep "dns-sd -L" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` + PIDs=`ps x | grep "mDNS -L" | grep _zotero._tcp | sed -E 's/ *([0-9]+).*/\1/' | xargs` + if [ "$PIDs" ]; then + kill $PIDs + fi + +elif [ $1 = "kill_service" ]; then + PIDs=`ps x | grep dns-sd | grep '_zotero._tcp' | sed -E 's/ *([0-9]+).*/\1/' | xargs` + if [ "$PIDs" ]; then + kill $PIDs + fi +fi