Fixed exception that occured in some cases when removing nodes.
[straw.git] / straw / FeedManager.py
blob162498b1765203d686aa52042f98f466952c8572
1 from JobManager import Job
2 from error import debug
3 from gobject import GObject, SIGNAL_RUN_FIRST
4 from model import Feed, Item, Category
5 from storage import DAO, Storage
6 import FeedUpdater
7 import JobManager
8 import gobject
9 import opml
10 import straw
12 _storage_path = None
14 model_data = None
16 def init():
17 fm = _get_instance()
18 fm.init_storage(_storage_path)
20 def setup(storage_path = None):
21 global _storage_path
22 _storage_path = storage_path
24 def import_opml(path, category):
25 fm = _get_instance()
26 fm.import_opml(path, category)
28 def export_opml(root_id, filename):
29 fm = _get_instance()
30 fm.export_opml(root_id, filename)
32 def lookup_feed(id, ctg_id):
33 fm = _get_instance()
34 return fm.lookup_feed(id, ctg_id)
36 def lookup_category(ctg_id):
37 fm = _get_instance()
38 return fm.lookup_category(ctg_id)
40 def get_feed_items(id):
41 fm = _get_instance()
42 return fm.get_feed_items(id)
44 def get_category_items(id):
45 fm = _get_instance()
46 return fm.get_category_items(id)
48 def get_feeds():
49 fm = _get_instance()
50 return fm.get_feeds()
52 def update_all_feeds(observers, feeds = None):
53 fm = _get_instance()
54 return fm.update_all_feeds(observers, feeds)
56 def is_update_all_running():
57 fm = _get_instance()
58 return fm.update_all_running
60 def save_category(category):
61 fm = _get_instance()
62 return fm.save_category(category)
64 def save_feed(feed):
65 fm = _get_instance()
66 result = fm.save_feed(feed)
67 return result
69 def save_all(save_list):
70 fm = _get_instance()
71 fm.save_all(save_list)
73 def get_model():
74 fm = _get_instance()
75 return fm.nodes
77 def categories(root_id = 1):
78 nodes = get_model()
80 if not nodes.has_key(root_id):
81 return
83 yield nodes[root_id]
85 for node in nodes[root_id].children:
86 if node.type == "C":
87 for child in categories(node.id):
88 yield child
90 def move_node(node, target_path):
91 fm = _get_instance()
92 fm.move_node(node, target_path)
94 def delete_nodes(node_ids):
95 fm = _get_instance()
96 fm.delete_nodes(node_ids)
98 class FeedManager(GObject):
99 __gsignals__ = {
100 "item-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
101 "feed-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
102 "category-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
103 "update-all-done" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
106 def __init__(self):
107 GObject.__init__(self)
109 self.storage = None
110 self.update_all_running = False
112 def init_storage(self, path):
113 self.storage = Storage(path)
114 self.dao = DAO(self.storage)
115 self.nodes = self.dao.get_nodes()
117 self._prepare_model()
119 def _prepare_model(self):
120 for node in self.nodes.values():
121 self._setup_node_signals(node)
123 if node.parent_id != None:
124 node.parent = self.nodes[node.parent_id]
125 node.parent.append_child(node)
126 else:
127 node.parent = None
129 def _setup_node_signals(self, node):
130 node.connect("parent-changed", self.on_parent_changed)
131 node.connect("norder-changed", self.on_norder_changed)
133 def import_opml(self, path, category):
134 job = Job("opml-parse")
135 job.data = (path, category)
136 job.observers = [ { "job-done": [ self.import_opml_save ] } ]
138 JobManager.start_job(job)
140 def import_opml_save(self, handler, opml_data):
141 save_list, category = opml_data
143 self.dao.tx_begin()
144 self.save_all(save_list)
145 self.dao.tx_commit()
147 def export_opml(self, root_id, filename):
148 if not self.nodes.has_key(root_id):
149 return None
151 opml.export(self.nodes[root_id], filename)
153 def _model_add_node(self, node):
154 self.nodes[node.parent_id].add_child(node)
156 def move_node(self, node, target_path):
157 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
158 new_parent = self.nodes[1].get_by_path(target_path[:-1])
160 if not new_parent:
161 new_parent = self.nodes[1]
163 node.move(new_parent, target_path[-1:][0])
165 def delete_nodes(self, node_ids):
166 self.dao.tx_begin()
168 for id in node_ids:
169 self.delete_node(id)
171 self.dao.tx_commit()
173 def delete_node(self, id):
174 if not self.nodes.has_key(id):
175 return
177 node = self.nodes[id]
179 children = list(node.children)
180 children.reverse()
182 for child_node in children:
183 self.delete_node(child_node.id)
185 node.parent.remove_child(node)
186 del self.nodes[id]
188 if node.type == "F":
189 debug("deleting F %s" % id)
190 self.dao.delete_feed(id)
191 elif node.type == "C":
192 debug("deleting C %s" % id)
193 self.dao.delete_category(id)
195 def save_all(self, save_list):
196 for item in save_list:
197 if isinstance(item, Feed):
198 item.parent_id = 1
200 if item.parent:
201 item.parent_id = item.parent.id
202 else:
203 item.parent_id = 1
204 item.parent = self.nodes[1]
206 self.save_feed(item)
207 else:
208 if item.parent:
209 item.parent_id = item.parent.id
210 else:
211 item.parent_id = 1
212 item.parent = self.nodes[1]
214 self.save_category(item)
216 def lookup_feed(self, id):
217 return self.nodes[id]
219 def lookup_category(self, id):
220 return self.nodes[id]
222 def save_feed(self, feed):
223 if not feed.parent_id:
224 feed.parent_id = 1
226 category = self.lookup_category(feed.parent_id)
228 if not category:
229 return None
231 result = self.dao.save(feed)
233 self.nodes[feed.id] = feed
234 self._model_add_node(feed)
235 self._setup_node_signals(feed)
236 self.emit("feed-added", feed)
238 return result
240 def update_all_feeds(self, observers, feeds = None):
241 if not feeds:
242 feeds = [node for node in self.nodes.values() if node.type == "F"]
244 self.update_all_running = True
246 FeedUpdater.update_feeds(feeds, [{
247 "job-done": [ self._on_update_all_done ],
248 "task-start": [ self._on_update_feed_start ],
249 "task-done": [ self._on_update_feed_done ]
250 }, observers])
252 def _on_update_all_done(self, handler, data):
253 self.update_all_running = False
254 self.emit("update-all-done")
256 def _on_update_feed_start(self, handler, feed):
257 feed.props.status = straw.FS_UPDATING
259 def _on_update_feed_done(self, handler, data):
260 feed = data.task_info.data["feed"]
261 feed.props.status = straw.FS_IDLE
263 if not data.result:
264 return
266 self.dao.tx_begin()
268 for item in data.result.items:
269 item.feed_id = feed.id
271 if not self.feed_item_exists(item):
272 self.dao.save(item)
273 feed.props.unread_count += 1
274 self.emit("item-added", item)
276 self.dao.tx_commit()
278 # CATEGORIES
280 def save_category(self, category):
281 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
283 if category.parent and not category.parent_id:
284 category.parent_id = category.parent.id
286 self.dao.tx_begin()
288 self.dao.save(category)
289 self._model_add_node(category)
290 self.dao.save(category)
292 self.dao.tx_commit()
294 self.nodes[category.id] = category
296 self._setup_node_signals(category)
297 self.emit("category-added", category)
299 def get_feed_items(self, id):
300 feed = self.lookup_feed(id)
301 feed.items = self.dao.get(Item, " WHERE feed_id = %d" % feed.id)
302 for item in feed.items:
303 item.feed = feed
304 item.connect("is-read-changed", feed.on_is_read_changed)
305 item.connect("is-read-changed", self.on_item_is_read_changed)
306 return feed.items
308 def get_category_items(self, id):
309 category = self.lookup_category(id)
311 if not category:
312 return None
314 def _get_children_feeds(category_id):
315 feed_ids = [str(node.id) for node in self.lookup_category(category_id).children if node.type == "F"]
316 children_categories = [node.id for node in self.lookup_category(category_id).children if node.type == "C"]
318 for ctg_id in children_categories:
319 feed_ids.extend(_get_children_feeds(ctg_id))
321 return feed_ids
323 in_str = ", ".join(_get_children_feeds(id))
325 items = self.dao.get(Item, " WHERE feed_id IN(%s) ORDER BY id" % in_str)
327 for item in items:
328 feed = self.lookup_feed(item.feed_id)
329 item.feed = feed
330 item.connect("is-read-changed", feed.on_is_read_changed)
331 item.connect("is-read-changed", self.on_item_is_read_changed)
333 return items
335 def feed_item_exists(self, item):
336 result = self.dao.get(Item, " WHERE feed_id = ? AND title = ?", (item.feed_id, item.title,))
337 return len(result) > 0
339 def on_item_is_read_changed(self, obj, is_read):
340 self.dao.save(obj)
342 def on_parent_changed(self, obj, old_parent):
343 debug("parent changed, saving %d" % (obj.id))
344 self.dao.save(obj)
346 def on_norder_changed(self, obj, old_norder):
347 debug("norder changed, saving %d" % (obj.id))
348 self.dao.save(obj)
350 _instance = None
352 def _get_instance():
353 global _instance
354 if not _instance:
355 _instance = FeedManager()
356 return _instance