pre gobject: 3-pane view, callgraph, feed navigation
[straw.git] / src / lib / FeedItems.py
blob8cb9dffb8cf2ef21e2c50ccd267abdcd8542a557
1 """ FeedItems.py
3 Straw module for handling items that belongs to a feed. This modules is
4 responsible for adding,cutting, and deleting items of the current feed.
5 """
6 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
7 __license__ = """GNU General Public License
9 This program is free software; you can redistribute it and/or modify it under the
10 terms of the GNU General Public License as published by the Free Software
11 Foundation; either version 2 of the License, or (at your option) any later
12 version.
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License along with
19 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20 Place - Suite 330, Boston, MA 02111-1307, USA. """
23 import QueueDict
24 import Config
25 import Feed
26 import Event
27 import ItemStore
28 import error
29 import locale
31 class FeedItems(object):
32 def __init__(self, feed):
33 self._feed = feed
34 self._items = QueueDict.ItemQueue()
35 self._init_items()
36 config = Config.get_instance()
37 config.signal_connect(Event.ItemOrderChangedSignal, self.item_order_changed)
38 config.signal_connect(Event.NumberOfItemsStoredChangedSignal, self.items_stored_changed)
40 def _init_items(self):
41 config = Config.get_instance()
42 self._items.clear()
43 self._items.sort_order = config.item_order
44 self._prefs_items_stored = config.number_of_items_stored
45 self._idseq = 0
46 self._number_of_items = None
47 self._loaded = False
50 @apply
51 def number_of_unread():
52 doc = ""
53 def fget(self):
54 return self._feed.n_items_unread
55 def fset(self, n):
56 self._feed.n_items_unread = n
57 return property(**locals())
59 @apply
60 def number_of_items():
61 doc = ""
62 def fget(self):
63 if self._number_of_items:
64 return self._number_of_items
65 if self._loaded:
66 error.log("was loaded but number_of_items was None!")
67 return 0
68 def fset(self, n):
69 self._number_of_items = n
70 return property(**locals())
72 def item_order_changed(self, event):
73 self._items.sort_order = event.sender.item_order
74 self._feed.signal_refresh_display()
76 def items_stored_changed(self, event):
77 self._prefs_items_stored = event.sender.number_of_items_stored
78 self._feed.signal_refresh_display()
80 def add_items(self, new_items):
81 cutpoint = self._get_cutpoint()
82 items = sorted(new_items, cmp=self._cmp)
83 newitems = []
84 removed_items = []
85 for x, item in enumerate(items):
86 if item in self._items:
87 continue
88 if not item.id:
89 self._idseq += 1
90 item.id = self._idseq
91 item.feed = self._feed
92 if self._number_of_items < cutpoint:
93 self._items.append(item)
94 else:
95 # no point on increasing the number of items here since it's
96 # already over the cutpoint
97 ritem = self._items.replace(item)
98 removed_items.append(ritem)
99 newitems.append(item)
100 item.signal_connect(Event.ItemReadSignal, self.item_read)
101 item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal)
102 self._idseq = max(self._items.keys())
103 self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen])
104 self.number_of_items = len(self._items)
106 if removed_items:
107 self._feed.signal_deleted_item(removed_items)
108 if newitems:
109 self._feed.signal_new_items(newitems)
110 self._feed.signal_refresh_display()
112 def restore_items(self, items):
114 Restores the given items in an ordered form
116 # should we check for duplicates here? Or should that be done
117 # when new items arrive?
120 items = self._sort(items)
121 no_restore = []
122 cutpoint = self._get_cutpoint()
123 for x,item in enumerate(items):
124 item.feed = self._feed
125 if item.sticky or x <= cutpoint:
126 item.signal_connect(Event.ItemReadSignal, self.item_read)
127 item.signal_connect(Event.ItemStickySignal, self._feed.forward_signal)
128 self._items.append(item)
129 continue
130 item.clean_up()
131 no_restore.append(item)
132 self._idseq = max(self._items.keys())
133 self.number_of_unread = len([i for i in self._items.itervalues() if not i.seen])
134 self.number_of_items = len(self._items)
135 if no_restore:
136 self._feed.signal_deleted_item(no_restore)
138 def _disconnect_item_signals(self, item):
139 item.signal_disconnect(Event.ItemReadSignal, self.item_read)
140 item.signal_disconnect(Event.ItemStickySignal, self._feed.forward_signal)
142 def item_read(self, signal):
143 if signal.sender.seen:
144 change = -1
145 else:
146 change = 1
147 self.number_of_unread = self.number_of_unread + change
148 self._feed.forward_signal(signal)
150 def get_items(self):
151 if not self._loaded:
152 self.load()
153 return self._items.values()
155 def get_item_index(self, item):
156 if not self._loaded:
157 self.load()
158 idx = self._items.index(item.id)
159 return idx
161 def mark_all_read(self):
162 if not self._loaded:
163 self.load()
164 keys = self._items.keys()
165 changed = [(keys.index(key), value) for key, value in self._items if value.set_seen_quiet()]
166 self.number_of_unread = 0
167 self._feed.signal_all_items_read(changed)
168 return
170 def load(self):
172 Loads and restores the items from the data store
175 if self._loaded:
176 return False
177 itemstore = ItemStore.get_instance()
178 items = itemstore.read_feed_items(self._feed)
179 if items:
180 #error.log("load ", self._feed.title, ", number of items: ", len(items), ", number of unread before restore: ", self.number_of_unread)
181 self.restore_items(items)
182 self._loaded = True
183 else:
184 #print "No items, not loading ", self._feed.title
185 self._loaded = False
186 return self._loaded
188 def unload(self):
190 Unloads the items by disconnecting the signals and reinitialising the
191 instance variables
193 TODO: unload seems to lose some circular references. garbage collector
194 will find them, though, so maybe it's not a problem.
196 if not self._loaded:
197 return
198 for key, item in self._items:
199 self._disconnect_item_signals(item)
200 self._init_items()
202 def _get_cutpoint(self):
204 Returns the current cutpoint
207 if self._feed.number_of_items_stored == Feed.Feed.DEFAULT:
208 cutpoint = self._prefs_items_stored
209 else:
210 cutpoint = self._feed.number_of_items_stored
211 return cutpoint
213 def _sort(self, items):
215 Sorts the given items according to the sort order
217 items.sort(self._cmp)
218 if self._items.sort_order:
219 items.reverse()
220 return items
222 def _cmp(self, a, b):
224 Comparator method to compare items based on the item's pub_date attribute
226 If an item doesn't have a pub_date, it uses title and prioritizes the
227 unread items
229 try:
230 return cmp(a.pub_date, b.pub_date)
231 except AttributeError:
232 return locale.strcoll(a.title, b.title) and not a.seen or not b.seen