Refactored FeedDiscovery, now it uses Fetcher; added support for HTTP Basic Auth...
[straw.git] / straw / FeedManager.py
blob3056d06fcc6ec8a001eadd1c9c1c4334475f9bfd
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.status = straw.FS_IDLE
215 item.parent_id = 1
217 if item.parent:
218 item.parent_id = item.parent.id
219 else:
220 item.parent_id = 1
221 item.parent = self.nodes[1]
223 self.save_feed(item)
224 else:
225 if item.parent:
226 item.parent_id = item.parent.id
227 else:
228 item.parent_id = 1
229 item.parent = self.nodes[1]
231 self.save_category(item)
233 def lookup_feed(self, id):
234 return self.nodes[id]
236 def lookup_category(self, id):
237 return self.nodes[id]
239 def save_feed(self, feed):
240 if not feed.parent_id:
241 feed.parent_id = 1
243 category = self.lookup_category(feed.parent_id)
245 if not category:
246 return None
248 if feed.id:
249 result = self.dao.save(feed)
250 else:
251 result = self.dao.save(feed)
252 self._model_add_node(feed)
253 self.dao.save(feed)
255 self.nodes[feed.id] = feed
257 self._setup_node_signals(feed)
258 self.emit("feed-added", feed)
260 return result
262 def update_all_feeds(self, observers):
263 feeds = [node for node in self.nodes.values() if node.type == "F"]
265 self.update_all_id = FeedUpdater.update(feeds, [{
266 "job-done": [ self._on_update_all_done ],
267 "update-started": [ self._on_update_feed_start ],
268 "update-done": [ self._on_update_feed_done ]
269 }, observers])
271 def update_nodes(self, nodes, observers):
272 feeds = []
274 for node in nodes:
275 if node.type == "F":
276 feeds.append(node)
277 else:
278 feeds.extend([child_node for child_node in node.all_children() if child_node.type == "F"])
280 FeedUpdater.update(feeds, [{
281 "update-started": [ self._on_update_feed_start ],
282 "update-done": [ self._on_update_feed_done ]
283 }, observers])
285 def stop_update_all(self):
286 FeedUpdater.stop(self.update_all_id)
288 def _on_update_all_done(self, handler, data):
289 self.update_all_id = None
290 self.emit("update-all-done")
292 def _on_update_feed_start(self, handler, feed):
293 feed.props.status = straw.FS_UPDATING
295 def _on_update_feed_done(self, handler, update_result):
296 feed = update_result.feed
298 if update_result.error:
299 feed.props.status = straw.FS_ERROR
300 return
302 feed.props.status = straw.FS_IDLE
304 self.dao.tx_begin()
306 # Some metadata could change between the updates.
307 self.save_feed(feed)
309 for item in feed.items:
310 item.feed_id = feed.id
312 if not ItemManager.feed_item_exists(item):
313 self.dao.save(item)
314 feed.props.unread_count += 1
315 self.emit("item-added", item)
317 self.dao.tx_commit()
319 def save_category(self, category):
320 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
322 if category.parent and not category.parent_id:
323 category.parent_id = category.parent.id
325 self.dao.tx_begin()
327 self.dao.save(category)
328 self._model_add_node(category)
329 self.dao.save(category)
331 self.dao.tx_commit()
333 self.nodes[category.id] = category
335 self._setup_node_signals(category)
336 self.emit("category-added", category)
338 def on_parent_changed(self, obj, old_parent):
339 debug("parent changed, saving %d" % (obj.id))
340 self.dao.save(obj)
342 def on_norder_changed(self, obj, old_norder):
343 debug("norder changed, saving %d" % (obj.id))
344 self.dao.save(obj)
346 _instance = None
348 def _get_instance():
349 global _instance
350 if not _instance:
351 _instance = FeedManager()
352 return _instance