fb585f8969e5710942fa4ead5d954a5c3acf15fd
[straw.git] / straw / FeedManager.py
blobfb585f8969e5710942fa4ead5d954a5c3acf15fd
1 from Constants import *
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 gobject
9 import opml
11 _storage_path = None
13 def init():
14 fm = _get_instance()
15 fm.init_storage(_storage_path)
17 def setup(storage_path = None):
18 global _storage_path
19 _storage_path = storage_path
21 def import_opml(path, category):
22 fm = _get_instance()
23 fm.import_opml(path, category)
25 def export_opml(root_id, filename):
26 fm = _get_instance()
27 fm.export_opml(root_id, filename)
29 def lookup_feed(id):
30 fm = _get_instance()
31 return fm.lookup_feed(id)
33 def lookup_category(ctg_id):
34 fm = _get_instance()
35 return fm.lookup_category(ctg_id)
37 def get_feeds():
38 fm = _get_instance()
39 return fm.get_feeds()
41 def update_all_feeds(observers):
42 fm = _get_instance()
43 return fm.update_all_feeds(observers)
45 def is_update_all_running():
46 fm = _get_instance()
47 return fm.update_all_id != None
49 def stop_update_all():
50 fm = _get_instance()
51 return fm.stop_update_all()
53 def update_nodes(nodes, observers = {}):
54 fm = _get_instance()
55 return fm.update_nodes(nodes, observers)
57 def save_category(category):
58 fm = _get_instance()
59 return fm.save_category(category)
61 def save_feed(feed):
62 fm = _get_instance()
63 result = fm.save_feed(feed)
64 return result
66 def save_all(save_list):
67 fm = _get_instance()
68 fm.save_all(save_list)
70 def get_model():
71 fm = _get_instance()
72 return fm.nodes
74 def get_children_feeds(node_id):
75 node = lookup_category(node_id)
77 if not node:
78 return None
80 return [_node for _node in node.all_children() if _node.type == "F"]
82 def categories(root_id = 1):
83 nodes = get_model()
85 if not nodes.has_key(root_id):
86 return
88 yield nodes[root_id]
90 for node in nodes[root_id].children:
91 if node.type == "C":
92 for child in categories(node.id):
93 yield child
95 def move_node(node, target_path):
96 fm = _get_instance()
97 fm.move_node(node, target_path)
99 def delete_nodes(node_ids):
100 fm = _get_instance()
101 fm.delete_nodes(node_ids)
103 class FeedManager(GObject):
104 __gsignals__ = {
105 "item-added": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
106 "feed-added": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
107 "feed-status-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
108 "category-added": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
109 "update-all-done": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
112 def __init__(self):
113 GObject.__init__(self)
115 self.storage = None
116 self.update_all_id = None
118 def emit(self, *args):
119 gobject.idle_add(gobject.GObject.emit, self, *args)
121 def init_storage(self, path):
122 self.storage = Storage(path)
123 self.dao = DAO(self.storage)
124 self.nodes = self.dao.get_nodes()
126 self._prepare_model()
128 def _prepare_model(self):
129 for node in self.nodes.values():
130 self._setup_node_signals(node)
132 if node.parent_id != None:
133 node.parent = self.nodes[node.parent_id]
134 node.parent.append_child(node)
135 else:
136 node.parent = None
138 if isinstance(node, Feed):
139 self._set_feed_status(node, FS_IDLE)
141 def _setup_node_signals(self, node):
142 node.connect("parent-changed", self.on_parent_changed)
143 node.connect("norder-changed", self.on_norder_changed)
145 def _set_feed_status(self, feed, status):
146 if not hasattr(feed, "status"):
147 feed.status = None
149 if feed.status != status:
150 feed.status = status
151 self.emit("feed-status-changed", feed)
153 def import_opml(self, path, category):
154 # XXX: We should support remote locations for OPML import!
155 url = "file://" + path
156 opml.import_opml(url, category, [ { "opml-imported": [ self._on_opml_imported ] } ])
158 def _on_opml_imported(self, handler, save_list):
159 if save_list:
160 self.dao.tx_begin()
161 self.save_all(save_list)
162 self.dao.tx_commit()
164 def export_opml(self, root_id, filename):
165 if not self.nodes.has_key(root_id):
166 return None
168 opml.export(self.nodes[root_id], filename)
170 def _model_add_node(self, node):
171 self.nodes[node.parent_id].add_child(node)
173 def move_node(self, node, target_path):
174 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
175 new_parent = self.nodes[1].get_by_path(target_path[:-1])
177 if not new_parent:
178 new_parent = self.nodes[1]
180 self.dao.tx_begin()
181 node.move(new_parent, target_path[-1:][0])
182 self.dao.tx_commit()
184 def delete_nodes(self, node_ids):
185 self.dao.tx_begin()
187 for id in node_ids:
188 self.delete_node(id)
190 self.dao.tx_commit()
192 def delete_node(self, id):
193 if not self.nodes.has_key(id):
194 return
196 node = self.nodes[id]
198 children = list(node.children)
199 children.reverse()
201 for child_node in children:
202 self.delete_node(child_node.id)
204 node.parent.remove_child(node)
205 del self.nodes[id]
207 if node.type == "F":
208 debug("deleting F %s" % id)
209 self.dao.delete_feed(id)
210 elif node.type == "C":
211 debug("deleting C %s" % id)
212 self.dao.delete_category(id)
214 def save_all(self, save_list):
215 for item in save_list:
216 if isinstance(item, Feed):
217 self._set_feed_status(item, FS_IDLE)
218 item.parent_id = 1
220 if item.parent:
221 item.parent_id = item.parent.id
222 else:
223 item.parent_id = 1
224 item.parent = self.nodes[1]
226 self.save_feed(item)
227 else:
228 if item.parent:
229 item.parent_id = item.parent.id
230 else:
231 item.parent_id = 1
232 item.parent = self.nodes[1]
234 self.save_category(item)
236 def lookup_feed(self, id):
237 return self.nodes[id]
239 def lookup_category(self, id):
240 return self.nodes[id]
242 def save_feed(self, feed):
243 if not feed.parent_id:
244 feed.parent_id = 1
246 category = self.lookup_category(feed.parent_id)
248 if not category:
249 return None
251 if feed.id:
252 result = self.dao.save(feed)
253 else:
254 result = self.dao.save(feed)
255 self._model_add_node(feed)
256 self.dao.save(feed)
258 self.nodes[feed.id] = feed
260 self._setup_node_signals(feed)
261 self.emit("feed-added", feed)
263 return result
265 def update_all_feeds(self, observers):
266 feeds = [node for node in self.nodes.values() if node.type == "F"]
268 self.update_all_id = FeedUpdater.update(feeds, [{
269 "job-done": [ self._on_update_all_done ],
270 "update-started": [ self._on_update_feed_start ],
271 "update-done": [ self._on_update_feed_done ]
272 }, observers])
274 def update_nodes(self, nodes, observers):
275 feeds = []
277 for node in nodes:
278 if node.type == "F":
279 feeds.append(node)
280 else:
281 feeds.extend([child_node for child_node in node.all_children() if child_node.type == "F"])
283 FeedUpdater.update(feeds, [{
284 "update-started": [ self._on_update_feed_start ],
285 "update-done": [ self._on_update_feed_done ]
286 }, observers])
288 def stop_update_all(self):
289 FeedUpdater.stop(self.update_all_id)
291 def _on_update_all_done(self, handler, data):
292 self.update_all_id = None
293 self.emit("update-all-done")
295 def _on_update_feed_start(self, handler, feed):
296 self._set_feed_status(feed, FS_UPDATING)
298 def _on_update_feed_done(self, handler, update_result):
299 feed = update_result.feed
301 if update_result.error:
302 feed.update_result = update_result
303 self._set_feed_status(feed, FS_ERROR)
304 return
306 self._set_feed_status(feed, FS_IDLE)
308 self.dao.tx_begin()
310 # Some metadata could change between the updates.
311 self.save_feed(feed)
313 for item in feed.items:
314 item.feed_id = feed.id
316 if not ItemManager.feed_item_exists(item):
317 self.dao.save(item)
318 feed.props.unread_count += 1
319 self.emit("item-added", item)
321 self.dao.tx_commit()
323 def save_category(self, category):
324 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
326 if category.parent and not category.parent_id:
327 category.parent_id = category.parent.id
329 self.dao.tx_begin()
331 self.dao.save(category)
332 self._model_add_node(category)
333 self.dao.save(category)
335 self.dao.tx_commit()
337 self.nodes[category.id] = category
339 self._setup_node_signals(category)
340 self.emit("category-added", category)
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