""" 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