Implemented Fetcher job, refactored JobManager, code cleanup.
[straw.git] / straw / FeedManager.py
blob313c0d7d3d9b10b5967c004168d8b2f7a0270683
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 ItemManager
8 import JobManager
9 import gobject
10 import opml
11 import straw
13 _storage_path = None
15 def init():
16 fm = _get_instance()
17 fm.init_storage(_storage_path)
19 def setup(storage_path = None):
20 global _storage_path
21 _storage_path = storage_path
23 def import_opml(path, category):
24 fm = _get_instance()
25 fm.import_opml(path, category)
27 def export_opml(root_id, filename):
28 fm = _get_instance()
29 fm.export_opml(root_id, filename)
31 def lookup_feed(id):
32 fm = _get_instance()
33 return fm.lookup_feed(id)
35 def lookup_category(ctg_id):
36 fm = _get_instance()
37 return fm.lookup_category(ctg_id)
39 def get_feeds():
40 fm = _get_instance()
41 return fm.get_feeds()
43 def update_all_feeds(observers):
44 fm = _get_instance()
45 return fm.update_all_feeds(observers)
47 def is_update_all_running():
48 fm = _get_instance()
49 return fm.update_all_id != None
51 def stop_update_all():
52 fm = _get_instance()
53 return fm.stop_update_all()
55 def update_nodes(nodes, observers = {}):
56 fm = _get_instance()
57 return fm.update_nodes(nodes, observers)
59 def save_category(category):
60 fm = _get_instance()
61 return fm.save_category(category)
63 def save_feed(feed):
64 fm = _get_instance()
65 result = fm.save_feed(feed)
66 return result
68 def save_all(save_list):
69 fm = _get_instance()
70 fm.save_all(save_list)
72 def get_model():
73 fm = _get_instance()
74 return fm.nodes
76 def get_children_feeds(node_id):
77 node = lookup_category(node_id)
79 if not node:
80 return None
82 return [_node for _node in node.all_children() if _node.type == "F"]
84 def categories(root_id = 1):
85 nodes = get_model()
87 if not nodes.has_key(root_id):
88 return
90 yield nodes[root_id]
92 for node in nodes[root_id].children:
93 if node.type == "C":
94 for child in categories(node.id):
95 yield child
97 def move_node(node, target_path):
98 fm = _get_instance()
99 fm.move_node(node, target_path)
101 def delete_nodes(node_ids):
102 fm = _get_instance()
103 fm.delete_nodes(node_ids)
105 class FeedManager(GObject):
106 __gsignals__ = {
107 "item-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
108 "feed-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
109 "category-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
110 "update-all-done" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
113 def __init__(self):
114 GObject.__init__(self)
116 self.storage = None
117 self.update_all_id = None
119 def emit(self, *args):
120 gobject.idle_add(gobject.GObject.emit, self, *args)
122 def init_storage(self, path):
123 self.storage = Storage(path)
124 self.dao = DAO(self.storage)
125 self.nodes = self.dao.get_nodes()
127 self._prepare_model()
129 def _prepare_model(self):
130 for node in self.nodes.values():
131 self._setup_node_signals(node)
133 if node.parent_id != None:
134 node.parent = self.nodes[node.parent_id]
135 node.parent.append_child(node)
136 else:
137 node.parent = None
139 if isinstance(node, Feed):
140 node.status = straw.FS_IDLE
142 def _setup_node_signals(self, node):
143 node.connect("parent-changed", self.on_parent_changed)
144 node.connect("norder-changed", self.on_norder_changed)
146 def import_opml(self, path, category):
147 job = Job("opml-parse")
148 job.data = ("file://" + path, category)
149 job.observers = [ { "task-done": [ self._on_opml_imported ] } ]
151 JobManager.start(job)
153 def _on_opml_imported(self, handler, task_result):
154 save_list = task_result.result
156 if save_list:
157 self.dao.tx_begin()
158 self.save_all(save_list)
159 self.dao.tx_commit()
161 def export_opml(self, root_id, filename):
162 if not self.nodes.has_key(root_id):
163 return None
165 opml.export(self.nodes[root_id], filename)
167 def _model_add_node(self, node):
168 self.nodes[node.parent_id].add_child(node)
170 def move_node(self, node, target_path):
171 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
172 new_parent = self.nodes[1].get_by_path(target_path[:-1])
174 if not new_parent:
175 new_parent = self.nodes[1]
177 self.dao.tx_begin()
178 node.move(new_parent, target_path[-1:][0])
179 self.dao.tx_commit()
181 def delete_nodes(self, node_ids):
182 self.dao.tx_begin()
184 for id in node_ids:
185 self.delete_node(id)
187 self.dao.tx_commit()
189 def delete_node(self, id):
190 if not self.nodes.has_key(id):
191 return
193 node = self.nodes[id]
195 children = list(node.children)
196 children.reverse()
198 for child_node in children:
199 self.delete_node(child_node.id)
201 node.parent.remove_child(node)
202 del self.nodes[id]
204 if node.type == "F":
205 debug("deleting F %s" % id)
206 self.dao.delete_feed(id)
207 elif node.type == "C":
208 debug("deleting C %s" % id)
209 self.dao.delete_category(id)
211 def save_all(self, save_list):
212 for item in save_list:
213 if isinstance(item, Feed):
214 item.parent_id = 1
216 if item.parent:
217 item.parent_id = item.parent.id
218 else:
219 item.parent_id = 1
220 item.parent = self.nodes[1]
222 self.save_feed(item)
223 else:
224 if item.parent:
225 item.parent_id = item.parent.id
226 else:
227 item.parent_id = 1
228 item.parent = self.nodes[1]
230 self.save_category(item)
232 def lookup_feed(self, id):
233 return self.nodes[id]
235 def lookup_category(self, id):
236 return self.nodes[id]
238 def save_feed(self, feed):
239 if not feed.parent_id:
240 feed.parent_id = 1
242 category = self.lookup_category(feed.parent_id)
244 if not category:
245 return None
247 if feed.id:
248 result = self.dao.save(feed)
249 else:
250 result = self.dao.save(feed)
251 self._model_add_node(feed)
252 self.dao.save(feed)
254 self.nodes[feed.id] = feed
256 self._setup_node_signals(feed)
257 self.emit("feed-added", feed)
259 return result
261 def update_all_feeds(self, observers):
262 feeds = [node for node in self.nodes.values() if node.type == "F"]
264 self.update_all_id = FeedUpdater.update(feeds, [{
265 "job-done": [ self._on_update_all_done ],
266 "update-started": [ self._on_update_feed_start ],
267 "update-done": [ self._on_update_feed_done ]
268 }, observers])
270 def update_nodes(self, nodes, observers):
271 feeds = []
273 for node in nodes:
274 if node.type == "F":
275 feeds.append(node)
276 else:
277 feeds.extend([child_node for child_node in node.all_children() if child_node.type == "F"])
279 FeedUpdater.update(feeds, [{
280 "update-started": [ self._on_update_feed_start ],
281 "update-done": [ self._on_update_feed_done ]
282 }, observers])
284 def stop_update_all(self):
285 FeedUpdater.stop(self.update_all_id)
287 def _on_update_all_done(self, handler, data):
288 self.update_all_id = None
289 self.emit("update-all-done")
291 def _on_update_feed_start(self, handler, feed):
292 feed.props.status = straw.FS_UPDATING
294 def _on_update_feed_done(self, handler, update_result):
295 feed = update_result.feed
297 if update_result.error:
298 feed.props.status = straw.FS_ERROR
299 return
301 feed.props.status = straw.FS_IDLE
303 self.dao.tx_begin()
305 # Some metadata could change between the updates.
306 self.save_feed(feed)
308 for item in feed.items:
309 item.feed_id = feed.id
311 if not ItemManager.feed_item_exists(item):
312 self.dao.save(item)
313 feed.props.unread_count += 1
314 self.emit("item-added", item)
316 self.dao.tx_commit()
318 def save_category(self, category):
319 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
321 if category.parent and not category.parent_id:
322 category.parent_id = category.parent.id
324 self.dao.tx_begin()
326 self.dao.save(category)
327 self._model_add_node(category)
328 self.dao.save(category)
330 self.dao.tx_commit()
332 self.nodes[category.id] = category
334 self._setup_node_signals(category)
335 self.emit("category-added", category)
337 def on_parent_changed(self, obj, old_parent):
338 debug("parent changed, saving %d" % (obj.id))
339 self.dao.save(obj)
341 def on_norder_changed(self, obj, old_norder):
342 debug("norder changed, saving %d" % (obj.id))
343 self.dao.save(obj)
345 _instance = None
347 def _get_instance():
348 global _instance
349 if not _instance:
350 _instance = FeedManager()
351 return _instance