diff --git a/chrome/content/zotero/xpcom/feedReader.js b/chrome/content/zotero/xpcom/feedReader.js new file mode 100644 index 0000000000..c6cf624f17 --- /dev/null +++ b/chrome/content/zotero/xpcom/feedReader.js @@ -0,0 +1,530 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2015 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 ***** +*/ + + +/** + * Sample feeds: + * + * http://cyber.law.harvard.edu/rss/examples/rss2sample.xml + * http://feeds.feedburner.com/acs/acbcct + * http://www.cell.com/molecular-cell/current.rss + * http://ieeexplore.ieee.org/search/searchresult.jsp?searchField%3DSearch_All%26queryText%3Dwater&searchOrigin=saved_searches&rssFeed=true&rssFeedName=water + * http://www.sciencemag.org/rss/current.xml + * http://rss.sciencedirect.com/publication/science/20925212 + * http://www.ncbi.nlm.nih.gov/entrez/eutils/erss.cgi?rss_guid=1fmfIeN4X5Q8HemTZD5Rj6iu6-FQVCn7xc7_IPIIQtS1XiD9bf + * http://export.arxiv.org/rss/astro-ph + */ + +/** + * class Zotero.FeedReader + * Asynchronously reads an ATOM/RSS feed + * + * @param {String} url URL of the feed + * + * @property {Zotero.Promise} feedProperties An object + * representing feed properties + * @property {Zotero.Promise*} itemIterator Returns an iterator + * for feed items. The iterator returns FeedItem promises that have to be + * resolved before requesting the next promise. When all items are exhausted. + * the promise resolves to null. + * @method {void} terminate Stops retrieving/parsing the feed. Data parsed up + * to this point is still available. + */ +Zotero.FeedReader = new function() { + let ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + + /***************************** + * Item processing functions * + *****************************/ + + /** + * Determine item type based on item data + */ + function guessItemType(item) { + // Default to journalArticle + item.itemType = 'journalArticle'; + + if (item.ISSN) { + return; // journalArticle + } + + if (item.ISBN) { + item.itemType = 'bookSection'; + return; + } + + if (item.publicationType) { + let type = item.publicationType.toLowerCase(); + if (type.indexOf('conference') != -1) { + item.itemType = 'conferencePaper'; + return; + } + if (type.indexOf('journal') != -1) { + item.itemType = 'journalArticle'; + return; + } + if (type.indexOf('book') != -1) { + item.itemType = 'bookSection'; + return; + } + } + }; + + /* + * Fetch creators from given field of a feed entry + */ + function processCreators(feedEntry, field, role) { + let names = [], + nameStr; + try { + let personArr = feedEntry[field]; // Seems like this part can throw if there is no author data in the feed + for (let i=0; i 1 + // If only one comma and first part has more than one space, + // it's probably not lastName, firstName + || (commas == 1 && name.split(/\s*,/)[0].indexOf(' ') != -1) + ) + ) { + // Probably multiple authors listed in a single field + nameStr = name; + break; // For clarity. personArr.length == 1 anyway + } else { + names.push(name); + } + } + } catch(e) { + if (e.result != Components.results.NS_ERROR_FAILURE) throw e + + if (field != 'authors') return []; + + // ieeexplore places these in "authors"... sigh + nameStr = getFeedField(feedEntry, null, 'authors'); + if (nameStr) nameStr = Zotero.Utilities.trimInternal(nameStr); + if (!nameStr) return []; + } + + if (nameStr) { + names = nameStr.split(/\s(?:and|&)\s|\s*[,;]\s*/); + } + + let creators = []; + for (let i=0; i { + let items = feed.items; + if (items && items.length) { + for (let i=0; i { + // Make sure the last promise gets resolved to null + let lastItem = this._feedItems[this._feedItems.length - 1]; + lastItem.resolve(null); + }); + + // Set up asynchronous feed processor + let feedProcessor = Components.classes["@mozilla.org/feed-processor;1"] + .createInstance(Components.interfaces.nsIFeedProcessor); + + let feedUrl = ios.newURI(url, null, null); + feedProcessor.parseAsync(null, feedUrl); + + feedProcessor.listener = { + /* + * MDN suggests that we could use nsIFeedProgressListener to handle the feed + * as it gets loaded, but this is actually not implemented (as of 32.0.3), + * so we have to load the whole feed and handle it in handleResult. + */ + handleResult: (result) => { + if (!result.doc) { + this.terminate("No Feed"); + return; + } + + let newFeed = result.doc.QueryInterface(Components.interfaces.nsIFeed); + this._feed.resolve(newFeed); + } + }; + + Zotero.debug("FeedReader: Fetching feed from " + feedUrl.spec); + + this._channel = ios.newChannelFromURI(feedUrl); + this._channel.asyncOpen(feedProcessor, null); // Sends an HTTP request + } + + Zotero.defineProperty(FeedReader.prototype, 'feedProperties', { + get: function() this._feedProperties + }); + + /* + * Feed item iterator + * Each iteration returns a _promise_ for an item. The promise _MUST_ be + * resolved before requesting the next item. + * The last item will always be resolved to `null`, unless the feed processing + * is terminated ahead of time, in which case it will be rejected with the reason + * for termination. + */ + Zotero.defineProperty(FeedReader.prototype, 'itemIterator', { + get: function() { + let items = this._feedItems; + return new function() { + let i = 0; + this.next = function() { + let item = items[i++]; + return { + value: item ? item.promise : null, + done: i >= items.length + }; + }; + } + } + }); + + /* + * Terminate feed processing at any given time + * @param {String} status Reason for terminating processing + */ + FeedReader.prototype.terminate = function(status) { + Zotero.debug("FeedReader: Terminating feed reader (" + status + ")"); + + // Reject feed promise if not resolved yet + if (this._feed.promise.isPending()) { + this._feed.reject(status); + } + + // Reject feed item promise if not resolved yet + let lastItem = this._feedItems[this._feedItems.length - 1]; + if (lastItem.promise.isPending()) { + lastItem.reject(status); + } + + // Close feed connection + if (channel.isPending) { + channel.cancel(Components.results.NS_BINDING_ABORTED); + } + }; + + return FeedReader; +}; \ No newline at end of file diff --git a/components/zotero-service.js b/components/zotero-service.js index 86ed2c06aa..6fe1af8979 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -85,6 +85,7 @@ const xpcomFilesLocal = [ 'data/tags', 'db', 'duplicates', + 'feedReader', 'fulltext', 'id', 'integration',