From 5d286e95ce5a414681b87dd7676d1f3d432fefe5 Mon Sep 17 00:00:00 2001 From: jmalonzo Date: Tue, 24 Jul 2007 09:31:00 +0000 Subject: [PATCH] iteration 1 - Use gobject for events in feed, summaryitem and feedlist - initial use of gobjects for events handling in Feed, SummaryItem, and FeedList - affected modules below. - unused modules were deleted. this modules have been refactored. modified: src/lib/Application.py deleted: src/lib/CategorySelector.py deleted: src/lib/Feed.py modified: src/lib/FeedCategoryList.py deleted: src/lib/FeedItems.py deleted: src/lib/FeedList.py modified: src/lib/FeedListView.py modified: src/lib/FeedPropertiesDialog.py modified: src/lib/Find.py modified: src/lib/ItemStore.py modified: src/lib/OPMLImport.py modified: src/lib/PollManager.py modified: src/lib/PreferencesDialog.py deleted: src/lib/QueueDict.py modified: src/lib/SummaryItem.py modified: src/lib/dialogs.py new file: src/lib/feeds.py modified: src/lib/subscribe.py git-svn-id: svn+ssh://svn.gnome.org/svn/straw/trunk@305 141a2093-ea25-0410-9ad2-d44d734a8f13 --- src/lib/Application.py | 24 +- src/lib/CategorySelector.py | 185 ------------- src/lib/Feed.py | 368 ------------------------- src/lib/FeedCategoryList.py | 33 +-- src/lib/FeedItems.py | 232 ---------------- src/lib/FeedList.py | 194 -------------- src/lib/FeedListView.py | 35 ++- src/lib/FeedPropertiesDialog.py | 10 +- src/lib/Find.py | 4 +- src/lib/ItemStore.py | 59 ++-- src/lib/OPMLImport.py | 7 +- src/lib/PollManager.py | 12 +- src/lib/PreferencesDialog.py | 6 +- src/lib/QueueDict.py | 109 -------- src/lib/SummaryItem.py | 33 ++- src/lib/dialogs.py | 1 - src/lib/feeds.py | 579 ++++++++++++++++++++++++++++++++++++++++ src/lib/subscribe.py | 7 +- 18 files changed, 690 insertions(+), 1208 deletions(-) delete mode 100644 src/lib/CategorySelector.py delete mode 100644 src/lib/Feed.py delete mode 100644 src/lib/FeedItems.py delete mode 100644 src/lib/FeedList.py delete mode 100644 src/lib/QueueDict.py create mode 100644 src/lib/feeds.py diff --git a/src/lib/Application.py b/src/lib/Application.py index d52866c..43f5e44 100644 --- a/src/lib/Application.py +++ b/src/lib/Application.py @@ -32,7 +32,7 @@ import utils import subscribe import Event import MessageManager -import FeedList +import feeds import FeedCategoryList import Config import error @@ -334,7 +334,7 @@ class ApplicationPresenter(MVP.BasicPresenter): def mark_all_as_read(self): mmgr = MainloopManager.get_instance() - flist = FeedList.get_instance().flatten_list() + flist = feeds.get_instance().flatten_list() mmgr.call_pending() for feed in flist: feed.mark_all_read() @@ -538,15 +538,15 @@ class ApplicationView(MVP.WidgetView): return def _initialize_window_updater(self): - feedlist = FeedList.get_instance() - feedlist.signal_connect(Event.AllItemsReadSignal, - lambda signal: self._update_title(feedlist)) - feedlist.signal_connect(Event.ItemReadSignal, - lambda signal: self._update_title(feedlist)) - feedlist.signal_connect(Event.ItemsAddedSignal, - lambda signal: self._update_title(feedlist)) - feedlist.signal_connect(Event.FeedsChangedSignal, - lambda signal: self._update_title(feedlist)) + feedlist = feeds.get_instance() + #feedlist.signal_connect(Event.AllItemsReadSignal, + # lambda signal: self._update_title(feedlist)) + #feedlist.signal_connect(Event.ItemReadSignal, + # lambda signal: self._update_title(feedlist)) + #feedlist.signal_connect(Event.ItemsAddedSignal, + # lambda signal: self._update_title(feedlist)) + #feedlist.signal_connect(Event.FeedsChangedSignal, + # lambda signal: self._update_title(feedlist)) return def _update_title(self, flist): @@ -759,7 +759,7 @@ class Application: config = Config.get_instance() - feedlist = FeedList.get_instance() + feedlist = feeds.get_instance() feed_categories = FeedCategoryList.get_instance() # initialise GUI diff --git a/src/lib/CategorySelector.py b/src/lib/CategorySelector.py deleted file mode 100644 index 145ef25..0000000 --- a/src/lib/CategorySelector.py +++ /dev/null @@ -1,185 +0,0 @@ -""" CategorySelector.py - -Module for selecting categories -""" -__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." -__license__ = """ -Straw is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -Straw 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 General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place - Suite 330, Boston, MA 02111-1307, USA. """ - -import locale -import pygtk -pygtk.require('2.0') -import gobject -import gtk -import FeedCategoryList -import MVP -import Event - -class Column: - TITLE=0 - CATEGORY=1 - -class CategoryView(MVP.WidgetView): - """ - Widget: gtk.ComboBox - Model: ListStore - """ - def _initialize(self): - self._popup = None - self._widget.set_wrap_width(1) - title = gtk.CellRendererText() - self._widget.pack_start(title,False) - self._widget.add_attribute(title,'text',Column.TITLE) - self._widget.connect('changed', self._selection_changed) - return - - def _model_set(self): - self._widget.set_model(self._model) - return - - def _selection_changed(self, combo, data=None): - self._presenter.selection_changed(combo, data) - - def set_active(self, index): self._widget.set_active(index) - def get_active(self): return self._widget.get_active() - - def get_active_iter(self): return self._widget.get_active_iter() - def set_active_iter(self, it): self._widget.set_active_iter(it) - -class CategoryPresenter(MVP.BasicPresenter): - def _initialize(self): - self.initialize_slots(Event.CategorySelectionChangedSignal) - self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) - self.model.set_sort_func(Column.TITLE, self._sort_func) - self.model.set_sort_column_id(Column.TITLE, gtk.SORT_ASCENDING) - self._init_signals() - return - - def _sort_func(self, model, a, b): - """ - From the gtk.TreeSortable.set_sort_func doc: - - The comparison callback should return -1 if the iter1 row should come before - the iter2 row, 0 if the rows are equal, or 1 if the iter1 row should come - after the iter2 row. - """ - fclist = FeedCategoryList.get_instance() - retval = 0 - cat_a = model.get_value(a, Column.CATEGORY) - cat_b = model.get_value(b, Column.CATEGORY) - - if cat_a in fclist.pseudo_categories: - retval = -1 - elif cat_b in fclist.pseudo_categories: - retval = 1 - elif cat_a is not None and cat_b is not None: - retval = locale.strcoll(cat_a.title, cat_b.title) - elif cat_a is not None: retval = -1 - elif cat_b is not None: retval = 1 - return retval - - def _init_signals(self): - fclist = FeedCategoryList.get_instance() - fclist.signal_connect(Event.FeedCategoryListLoadedSignal, - self._display_categories) - fclist.signal_connect(Event.FeedCategoryAddedSignal, - self._category_added_cb) - fclist.signal_connect(Event.FeedCategoryRemovedSignal, - self._category_removed_cb) - fclist.signal_connect(Event.FeedCategoryChangedSignal, - self._category_changed_cb) - return - - def _get_selected_category(self): - category = None - if len(self._model): - active = self._view.get_active() - category = self._model[active][Column.CATEGORY] - return category - - def _category_added_cb(self, signal): - newcat = signal.category - treeiter = self._model.append() - self._model.set(treeiter, - Column.TITLE, newcat.title, - Column.CATEGORY, newcat) - return - - def _category_removed_cb(self, signal): - self._model.foreach(self._foreach_remove_cat, - signal.category) - self._update_selection() - return - - def _foreach_remove_cat(self, model, path, iter, category): - cat = model[path][Column.CATEGORY] - if cat is category: - model.remove(iter) - return True - return False - - def _category_changed_cb(self, signal): - self._model.foreach(self._foreach_category_changed, - signal.sender) - self._update_selection() - return - - def _foreach_category_changed(self, model, path, iter, category): - cat = model[path][Column.CATEGORY] - if cat is category: - model.set(iter, - Column.TITLE, category.title, - Column.CATEGORY, category) - return True - return False - - def _display_categories(self, signal): - fclist = signal.sender - self._model.clear() - for category in fclist.pseudo_categories: - it = self._model.append() - self._model.set(it, - Column.TITLE, category.title, - Column.CATEGORY, category) - for category in fclist.user_categories: - it = self._model.append() - self._model.set(it, - Column.TITLE, category.title, - Column.CATEGORY, category) - self._update_selection() - return - - def _update_selection(self): - active = self._view.get_active() - if active < 0: - self._view.set_active_iter(self._model.get_iter_first()) - return - - def selection_changed(self, combo, data=None): - model = combo.get_model() - newitem = model[combo.get_active()][1] - self.emit_signal(Event.CategorySelectionChangedSignal(self,newitem)) - return - - def category_selected(self, category): - self._model.foreach(self._foreach_category_selected, - category) - self._update_selection() - - def _foreach_category_selected(self, model, path, iter, category): - cat = model[path][Column.CATEGORY] - if cat is category: - self._view.set_active_iter(iter) - return True - return False diff --git a/src/lib/Feed.py b/src/lib/Feed.py deleted file mode 100644 index dad60e1..0000000 --- a/src/lib/Feed.py +++ /dev/null @@ -1,368 +0,0 @@ -""" Feed.py - -Provides a module for managing feeds -""" -__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." -__license__ = """ -Straw is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -Straw 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 General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place - Suite 330, Boston, MA 02111-1307, USA. """ - - -import Event -import FeedItems -import FeedDataRouter -import Config -from error import log - -class Feed(object, Event.SignalEmitter): - "A Feed object stores information set by user about a RSS feed." - - DEFAULT = -1 - STATUS_IDLE = 0 - STATUS_POLLING = 1 - - __slots__ = ('_title', '_location', '_username', '_password', '_parsed', - '__save_fields', '_items', '_slots', - '_id', '_channel_description', - '_channel_title', '_channel_link', '_channel_copyright', - 'channel_lbd', 'channel_editor', 'channel_webmaster', - 'channel_creator','_error', '_process_status', 'router', 'sticky', '_parent', - '_items_stored', '_poll_freq', '_last_poll', '_n_items_unread') - - __save_fields = (('_title', ""), ('_location', ""), ('_username', ""), - ('_password', ""), ('_id', ""), - ('_channel_description', ""), ('_channel_title', ""), - ('_channel_link', ""), ('_channel_copyright', ""), - ('channel_creator', ""), ('_error', None), - ('_items_stored', DEFAULT), - ('_poll_freq', DEFAULT), - ('_last_poll', 0), - ('_n_items_unread', 0)) - - - # use one of the factory functions below instead of this directly - def __init__(self, title="", location="", username="", password=""): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.FeedInfoUpdatedSignal, - #Event.FeedUnreadChangedSignal, - #Event.FeedNumOfItemsStoredChangedSignal, - #Event.FeedPollFrequencyChangedSignal, - Event.FeedLastPollChangedSignal, - Event.FeedStatusChangedSignal, - Event.FeedErrorStatusChangedSignal, - Event.FeedPolledSignal, - Event.NewItemsSignal, - Event.ItemsAddedSignal, - Event.ItemReadSignal, - Event.ItemStickySignal, - Event.ItemDeletedSignal, - Event.AllItemsReadSignal, - Event.RefreshFeedDisplaySignal) - - self._title = title - self._channel_description = "" - self._channel_title = "" - self._channel_link = "" - self._channel_copyright = "" - self.channel_lbd = None - self.channel_editor = "" - self.channel_webmaster = "" - self.channel_creator = "" - self._location = location - self._username = username - self._password = password - self._parsed = None - self._items = FeedItems.FeedItems(self) - self._error = None - self._process_status = self.STATUS_IDLE - self.router = FeedDataRouter.FeedDataRouter(self) - self._parent = None - self._items_stored = Feed.DEFAULT - self._poll_freq = Feed.DEFAULT - self._last_poll = 0 - self._n_items_unread = 0 - return - - def __str__(self): - return "Feed '%s' from %s" % (self._title, self._location) - - @property - def id(self): - return self._id - - @apply - def parsed(): - doc = "A ParsedSummary object generated from the summary file" - def fget(self): - return self._parsed - def fset(self, parsed): - self._parsed = parsed - return property(**locals()) - - @apply - def title(): - doc = "The title of this Feed (as defined by user)" - def fget(self): - text = '' - if self._title: - text = self._title - return text - def fset(self, title): - if self._title != title: - self._title = title - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def access_info(): - doc = "A tuple of location, username, password" - def fget(self): - return (self._location, self._username, self._password) - def fset(self, (location,username,password)): - self._location = location - self._username = username - self._password = password - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def location(): - doc = "" - def fget(self): - return self._location - def fset(self, location): - if self._location != location: - self._location = location - self.emit_signal(Event.FeedInfoUpdateSignal(self)) - return property(**locals()) - - @apply - def channel_title(): - doc = "" - def fget(self): - text = '' - if self._channel_title: - text = self._channel_title - return text - def fset(self, t): - changed = self._channel_title != t - self._channel_title = t - if changed: - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def channel_description(): - doc = "" - def fget(self): - text = '' - if self._channel_description: - text = self._channel_description - return text - def fset(self, t): - changed = self._channel_description != t - self._channel_description = t - if changed: - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def channel_link(): - doc = "" - def fget(self): - return self._channel_link - def fset(self, t): - changed = self._channel_link != t - self._channel_link = t - if changed: - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def channel_copyright(): - doc = "" - def fget(self): - return self._channel_copyright - def fset(self, t): - changed = self._channel_copyright != t - self._channel_copyright = t - if changed: - self.emit_signal(Event.FeedInfoUpdatedSignal(self)) - return property(**locals()) - - @apply - def number_of_items_stored(): - doc = "" - def fget(self): - return self._items_stored - def fset(self, num=None): - if self._items_stored != num: - self._items_stored = num - self.emit_signal(Event.FeedNumOfItemsStoredChangedSignal(self)) - return property(**locals()) - - @apply - def poll_frequency(): - doc = "" - def fget(self): - return self._poll_freq - def fset(self, freq): - if self._poll_freq != freq: - self._poll_freq = freq - self.emit_signal(Event.FeedPollFrequencyChangedSignal(self)) - return property(**locals()) - - @apply - def last_poll(): - doc = "" - def fget(self): - return self._last_poll - def fset(self, time): - if self._last_poll != time: - self._last_poll = time - self.emit_signal(Event.FeedLastPollChangedSignal(self)) - return property(**locals()) - - @apply - def n_items_unread(): - doc = "" - def fget(self): - return self._n_items_unread - def fset(self, n): - if self._n_items_unread != n: - self._n_items_unread = n - self.emit_signal(Event.FeedUnreadChangedSignal(self)) - return property(**locals()) - - @apply - def error(): - doc = "" - def fget(self): - return self._error - def fset(self, error): - if self._error != error: - self._error = error - self.emit_signal(Event.FeedErrorStatusChangedSignal(self)) - return property(**locals()) - - @apply - def process_status(): - doc = "" - def fget(self): - return self._process_status - def fset(self, status): - if status != self._process_status: - self._process_status = status - self.emit_signal(Event.FeedStatusChangedSignal(self)) - return property(**locals()) - - @apply - def parent(): - doc = "" - def fget(self): - return self._parent - def fset(self, parent): - self._parent = parent - return property(**locals()) - - @property - def next_refresh(self): - """ return the feed's next refresh (time)""" - nr = None - if self._poll_freq == self.DEFAULT: - increment = Config.get_instance().poll_frequency - else: - increment = self._poll_freq - if increment > 0: - nr = self.last_poll + increment - return nr - - def dump(self): - fl = {} - for f, default in self.__save_fields: - fl[f] = self.__getattribute__(f) - return fl - - def undump(self, dict): - for f, default in self.__save_fields: - self.__setattr__(f, dict.get(f, default)) - return - - def poll_done(self): - self.emit_signal(Event.FeedPolledSignal(self)) - - def signal_new_items(self, items): - self.emit_signal(Event.NewItemsSignal(self, items)) - - def signal_added_items(self, items): - self.emit_signal(Event.ItemsAddedSignal(self, items)) - - def signal_deleted_item(self, item): - self.emit_signal(Event.ItemDeletedSignal(self, item)) - - def signal_refresh_display(self): - self.emit_signal(Event.RefreshFeedDisplaySignal(self)) - - def signal_all_items_read(self, items): - self.emit_signal(Event.AllItemsReadSignal(self, items)) - - def forward_signal(self, signal): - self.emit_signal(signal) - - def add_items(self, items): - return self._items.add_items(items) - - def restore_items(self, items): - return self._items.restore_items(items) - - def delete_all_items(self): - self.signal_deleted_item(self.items) - - def get_item_index(self, item): - return self._items.get_item_index(item) - - @property - def items(self): - return self._items.get_items() - - @property - def number_of_unread(self): - return self._n_items_unread - - @property - def number_of_items(self): - return self._items.number_of_items - - def mark_all_read(self): - return self._items.mark_all_read() - - def load_contents(self): - return self._items.load() - - def unload_contents(self): - self._items.unload() - - @classmethod - def create_new_feed(klass, title, location="", username="", password=""): - f = klass() - f._title = title - f._location = location - f._id = Config.get_instance().next_feed_id_seq() - f._username = username - f._password = password - return f - - @classmethod - def create_empty_feed(klass): - f = klass() - return f diff --git a/src/lib/FeedCategoryList.py b/src/lib/FeedCategoryList.py index af4e6a7..33c6418 100644 --- a/src/lib/FeedCategoryList.py +++ b/src/lib/FeedCategoryList.py @@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ -import Feed +import feeds from StringIO import StringIO import Config import Event -import FeedList import OPMLImport import error import locale @@ -50,13 +49,10 @@ class FeedCategoryList(object, Event.SignalEmitter): self._user_categories = [] self._pseudo_categories = (self._all_category, self._un_category) self._loading = False - self._feedlist = FeedList.get_instance() - self._feedlist.signal_connect(Event.FeedDeletedSignal, - self.feed_deleted) - self._feedlist.signal_connect(Event.FeedCreatedSignal, - self.feed_created) - self._feedlist.signal_connect(Event.FeedsImportedSignal, - self.feeds_imported) + self._feedlist = feeds.get_instance() + self._feedlist.connect('deleted', self.feed_deleted) + self._feedlist.connect('updated', self.feed_created) + self._feedlist.connect('imported', self.feeds_imported) def load_data(self): """Loads categories from config. @@ -146,17 +142,14 @@ class FeedCategoryList(object, Event.SignalEmitter): self.save_data() self.emit_signal(signal) - def feed_deleted(self, signal): + def feed_deleted(self, feedlist, feed): for c in self: try: - c.remove_feed(signal.feed) + c.remove_feed(feed) except ValueError: pass - def feed_created(self, signal): - value = signal.feed - category = signal.category - index = signal.index + def feed_created(self, feedlist, value, category, index): if category and category not in self.pseudo_categories: if index: category.insert_feed(index, value, False) @@ -166,11 +159,7 @@ class FeedCategoryList(object, Event.SignalEmitter): self.un_category.append_feed(value, False) self.all_category.append_feed(value, False) - def feeds_imported(self, signal): - category = signal.category - feeds = signal.feeds - from_sub = signal.from_sub - + def feeds_imported(self, feedlist, feeds, category, from_sub): if category and category not in self.pseudo_categories: category.extend_feed(feeds, from_sub) else: @@ -432,14 +421,14 @@ class FeedCategory(list, Event.SignalEmitter): return subfeeds = self.subscription.contents sfdict = dict(subfeeds) - feedlist = FeedList.get_instance() + feedlist = feeds.get_instance() current = dict([(feed.location, feed) for feed in self.feeds]) allfeeds = dict([(feed.location, feed) for feed in feedlist]) common, toadd, toremove = utils.listdiff(sfdict.keys(), current.keys()) existing, nonexisting, ignore = utils.listdiff( toadd, allfeeds.keys()) - newfeeds = [Feed.Feed.create_new_feed(sfdict[f], f) for f in nonexisting] + newfeeds = [feeds.Feed.create_new_feed(sfdict[f], f) for f in nonexisting] feedlist.extend(self, newfeeds, from_sub=True) # will call extend_feed self.extend_feed([allfeeds[f] for f in existing], True) diff --git a/src/lib/FeedItems.py b/src/lib/FeedItems.py deleted file mode 100644 index 8cb9dff..0000000 --- a/src/lib/FeedItems.py +++ /dev/null @@ -1,232 +0,0 @@ -""" FeedItems.py - -Straw module for handling items that belongs to a feed. This modules is -responsible for adding,cutting, and deleting items of the current feed. -""" -__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." -__license__ = """GNU General Public License - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program 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 General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place - Suite 330, Boston, MA 02111-1307, USA. """ - - -import QueueDict -import Config -import Feed -import Event -import ItemStore -import error -import locale - -class FeedItems(object): - def __init__(self, feed): - self._feed = feed - self._items = QueueDict.ItemQueue() - self._init_items() - config = Config.get_instance() - config.signal_connect(Event.ItemOrderChangedSignal, self.item_order_changed) - config.signal_connect(Event.NumberOfItemsStoredChangedSignal, self.items_stored_changed) - - def _init_items(self): - config = Config.get_instance() - self._items.clear() - self._items.sort_order = config.item_order - self._prefs_items_stored = config.number_of_items_stored - self._idseq = 0 - self._number_of_items = None - self._loaded = False - - - @apply - def number_of_unread(): - doc = "" - def fget(self): - return self._feed.n_items_unread - def fset(self, n): - self._feed.n_items_unread = n - return property(**locals()) - - @apply - def number_of_items(): - doc = "" - def fget(self): - if self._number_of_items: - return self._number_of_items - if self._loaded: - error.log("was loaded but number_of_items was None!") - return 0 - def fset(self, n): - self._number_of_items = n - return property(**locals()) - - def item_order_changed(self, event): - self._items.sort_order = event.sender.item_order - self._feed.signal_refresh_display() - - def items_stored_changed(self, event): - self._prefs_items_stored = event.sender.number_of_items_stored - self._feed.signal_refresh_display() - - def add_items(self, new_items): - cutpoint = self._get_cutpoint() - items = sorted(new_items, cmp=self._cmp) - newitems = [] - removed_items = [] - for x, item in enumerate(items): - if item in self._items: - continue - if not item.id: - self._idseq += 1 - item.id = self._idseq - item.feed = self._feed - if self._number_of_items < cutpoint: - self._items.append(item) - else: - # no point on increasing the number of items here since it's - # already over the cutpoint - ritem = self._items.replace(item) - removed_items.append(ritem) - newitems.append(item) - item.signal_connect(Event.ItemReadSignal, self.item_read) - item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal) - self._idseq = max(self._items.keys()) - self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen]) - self.number_of_items = len(self._items) - - if removed_items: - self._feed.signal_deleted_item(removed_items) - if newitems: - self._feed.signal_new_items(newitems) - self._feed.signal_refresh_display() - - def restore_items(self, items): - """ - Restores the given items in an ordered form - - # should we check for duplicates here? Or should that be done - # when new items arrive? - - """ - items = self._sort(items) - no_restore = [] - cutpoint = self._get_cutpoint() - for x,item in enumerate(items): - item.feed = self._feed - if item.sticky or x <= cutpoint: - item.signal_connect(Event.ItemReadSignal, self.item_read) - item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal) - self._items.append(item) - continue - item.clean_up() - no_restore.append(item) - self._idseq = max(self._items.keys()) - self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen]) - self.number_of_items = len(self._items) - if no_restore: - self._feed.signal_deleted_item(no_restore) - - def _disconnect_item_signals(self, item): - item.signal_disconnect(Event.ItemReadSignal, self.item_read) - item.signal_disconnect(Event.ItemStickySignal, self._feed.forward_signal) - - def item_read(self, signal): - if signal.sender.seen: - change = -1 - else: - change = 1 - self.number_of_unread = self.number_of_unread + change - self._feed.forward_signal(signal) - - def get_items(self): - if not self._loaded: - self.load() - return self._items.values() - - def get_item_index(self, item): - if not self._loaded: - self.load() - idx = self._items.index(item.id) - return idx - - def mark_all_read(self): - if not self._loaded: - self.load() - keys = self._items.keys() - changed = [(keys.index(key), value) for key, value in self._items if value.set_seen_quiet()] - self.number_of_unread = 0 - self._feed.signal_all_items_read(changed) - return - - def load(self): - """ - Loads and restores the items from the data store - - """ - if self._loaded: - return False - itemstore = ItemStore.get_instance() - items = itemstore.read_feed_items(self._feed) - if items: - #error.log("load ", self._feed.title, ", number of items: ", len(items), ", number of unread before restore: ", self.number_of_unread) - self.restore_items(items) - self._loaded = True - else: - #print "No items, not loading ", self._feed.title - self._loaded = False - return self._loaded - - def unload(self): - """ - Unloads the items by disconnecting the signals and reinitialising the - instance variables - - TODO: unload seems to lose some circular references. garbage collector - will find them, though, so maybe it's not a problem. - """ - if not self._loaded: - return - for key, item in self._items: - self._disconnect_item_signals(item) - self._init_items() - - def _get_cutpoint(self): - """ - Returns the current cutpoint - - """ - if self._feed.number_of_items_stored == Feed.Feed.DEFAULT: - cutpoint = self._prefs_items_stored - else: - cutpoint = self._feed.number_of_items_stored - return cutpoint - - def _sort(self, items): - """ - Sorts the given items according to the sort order - """ - items.sort(self._cmp) - if self._items.sort_order: - items.reverse() - return items - - def _cmp(self, a, b): - """ - Comparator method to compare items based on the item's pub_date attribute - - If an item doesn't have a pub_date, it uses title and prioritizes the - unread items - """ - try: - return cmp(a.pub_date, b.pub_date) - except AttributeError: - return locale.strcoll(a.title, b.title) and not a.seen or not b.seen diff --git a/src/lib/FeedList.py b/src/lib/FeedList.py deleted file mode 100644 index e80551e..0000000 --- a/src/lib/FeedList.py +++ /dev/null @@ -1,194 +0,0 @@ -""" FeedList.py - -Provides a container for handling the feeds -""" -__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." -__license__ = """GNU General Public License - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program 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 General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place - Suite 330, Boston, MA 02111-1307, USA. """ - - -import Event -import Feed -import Config -import locale -import utils - -class FeedList(list, Event.SignalEmitter): - def __init__(self, init_seq = []): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.FeedsChangedSignal, - Event.FeedDeletedSignal, - Event.FeedCreatedSignal, - Event.AllItemsReadSignal, - Event.ItemReadSignal, - Event.ItemsAddedSignal, - Event.FeedPolledSignal, - Event.FeedStatusChangedSignal, - Event.FeedErrorStatusChangedSignal, - Event.FeedsImportedSignal, - Event.FeedDetailChangedSignal) - self._loading = False - - def load_data(self): - def _load(feeds, parent): - for df in feeds: - if isinstance(df, list): - #fc = straw.FeedCategory() - #fc.undump(df[0]) - #self.append(parent, fc) - _load(df[1:], parent) - else: - f = Feed.Feed.create_empty_feed() - f.undump(df) - self.append(parent, f) - self._loading = True - feeds = Config.get_instance().feeds - if feeds is not None: - _load(feeds, None) - self._loading = False - self.emit_signal(Event.FeedsChangedSignal(self)) - - def _forward_signal(self, signal): - self.emit_signal(signal) - - def connect_signals(self, ob): - #ob.signal_connect(Event.FeedsChangedSignal, - # self.feeds_changed, True) - ob.signal_connect(Event.FeedDetailChangedSignal, - self.feed_detail_changed, True) - - # these signals are forwarded so that listeners who just want to - # listen for a specific event regardless of what feed it came from can - # just connect to this feedlist instead of connecting to the - # individual feeds. - ob.signal_connect(Event.AllItemsReadSignal, self._forward_signal) - ob.signal_connect(Event.ItemReadSignal, self._forward_signal) - ob.signal_connect(Event.ItemsAddedSignal, self._forward_signal) - ob.signal_connect(Event.FeedPolledSignal, self._forward_signal) - ob.signal_connect(Event.FeedStatusChangedSignal, self._forward_signal) - ob.signal_connect(Event.FeedErrorStatusChangedSignal, self._forward_signal) - - def __setitem__(self, key, value): - list.__setitem__(self, key, value) - self.connect_signals(value) - self.save_feeds_and_notify(True) - - def extend(self, parent, values, from_sub=False): - list.extend(self, values) - for f in values: - f.parent = parent - self.connect_signals(f) - self.save_feeds_and_notify( - True, signal=Event.FeedsImportedSignal(self, values, parent, from_sub)) - - def append(self, parent, value): - list.append(self, value) - value.parent = parent - self.connect_signals(value) - self.save_feeds_and_notify( - True, signal=Event.FeedCreatedSignal(self, value, parent)) - - def insert(self, index, parent, value): - list.insert(self, index, value) - value.parent = parent - self.connect_signals(value) - self.save_feeds_and_notify( - True, signal=Event.FeedCreatedSignal(self, value, parent, index)) - - def index(self, feed): - return list.index(self, feed) - - def reorder(self, move, delta): - k = self[:] - move = list(move) - move.sort() - if delta > 0: - move.reverse() - if move[0] == 0 and delta < 0 or move[-1] == (len(self) - 1) and delta > 0: - return - for m in move: - k[m + delta], k[m] = k[m], k[m + delta] - for i in range(len(k)): - list.__setitem__(self, i, k[i]) - self.save_feeds_and_notify(True) - - def __delitem__(self, value): - feed = self[value] - list.__delitem__(self, value) - feed.delete_all_items() - self.save_feeds_and_notify( - True, signal=Event.FeedDeletedSignal(self, feed)) - - def save_feeds_and_notify(self, notify, signal=None): - if not self._loading: - config = Config.get_instance() - config.feeds = [f.dump() for f in self] - if notify: - if not signal: - signal = Event.FeedsChangedSignal(self) - self.emit_signal(signal) - return - - def feeds_changed(self, signal, notify): - self.save_feeds_and_notify(notify, signal) - - def feed_detail_changed(self, signal, notify): - self.save_feeds_and_notify(notify, signal) - - def _sort_dsu(self, seq): - aux_list = [(x.title, x) for x in seq] - aux_list.sort(lambda a,b:locale.strcoll(a[0],b[0])) - return [x[1] for x in aux_list] - - def sort(self, indices = None): - if not indices or len(indices) == 1: - self[:] = self._sort_dsu(self) - else: - items = self._sort_dsu(indices) - for i,x in enumerate(items): - list.__setitem__(self, indices[i], items[i]) - self.save_feeds_and_notify(True) - - def __hash__(self): - h = 0 - for item in self: - h ^= hash(item) - return h - - def get_feed_with_id(self, id): - for f in self.flatten_list(): - if f.id == id: - return f - return None - - def flatten_list(self, ob=None): - if ob is None: - ob = self - l = [] - for o in ob: - if isinstance(o, list): - l = l + self.flatten_list(o) - else: - l.append(o) - return l - -feedlist_instance = None - -def get_instance(): - global feedlist_instance - if feedlist_instance is None: - feedlist_instance = FeedList() - return feedlist_instance - diff --git a/src/lib/FeedListView.py b/src/lib/FeedListView.py index 442c5c1..ea4c6b0 100644 --- a/src/lib/FeedListView.py +++ b/src/lib/FeedListView.py @@ -28,8 +28,7 @@ import gtk import pango import FeedCategoryList import FeedPropertiesDialog -import FeedList -import Feed +import feeds import Event import dialogs import Config @@ -363,20 +362,20 @@ class FeedsPresenter(MVP.BasicPresenter): return def _init_signals(self): - flist = FeedList.get_instance() - flist.signal_connect(Event.ItemReadSignal, - self._feed_item_read) - flist.signal_connect(Event.AllItemsReadSignal, - self._feed_all_items_read) - flist.signal_connect(Event.FeedsChangedSignal, - self._feeds_changed) - flist.signal_connect(Event.FeedDetailChangedSignal, - self._feed_detail_changed) - fclist = FeedCategoryList.get_instance() - fclist.signal_connect(Event.FeedCategorySortedSignal, - self._feeds_sorted_cb) - fclist.signal_connect(Event.FeedCategoryChangedSignal, - self._fcategory_changed_cb) + flist = feeds.get_instance() + #flist.signal_connect(Event.ItemReadSignal, + # self._feed_item_read) + #flist.signal_connect(Event.AllItemsReadSignal, + # self._feed_all_items_read) + #flist.signal_connect(Event.FeedsChangedSignal, + # self._feeds_changed) + #flist.signal_connect(Event.FeedDetailChangedSignal, + # self._feed_detail_changed) + #fclist = FeedCategoryList.get_instance() + #fclist.signal_connect(Event.FeedCategorySortedSignal, + # self._feeds_sorted_cb) + #fclist.signal_connect(Event.FeedCategoryChangedSignal, + # self._fcategory_changed_cb) def select_next_feed(self, unread=False): self.view.select_next_feed(unread) @@ -645,7 +644,7 @@ class TreeNodeAdapter: # Fyi, gtk.Widget is abstract that is why we need a subclass to do # this for us. try: - if self.obj.process_status is not Feed.Feed.STATUS_IDLE: + if self.obj.process_status is not feeds.Feed.STATUS_IDLE: return widget.render_icon(gtk.STOCK_EXECUTE. gtk.ICON_SIZE_MENU) elif self.obj.error: return widget.render_icon(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) @@ -656,7 +655,7 @@ class TreeNodeAdapter: @property def feed(self): ''' An alias to a Feed object ''' - if not isinstance(self.obj, Feed.Feed): + if not isinstance(self.obj, feeds.Feed): raise TypeError, _("object is not of a Feed") return self.obj diff --git a/src/lib/FeedPropertiesDialog.py b/src/lib/FeedPropertiesDialog.py index 1df41be..e6a78b8 100644 --- a/src/lib/FeedPropertiesDialog.py +++ b/src/lib/FeedPropertiesDialog.py @@ -26,7 +26,7 @@ import gtk import gobject import time import utils -import Feed +import feeds import Config import FeedCategoryList import SummaryParser @@ -123,7 +123,7 @@ class FeedPropertyView(MVP.GladeView): self._location.set_text(self._feed.access_info[0]) config = Config.get_instance() - if self._feed.poll_frequency == Feed.Feed.DEFAULT: + if self._feed.poll_frequency == feeds.Feed.DEFAULT: freq = config.poll_frequency fdefault = True self._refresh_keep = True @@ -134,7 +134,7 @@ class FeedPropertyView(MVP.GladeView): self._refresh_spin.set_value(float(freq / 60)) self._refresh_spin.set_sensitive(not fdefault) self._refresh_default_check.set_active(fdefault) - if self._feed.number_of_items_stored == Feed.Feed.DEFAULT: + if self._feed.number_of_items_stored == feeds.Feed.DEFAULT: nitems = config.number_of_items_stored nidefault = True self._articles_keep = True @@ -305,7 +305,7 @@ class FeedPropertyView(MVP.GladeView): return isactive = widget.get_active() if isactive: - self._presenter.set_poll_frequency(Feed.Feed.DEFAULT) + self._presenter.set_poll_frequency(feeds.Feed.DEFAULT) self._refresh_spin.set_value(float(Config.get_instance().poll_frequency / 60)) else: self._presenter.set_poll_frequency(self._refresh_spin.get_value_as_int() * 60) @@ -318,7 +318,7 @@ class FeedPropertyView(MVP.GladeView): return isactive = widget.get_active() if isactive: - self._presenter.set_items_stored(Feed.Feed.DEFAULT) + self._presenter.set_items_stored(feeds.Feed.DEFAULT) self._articles_spin.set_value(float(Config.get_instance().number_of_items_stored)) else: self._presenter.set_items_stored(self._articles_spin.get_value_as_int()) diff --git a/src/lib/Find.py b/src/lib/Find.py index 9ad05d9..6b0fa1b 100644 --- a/src/lib/Find.py +++ b/src/lib/Find.py @@ -28,7 +28,7 @@ import pango import time import Event import utils -import FeedList +import feeds import MessageManager from ItemList import ItemsView, ItemsPresenter import MVP @@ -74,7 +74,7 @@ class FindLimit(object): class Finder: def __init__(self): self._stack = list() - self._feedlist = FeedList.get_instance() + self._feedlist = feeds.get_instance() def find_matching(self, limit, items): if not len(items): diff --git a/src/lib/ItemStore.py b/src/lib/ItemStore.py index 529c978..1730f3e 100644 --- a/src/lib/ItemStore.py +++ b/src/lib/ItemStore.py @@ -33,7 +33,7 @@ except ImportError: import Event import SummaryItem -import FeedList +import feeds import ImageCache from MainloopManager import MainloopManager @@ -478,7 +478,7 @@ class ItemsAddedAction: def doit(self, db): db.add_items(self._feed, self._items) -class DeleteItemAction: +class DeleteItemsAction: def __init__(self, feed, items): self._feed = feed self._items = items @@ -504,43 +504,42 @@ class ImageCountChangedAction: class ItemStore: def __init__(self, dbhome): - feedlist = FeedList.get_instance() + feedlist = feeds.get_instance() self._db = MyDB(DATABASE_FILE_NAME, dbhome, create = 1) self.connect_signals() - feedlist.signal_connect(Event.FeedCreatedSignal, - self._feed_created_cb) - feedlist.signal_connect(Event.FeedDeletedSignal, - self._feed_deleted_cb) + feedlist.connect('updated', self._feed_created_cb) + feedlist.connect('deleted', self._feed_deleted_cb) ImageCache.cache.signal_connect(Event.ImageUpdatedSignal, self.image_updated) self._stop = False self._action_queue = [] - def _feed_created_cb(self, signal): - self._connect_feed_signals(signal.feed) + def _feed_created_cb(self, flist, feed, *args): + self._connect_feed_signals(feed) - def _feed_deleted_cb(self, signal): - self._disconnect_feed_signals(signal.feed) + def _feed_deleted_cb(self, flist, feed): + self._disconnect_feed_signals(feed) def connect_signals(self): - flist = FeedList.get_instance().flatten_list() + flist = feeds.get_instance().flatten_list() for f in flist: self._connect_feed_signals(f) def _connect_feed_signals(self, feed): - feed.signal_connect(Event.NewItemsSignal, self.items_added) - feed.signal_connect(Event.ItemReadSignal, self.item_modified) - feed.signal_connect(Event.ItemStickySignal, self.item_modified) - feed.signal_connect(Event.AllItemsReadSignal, self.all_items_read) - feed.signal_connect(Event.ItemDeletedSignal, self.item_deleted) + feed.connect('items-updated', self.items_added) + feed.connect('items-read', self.all_items_read) + feed.connect('items-deleted', self.items_deleted) + # XXX + #feed.signal_connect(Event.ItemReadSignal, self.item_modified) + #feed.signal_connect(Event.ItemStickySignal, self.item_modified) def _disconnect_feed_signals(self, feed): - feed.signal_disconnect(Event.NewItemsSignal, self.items_added) - feed.signal_disconnect(Event.ItemReadSignal, self.item_modified) - feed.signal_disconnect(Event.ItemStickySignal, self.item_modified) - feed.signal_disconnect(Event.AllItemsReadSignal, self.all_items_read) - feed.signal_disconnect(Event.ItemDeletedSignal, self.item_deleted) - + #feed.signal_disconnect(Event.NewItemsSignal, self.items_added) + #feed.signal_disconnect(Event.ItemReadSignal, self.item_modified) + #feed.signal_disconnect(Event.ItemStickySignal, self.item_modified) + #feed.signal_disconnect(Event.AllItemsReadSignal, self.all_items_read) + #feed.signal_disconnect(Event.ItemDeletedSignal, self.item_deleted) + pass def modify_item(self, item): self._action_queue.append(ModifyItemAction(item)) @@ -553,19 +552,17 @@ class ItemStore: def read_image(self, url): return self._db.get_image_data(url) - def item_deleted(self, signal): - self._action_queue.append(DeleteItemAction(signal.sender, signal.item)) + def items_deleted(self, feed, items): + self._action_queue.append(DeleteItemsAction(feed, items)) def item_modified(self, signal): self.modify_item(signal.item) - def all_items_read(self, signal): - self._action_queue.append(ModifyItemsAction( - [item for index, item in signal.changed])) + def all_items_read(self, feed, items): + self._action_queue.append(ModifyItemsAction(items)) - def items_added(self, signal): - self._action_queue.append( - ItemsAddedAction(signal.sender, signal.items)) + def items_added(self, feed, items): + self._action_queue.append(ItemsAddedAction(feed, items)) def read_feed_items(self, feed): return self._db.get_feed_items(feed) diff --git a/src/lib/OPMLImport.py b/src/lib/OPMLImport.py index b22d94f..763c99e 100644 --- a/src/lib/OPMLImport.py +++ b/src/lib/OPMLImport.py @@ -18,8 +18,7 @@ Place - Suite 330, Boston, MA 02111-1307, USA. """ import OPML -import Feed -import FeedList +import feeds class BlogListEntry(object): __slots__ = ('text', 'url') @@ -87,12 +86,12 @@ def read(stream): return ret def import_opml(filename,category=None): - feedlist = FeedList.get_instance() + feedlist = feeds.get_instance() fstream = open (filename) opml = read(fstream) if not opml: return feeds = [feed.access_info[0] for feed in feedlist] - newitems = [Feed.Feed.create_new_feed(b.text, b.url) for b in opml if b.url not in feeds] + newitems = [feeds.Feed.create_new_feed(b.text, b.url) for b in opml if b.url not in feeds] feedlist.extend(category, newitems) return diff --git a/src/lib/PollManager.py b/src/lib/PollManager.py index 3e75cdf..1093699 100644 --- a/src/lib/PollManager.py +++ b/src/lib/PollManager.py @@ -19,13 +19,12 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import Config -import FeedList +import feeds import Event from MainloopManager import MainloopManager import MessageManager import NetworkConstants import URLFetch -import Feed import FeedCategoryList import SummaryParser import utils @@ -53,11 +52,12 @@ class PollManager: def __init__(self): self._config = Config.get_instance() self._mmgr = MessageManager.get_instance() - self._feedlist = FeedList.get_instance() + self._feedlist = feeds.get_instance() self._config.signal_connect(Event.PollFrequencyChangedSignal, self.poll_changed) self._config.signal_connect(Event.OfflineModeChangedSignal, self.offline_changed) - self._feedlist.signal_connect(Event.FeedsChangedSignal, self.feeds_changed) + + self._feedlist.connect('changed', self.feeds_changed) self._is_offline = self._config.offline self._poll_frequency = self._config.poll_frequency @@ -73,7 +73,7 @@ class PollManager: self._is_offline = self._config.offline def feeds_changed(self, *args): - self._feedlist = FeedList.get_instance() + self._feedlist = feeds.get_instance() self._feeds = self._feedlist.flatten_list() def start_polling_loop(self): @@ -101,7 +101,7 @@ class PollManager: try: context = {} config_pf = self._config.poll_frequency - feed_use_default = Feed.Feed.DEFAULT + feed_use_default = feeds.Feed.DEFAULT self.poll([feed for feed, timediff, fpf in [(feed, now - feed.last_poll, feed.poll_frequency) for feed in self._feeds] diff --git a/src/lib/PreferencesDialog.py b/src/lib/PreferencesDialog.py index 4ee0583..0981b5f 100644 --- a/src/lib/PreferencesDialog.py +++ b/src/lib/PreferencesDialog.py @@ -30,7 +30,7 @@ import MVP from weakref import WeakKeyDictionary import Event import FeedCategoryList -import FeedList +import feeds import Config import ValueMonitor @@ -302,7 +302,7 @@ class FeedListView(MVP.WidgetView): def _populate_treemodel(self): treemodel = self._widget.get_model() treemodel.clear() - feedlist = FeedList.get_instance() + feedlist = feeds.get_instance() if self._category is None: return category_feeds = self._category.feeds enabletoggle = not isinstance( @@ -381,7 +381,7 @@ class CategoriesTab: self._categories_presenter.signal_connect( CategorySelectionChangedSignal, self._category_changed) - feedmodel = FeedList.get_instance() + feedmodel = feeds.get_instance() self._feeds_presenter = FeedListPresenter() self._feeds_presenter.model = feedmodel self._feeds_presenter.view = FeedListView( diff --git a/src/lib/QueueDict.py b/src/lib/QueueDict.py deleted file mode 100644 index 693c688..0000000 --- a/src/lib/QueueDict.py +++ /dev/null @@ -1,109 +0,0 @@ -""" QueueDict.py - -Sorted container of SummaryItems -""" -__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc." -__license__ = """GNU General Public License - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program 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 General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place - Suite 330, Boston, MA 02111-1307, USA. """ - -from bisect import insort - -class ItemQueue(dict): - def __init__(self): - dict.__init__(self) - self._reverse = 0 - self._keys = list() - - def __delitem__(self, key): - try: - self._keys.remove(key) - dict.__delitem__(self, key) - except KeyError: - raise "FeedQD: No Such Key %d" % key - - def __iter__(self): - return self.iteritems() - - def __contains__(self, item): - return item in self.itervalues() - - def __setitem__(self, key, value): - self.append(value) - - def _set_reverse(self, reverse): - self._reverse = reverse - - def _get_reverse(self): - return self._reverse - - sort_order = property(_get_reverse, _set_reverse) - - def append(self, item): - if item.id not in self._keys: - insort(self._keys, item.id) - dict.__setitem__(self, item.id, item) - - def replace(self, item): - self.append(item) - k = self.keys() - item = None - for iid in reversed(k): - item = self[iid] - if item.sticky: - continue - else: - del self[iid] - break - return item - - def clear(self): - dict.clear(self) - del self._keys[:] - - def keys(self): - if self._reverse: - return sorted(self._keys, reverse=True) - return self._keys - - def values(self): - return map(self.get, self.keys()) - - def iteritems(self): - def sorteditems(): - for k in self.keys(): - yield (k, self[k]) - return sorteditems() - - def iterkeys(self): - def sortedkeys(): - for k in self.keys(): - yield k - return sortedkeys() - - def itervalues(self): - def sortedvalues(): - for k in self.keys(): - yield self[k] - return sortedvalues() - - def items(self): - return [item for item in self.iteritems()] - - def index(self, key): - try: - idx = self.keys().index(key) - except (IndexError, ValueError): - idx = None - return idx diff --git a/src/lib/SummaryItem.py b/src/lib/SummaryItem.py index e29a50d..56e05f8 100644 --- a/src/lib/SummaryItem.py +++ b/src/lib/SummaryItem.py @@ -16,13 +16,11 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ +import gobject import time import locale -import Event -import ImageCache - -class SummaryItem(object, Event.SignalEmitter): +class SummaryItem(gobject.GObject): __slots__ = ('title', 'link', 'description', 'guid', 'pub_date', 'source', '_images', '_seen', '_id', 'feed', '_slots', 'fm_license', 'fm_changes', 'creator', 'license_urls', @@ -34,9 +32,19 @@ class SummaryItem(object, Event.SignalEmitter): _searchable_fields = ('title', 'description', 'fm_license', 'fm_changes', 'creator', 'contributors') - def __init__(self): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.ItemReadSignal, Event.ItemStickySignal) + __gsignals__ = { + 'read' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + #'unread' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'sticky' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) + } + + def __init__(self, imageCache=None): + gobject.GObject.__init__(self) + self.imageCache = imageCache + if not self.imageCache: + import ImageCache + self.imageCache = ImageCache.cache + self.title = None self.link = None self.description = None @@ -77,7 +85,7 @@ class SummaryItem(object, Event.SignalEmitter): def fset(self, seen = True): if self._seen != seen: self._seen = seen - self.emit_signal(Event.ItemReadSignal(self)) + self.emit('read') return property(**locals()) @apply @@ -88,7 +96,7 @@ class SummaryItem(object, Event.SignalEmitter): def fset(self, sticky): if self._sticky != sticky: self._sticky = sticky - self.emit_signal(Event.ItemStickySignal(self)) + self.emit('sticky') return property(**locals()) @apply @@ -101,8 +109,8 @@ class SummaryItem(object, Event.SignalEmitter): return property(**locals()) def _add_image(self, image_name, restore): - ImageCache.cache.add_refer(image_name, restore, self) - self._images[image_name] = ImageCache.cache[image_name] + self.imageCache.add_refer(image_name, restore, self) + self._images[image_name] = self.imageCache.cache[image_name] def add_image(self, image_name): self._add_image(image_name, restore = False) @@ -123,6 +131,7 @@ class SummaryItem(object, Event.SignalEmitter): return self._images.keys() def set_seen_quiet(self, seen = 1): + print "XXX: DEPRECATE / FIX" old = self._seen self._seen = seen return seen != old @@ -141,4 +150,4 @@ class SummaryItem(object, Event.SignalEmitter): def clean_up(self): for image in self._images: - ImageCache.cache.remove_refer(image) + self.imageCache.remove_refer(image) diff --git a/src/lib/dialogs.py b/src/lib/dialogs.py index 2d7304c..4a493d6 100644 --- a/src/lib/dialogs.py +++ b/src/lib/dialogs.py @@ -25,7 +25,6 @@ import gtk import gnome import error import FeedCategoryList -import FeedList import OPMLExport import OPMLImport import utils diff --git a/src/lib/feeds.py b/src/lib/feeds.py new file mode 100644 index 0000000..a483c30 --- /dev/null +++ b/src/lib/feeds.py @@ -0,0 +1,579 @@ +import locale +import gobject +import FeedDataRouter +import Config +from error import log + +class FeedList(gobject.GObject): + + __gsignals__ = { + 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'updated' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_INT,)), + 'imported' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, + gobject.TYPE_BOOLEAN,)), + 'deleted' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + } + + def __init__(self, init_seq = []): + gobject.GObject.__init__(self) + self.feedlist = [] + #self.initialize_slots(Event.FeedsChangedSignal, + # Event.FeedDeletedSignal, + # Event.FeedCreatedSignal, + # Event.AllItemsReadSignal, + # Event.ItemReadSignal, + # Event.ItemsAddedSignal, + # Event.FeedPolledSignal, + # Event.FeedStatusChangedSignal, + # Event.FeedErrorStatusChangedSignal, + # Event.FeedsImportedSignal, + # Event.FeedDetailChangedSignal) + self._loading = False + + def __iter__(self): + return iter(self.feedlist) + + def load_data(self): + def _load(feedlist, parent): + for df in feedlist: + if isinstance(df, list): + #fc = straw.FeedCategory() + #fc.undump(df[0]) + #self.append(parent, fc) + _load(df[1:], parent) + else: + f = Feed.create_empty_feed() + f.undump(df) + self.append(parent, f) + + self._loading = True + feedlist = Config.get_instance().feeds + if feedlist: + _load(feedlist, None) + self._loading = False + self.emit('changed') + + def _forward_signal(self, signal): + self.emit_signal(signal) + + def connect_signals(self, ob): + ob.connect('info-updated', self.feed_detail_changed) + + # these signals are forwarded so that listeners who just want to + # listen for a specific event regardless of what feed it came from can + # just connect to this feedlist instead of connecting to the + # individual feeds. + #ob.signal_connect(Event.AllItemsReadSignal, self._forward_signal) + #ob.signal_connect(Event.ItemReadSignal, self._forward_signal) + #ob.signal_connect(Event.ItemsAddedSignal, self._forward_signal) + #ob.signal_connect(Event.FeedPolledSignal, self._forward_signal) + #ob.signal_connect(Event.FeedStatusChangedSignal, self._forward_signal) + #ob.signal_connect(Event.FeedErrorStatusChangedSignal, self._forward_signal) + + def __setitem__(self, key, value): + self.feedlist.__setitem__(key, value) + self.connect_signals(value) + self.save_feeds_and_notify(True) + + def extend(self, parent, values, from_sub=False): + list.extend(self.feedlist, values) + for f in values: + f.parent = parent + self.connect_signals(f) + self.save_feeds() + self.emit('imported', values, parent, from_sub) + + def append(self, parent, value): + self.feedlist.append(value) + value.parent = parent + self.connect_signals(value) + self.save_feeds() + self.emit('updated', value, parent, -1) + + def insert(self, index, parent, value): + self.feedlist.insert(index, value) + value.parent = parent + self.connect_signals(value) + self.save_feeds() + self.emit('updated', value, parent, index) + + def index(self, feed): + return self.feedlist.index(feed) + + def reorder(self, move, delta): + k = self.feedlist[:] + move = list(move) + move.sort() + if delta > 0: + move.reverse() + if move[0] == 0 and delta < 0 or move[-1] == (len(self.feedlist) - 1) and delta > 0: + return + for m in move: + k[m + delta], k[m] = k[m], k[m + delta] + for i in range(len(k)): + list.__setitem__(self.feedlist, i, k[i]) + self.save_feeds() + self.emit('changed') + + def __delitem__(self, value): + feed = self.feedlist[value] + list.__delitem__(self.feedlist, value) + feed.delete_all_items() + self.save_feeds() + self.emit('deleted', feed) + + def save_feeds(self): + if not self._loading: + config = Config.get_instance() + config.feeds = [f.dump() for f in self.feedlist] + return + + def feed_detail_changed(self, feed): + self.save_feeds() + self.emit('changed') # XXXX send the feed as well? + + def _sort_dsu(self, seq): + aux_list = [(x.title, x) for x in seq] + aux_list.sort(lambda a,b:locale.strcoll(a[0],b[0])) + return [x[1] for x in aux_list] + + def sort(self, indices = None): + if not indices or len(indices) == 1: + self[:] = self._sort_dsu(self) + else: + items = self._sort_dsu(indices) + for i,x in enumerate(items): + list.__setitem__(self, indices[i], items[i]) + self.save_feeds() + self.emit('changed') + + def __hash__(self): + h = 0 + for item in self: + h ^= hash(item) + return h + + def get_feed_with_id(self, id): + for f in self.flatten_list(): + if f.id == id: + return f + return None + + def flatten_list(self, ob=None): + if ob is None: + ob = self.feedlist + l = [] + for o in ob: + if isinstance(o, list): + l = l + self.flatten_list(o) + else: + l.append(o) + return l + +feedlist_instance = None + +def get_instance(): + global feedlist_instance + if feedlist_instance is None: + feedlist_instance = FeedList() + return feedlist_instance + +class Feed(gobject.GObject): + "A Feed object stores information set by user about a RSS feed." + + DEFAULT = -1 + STATUS_IDLE = 0 + STATUS_POLLING = 1 + + __slots__ = ('_title', '_location', '_username', '_password', '_parsed', + '__save_fields', '_items', '_slots', + '_id', '_channel_description', + '_channel_title', '_channel_link', '_channel_copyright', + 'channel_lbd', 'channel_editor', 'channel_webmaster', + 'channel_creator','_error', '_process_status', 'router', 'sticky', '_parent', + '_items_stored', '_poll_freq', '_last_poll', '_n_items_unread') + + __save_fields = (('_title', ""), ('_location', ""), ('_username', ""), + ('_password', ""), ('_id', ""), + ('_channel_description', ""), ('_channel_title', ""), + ('_channel_link', ""), ('_channel_copyright', ""), + ('channel_creator', ""), ('_error', None), + ('_items_stored', DEFAULT), + ('_poll_freq', DEFAULT), + ('_last_poll', 0), + ('_n_items_unread', 0)) + + __gsignals__ = { + 'info-updated' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'error-occurred' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'process-status-changed' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, ()), + 'poll-done' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'items-updated' :(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'items-read' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'items-deleted' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'item-order-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,()) + } + + + # use one of the factory functions below instead of this directly + def __init__(self, title="", location="", username="", password=""): + gobject.GObject.__init__(self) + self._title = title + self._channel_description = "" + self._channel_title = "" + self._channel_link = "" + self._channel_copyright = "" + self.channel_lbd = None + self.channel_editor = "" + self.channel_webmaster = "" + self.channel_creator = "" + self._location = location + self._username = username + self._password = password + self._parsed = None + self._error = None + self._process_status = self.STATUS_IDLE + self.router = FeedDataRouter.FeedDataRouter(self) + self._parent = None + self._items_stored = Feed.DEFAULT + self._poll_freq = Feed.DEFAULT + self._last_poll = 0 + self._n_items_unread = 0 + self._items = None + self._items_loaded = False + return + + def __str__(self): + return "Feed '%s' from %s" % (self._title, self._location) + + @property + def id(self): + return self._id + + @apply + def parsed(): + doc = "A ParsedSummary object generated from the summary file" + def fget(self): + return self._parsed + def fset(self, parsed): + self._parsed = parsed + return property(**locals()) + + @apply + def title(): + doc = "The title of this Feed (as defined by user)" + def fget(self): + text = '' + if self._title: + text = self._title + return text + def fset(self, title): + if self._title != title: + self._title = title + self.emit('info-updated') + return property(**locals()) + + @apply + def access_info(): + doc = "A tuple of location, username, password" + def fget(self): + return (self._location, self._username, self._password) + def fset(self, (location,username,password)): + self._location = location + self._username = username + self._password = password + self.emit('info-updated') + return property(**locals()) + + @apply + def location(): + doc = "" + def fget(self): + return self._location + def fset(self, location): + if self._location != location: + self._location = location + self.emit('info-updated') + return property(**locals()) + + @apply + def channel_title(): + doc = "" + def fget(self): + text = '' + if self._channel_title: + text = self._channel_title + return text + def fset(self, t): + changed = self._channel_title != t + self._channel_title = t + if changed: + self.emit('info-updated') + return property(**locals()) + + @apply + def channel_description(): + doc = "" + def fget(self): + text = '' + if self._channel_description: + text = self._channel_description + return text + def fset(self, t): + changed = self._channel_description != t + self._channel_description = t + if changed: + self.emit('info-updated') + return property(**locals()) + + @apply + def channel_link(): + doc = "" + def fget(self): + return self._channel_link + def fset(self, t): + changed = self._channel_link != t + self._channel_link = t + if changed: + self.emit('info-updated') + return property(**locals()) + + @apply + def channel_copyright(): + doc = "" + def fget(self): + return self._channel_copyright + def fset(self, t): + changed = self._channel_copyright != t + self._channel_copyright = t + if changed: + self.emit('info-updated') + return property(**locals()) + + @apply + def number_of_items_stored(): + doc = "" + def fget(self): + return self._items_stored + def fset(self, num=None): + if self._items_stored != num: + self._items_stored = num + return property(**locals()) + + @apply + def poll_frequency(): + doc = "" + def fget(self): + return self._poll_freq + def fset(self, freq): + if self._poll_freq != freq: + self._poll_freq = freq + return property(**locals()) + + @apply + def last_poll(): + doc = "" + def fget(self): + return self._last_poll + def fset(self, time): + if self._last_poll != time: + self._last_poll = time + return property(**locals()) + + @apply + def n_items_unread(): + doc = "" + def fget(self): + return self._n_items_unread + def fset(self, n): + if self._n_items_unread != n: + self._n_items_unread = n + return property(**locals()) + + @apply + def error(): + doc = "" + def fget(self): + return self._error + def fset(self, error): + if self._error != error: + self._error = error + self.emit('error-occurred') + return property(**locals()) + + @apply + def process_status(): + doc = "" + def fget(self): + return self._process_status + def fset(self, status): + if status != self._process_status: + self._process_status = status + self.emit('process_status_changed') + return property(**locals()) + + @apply + def parent(): + doc = "" + def fget(self): + return self._parent + def fset(self, parent): + self._parent = parent + return property(**locals()) + + @property + def next_refresh(self): + """ return the feed's next refresh (time)""" + nr = None + if self._poll_freq == self.DEFAULT: + increment = Config.get_instance().poll_frequency + else: + increment = self._poll_freq + if increment > 0: + nr = self.last_poll + increment + return nr + + def dump(self): + fl = {} + for f, default in self.__save_fields: + fl[f] = self.__getattribute__(f) + return fl + + def undump(self, dict): + for f, default in self.__save_fields: + self.__setattr__(f, dict.get(f, default)) + return + + def poll_done(self): + self.emit('poll-done') + + # FeedItems stuff + def _sort(self, items): + """ + Sorts the given items according to the sort order + """ + items = sorted(items, cmp=self._cmp) + # XXX + #if self._items.sort_order: + # items.reverse() + return items + + def _cmp(self, a, b): + """ + Comparator method to compare items based on the item's pub_date attribute + + If an item doesn't have a pub_date, it uses title and prioritizes the + unread items + """ + try: + return cmp(a.pub_date, b.pub_date) + except AttributeError: + return locale.strcoll(a.title, b.title) and not a.seen or not b.seen + + def add_items(self, items): + config = Config.get_instance() + cutpoint = self.number_of_items_stored + if cutpoint == Feed.DEFAULT: + cutpoint = config.number_of_items_stored + items = set(sorted(items, cmp=self._cmp)) + maxid = reduce(max, [item.id for item in self._items]) + for item in items: + maxid += 1 + item.id = maxid + item.feed = self + items.updated(item) + self._items = self._items.union(items) + self.restore_items(self._items) + self.emit('items-updated', items) + + def restore_items(self, items): + olditems = [] + num_unread = 0 + config = Config.get_instance() + cutpoint = self.number_of_items_stored + if cutpoint == Feed.DEFAULT: + cutpoint = config.number_of_items_stored + items = self._sort(items) + for idx, item in enumerate(items): + item.feed = self + if item.sticky or not item.seen or idx <= cutpoint: + item.connect('read', self.item_marked_read) + # forward sticky signal? XXX + self._items.add(item) + num_unread += item.seen and 1 or 0 + continue + else: + item.clean_up() + olditems.append(item) + self.n_items_unread = num_unread + #self.number_of_items_stored = len(self.items) + if olditems: + self.emit('items-deleted', olditems) + return + + def item_marked_read(self, item): + self.n_items_unread += item.seen and -1 or 1 + + def delete_all_items(self): + self.emit('items-deleted', self._items) + + @property + def items(self): + return self._items + + @property + def number_of_unread(self): + return self._n_items_unread + + @property + def number_of_items(self): + return len(self._items) + + def mark_all_items_as_read(self): + unread = [item for item in self._items if not item.seen] + self.emit('items-read', unread) + self.n_items_unread = 0 + + def load_contents(self): + if self._items_loaded: + return False + itemstore = ItemStore.get_instance() + items = itemstore.read_feed_items(self) + if items: + self.restore_items(items) + self._items_loaded = True + return self._items_loaded + + def unload_contents(self): + """ + Unloads the items by disconnecting the signals and reinitialising the + instance variables + + TODO: unload seems to lose some circular references. garbage collector + will find them, though, so maybe it's not a problem. + """ + if not self._items_loaded: + return + self._items.clear() + self._items_loaded = False + + @classmethod + def create_new_feed(klass, title, location="", username="", password=""): + f = klass() + f._title = title + f._location = location + f._id = Config.get_instance().next_feed_id_seq() + f._username = username + f._password = password + return f + + @classmethod + def create_empty_feed(klass): + f = klass() + return f diff --git a/src/lib/subscribe.py b/src/lib/subscribe.py index 604ae71..671f510 100644 --- a/src/lib/subscribe.py +++ b/src/lib/subscribe.py @@ -26,8 +26,7 @@ pygtk.require('2.0') import gtk import gtk.glade import gettext -from Feed import Feed -import FeedList +import feeds import error import Event import utils @@ -159,7 +158,7 @@ class SubscribeConsumer: def http_results(self, status, data): url, user, password = self._presenter.get_credential() - feed = Feed.create_new_feed("", url, user, password) + feed = feeds.Feed.create_new_feed("", url, user, password) parsed = SummaryParser.parse(data, feed) if parsed.title == "": # store url loc in title in case it's empty @@ -169,7 +168,7 @@ class SubscribeConsumer: feed.access_info = (url, user, password) feed.last_poll = int(time.time()) category = None # Add to 'All' category - FeedList.get_instance().append(category, feed) + feeds.get_instance().append(category, feed) feed.router.route_all(parsed) feed.poll_done() -- 2.11.4.GIT