# The contents of this file are subject to the BitTorrent Open Source License # Version 1.1 (the License). You may not copy or use this file, in either # source code or executable form, except in compliance with the License. You # may obtain a copy of the License at http://www.bittorrent.com/license/. # # Software distributed under the License is distributed on an AS IS basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # Written by Matt Chisholm import libxml2 import BTL.stackthreading as threading #import gobject from PluginSupport import PluginManager, BasePlugin # BUG should be in config UPDATE_FEED_TIMEOUT = 5 * 60 def get_content(xpath_result): if len(xpath_result) == 1: return xpath_result[0].content return None class FeedPlugin(BasePlugin): mimetype = 'text/xml' subtype = None def __init__(self, main, url, title, description, doc=None): BasePlugin.__init__(self, main) self.url = url self.title = title self.description = description self.doc = None self.thread = None self.update(doc) timeout_id = gobject.timeout_add(UPDATE_FEED_TIMEOUT * 1000, self.update) def _matches_type(mimetype, subtype): raise NotImplementedError matches_type = staticmethod(_matches_type) def update(self, doc=None): if self.thread is not None: if self.thread.isAlive(): return True self.thread = threading.Thread(target=self._update, args=(doc,)) self.thread.setDaemon(True) self.main.show_status("Downloading %s" % self.url) self.main.ui_wrap_func(self.thread.start) return True def _update(self, doc=None): self.main.show_status('_update running') if self.doc is not None: self.main.show_status('_update freeing doc') self.doc.freeDoc() if doc is None: self.main.show_status('_update parsingFile') doc = libxml2.parseFile(self.url) self.doc = doc self.main.feed_was_updated(self.url) self.main.show_status(self.get_items()) self.main.show_status('_update done, thread should die') def get_items(self): raise NotImplementedError class FeedManager(PluginManager): kind = 'Feed' def __init__(self, config, ui_wrap_func): PluginManager.__init__(self, config, ui_wrap_func) self.ui_wrap_func = ui_wrap_func self.channels = {} self.update_all() def _check_plugin(self, plugin): if not PluginManager._check_plugin(self, plugin): return False if issubclass(plugin, FeedPlugin): if hasattr(plugin, 'get_items'): return True return False def update_all(self): for k in self.config.keys(): self.update_feed(k) def update_feed(self, feed_url): if self.channels.has_key(feed_url): self.show_status("Updating %s" % feed_url) self.channels[feed_url].update() else: self.new_channel(feed_url) return def new_channel(self, url): thread = threading.Thread(target=self._new_channel, args=(url,)) thread.setDaemon(True) self.ui_wrap_func(thread.start) def _new_channel(self, url): self.show_status("Adding %s" % url) doc = None channel = None try: doc = libxml2.parseFile(url) except libxml2.parserError: self.show_status("Could not parse \"%s\" as XML, searching for torrents" % url) else: context = doc.xpathNewContext() res = context.xpathEval('/rss/@version') if get_content(res) in ('0.91', '0.92', '2.0'): self.show_status("Found RSS 0.9x/2.0") plugin = self._find_plugin('text/xml', 'rss2') if plugin is not None: channel = plugin(self, url, doc) root = doc.children if (root.ns() is not None and root.ns().get_content() in ("http://www.w3.org/2005/Atom",)): self.show_status("Found Atom") plugin = self._find_plugin('text/xml', 'atom') if plugin is not None: channel = plugin(self, url, doc) context.xpathFreeContext() if channel is None: if doc is not None: doc.freeDoc() self.show_status("Unknown feed type, using raw") plugin = self._find_plugin(None, 'raw') if plugin is not None: channel = plugin(self, url, url) self.channels[url] = channel def feed_was_updated(self, feed_url): self.run_ui_task('feed_was_updated', feed_url) def get_feed(self, feed_url): if not self.channels.has_key(feed_url): self.update_feed(feed_url) feed = self.channels[feed_url] return feed def get_items(self, feed_url): channel = self.channels[feed_url] items = channel.get_items() return items