Made deleting node children more efficient.
[straw.git] / straw / FeedManager.py
bloba733fbec67330f1eb8d1fc0fb5f7150c9d8c1c68
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 straw
11 _storage_path = None
13 model_data = None
15 def import_opml(path, category):
16 fm = _get_instance()
17 fm.import_opml(path, category)
19 def init():
20 fm = _get_instance()
21 fm.init_storage(_storage_path)
23 def setup(storage_path = None):
24 global _storage_path
25 _storage_path = storage_path
27 def lookup_feed(id, ctg_id):
28 fm = _get_instance()
29 return fm.lookup_feed(id, ctg_id)
31 def lookup_category(ctg_id):
32 fm = _get_instance()
33 return fm.lookup_category(ctg_id)
35 def get_feed_items(id):
36 fm = _get_instance()
37 return fm.get_feed_items(id)
39 def get_category_items(id):
40 fm = _get_instance()
41 return fm.get_category_items(id)
43 def get_feeds():
44 fm = _get_instance()
45 return fm.get_feeds()
47 def update_all_feeds(observers, feeds = None):
48 fm = _get_instance()
49 return fm.update_all_feeds(observers, feeds)
51 def save_category(category):
52 fm = _get_instance()
53 return fm.save_category(category)
55 def save_feed(feed):
56 fm = _get_instance()
57 result = fm.save_feed(feed)
58 return result
60 def save_all(save_list):
61 fm = _get_instance()
62 fm.save_all(save_list)
64 def get_model():
65 fm = _get_instance()
66 return fm.nodes
68 def categories(root_id = 1):
69 nodes = get_model()
71 if not nodes.has_key(root_id):
72 return
74 yield nodes[root_id]
76 for node in nodes[root_id].children:
77 if node.type == "C":
78 for child in categories(node.id):
79 yield child
81 def move_node(node, target_path):
82 fm = _get_instance()
83 fm.move_node(node, target_path)
85 def delete_nodes(node_ids):
86 fm = _get_instance()
87 fm.delete_nodes(node_ids)
89 class FeedManager(GObject):
90 __gsignals__ = {
91 "item-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
92 "feed-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
93 "category-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
96 def __init__(self):
97 GObject.__init__(self)
99 self.storage = None
101 def init_storage(self, path):
102 self.storage = Storage(path)
103 self.dao = DAO(self.storage)
104 self.nodes = self.dao.get_nodes()
106 self._prepare_model()
108 def _prepare_model(self):
109 for node in self.nodes.values():
110 self._setup_node_signals(node)
112 if node.parent_id != None:
113 node.parent = self.nodes[node.parent_id]
114 node.parent.append_child(node)
115 else:
116 node.parent = None
118 def _setup_node_signals(self, node):
119 node.connect("parent-changed", self.on_parent_changed)
120 node.connect("norder-changed", self.on_norder_changed)
122 def import_opml(self, path, category):
123 job = Job("opml-parse")
124 job.data = (path, category)
125 job.observers = [ { "job-done": [ self.import_opml_save ] } ]
127 JobManager.start_job(job)
129 def import_opml_save(self, handler, opml_data):
130 save_list, category = opml_data
132 self.dao.tx_begin()
133 self.save_all(save_list)
134 self.dao.tx_commit()
136 def _model_add_node(self, node):
137 self.nodes[node.parent_id].add_child(node)
139 def move_node(self, node, target_path):
140 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
141 new_parent = self.nodes[1].get_by_path(target_path[:-1])
143 if not new_parent:
144 new_parent = self.nodes[1]
146 node.move(new_parent, target_path[-1:][0])
148 def delete_nodes(self, node_ids):
149 self.dao.tx_begin()
151 for id in node_ids:
152 self.delete_node(id)
154 self.dao.tx_commit()
156 def delete_node(self, id):
157 if not self.nodes.has_key(id):
158 return
160 node = self.nodes[id]
162 children = list(node.children)
163 children.reverse()
165 for child_node in children:
166 self.delete_node(child_node.id)
168 node.parent.remove_child(node)
169 del self.nodes[id]
171 if node.type == "F":
172 debug("deleting F %s" % id)
173 self.dao.delete_feed(id)
174 elif node.type == "C":
175 debug("deleting C %s" % id)
176 self.dao.delete_category(id)
178 def save_all(self, save_list):
179 for item in save_list:
180 if isinstance(item, Feed):
181 item.parent_id = 1
183 if item.parent:
184 item.parent_id = item.parent.id
185 else:
186 item.parent_id = 1
187 item.parent = self.nodes[1]
189 self.save_feed(item)
190 else:
191 if item.parent:
192 item.parent_id = item.parent.id
193 else:
194 item.parent_id = 1
195 item.parent = self.nodes[1]
197 self.save_category(item)
199 def lookup_feed(self, id):
200 return self.nodes[id]
202 def lookup_category(self, id):
203 return self.nodes[id]
205 def save_feed(self, feed):
206 if not feed.parent_id:
207 feed.parent_id = 1
209 category = self.lookup_category(feed.parent_id)
211 if not category:
212 return None
214 result = self.dao.save(feed)
216 self.nodes[feed.id] = feed
217 self._model_add_node(feed)
218 self._setup_node_signals(feed)
219 self.emit("feed-added", feed)
221 return result
223 def update_all_feeds(self, observers, feeds = None):
224 if not feeds:
225 feeds = [node for node in self.nodes.values() if node.type == "F"]
227 FeedUpdater.update_feeds(feeds, [{
228 "job-done": [ self.update_all_feeds_done ],
229 "task-start": [ self._on_update_feed_start ],
230 "task-done": [ self._on_update_feed_done ]
231 }, observers])
233 def update_all_feeds_done(self, handler, data):
234 pass
236 def _on_update_feed_start(self, handler, feed):
237 feed.props.status = straw.FS_UPDATING
239 def _on_update_feed_done(self, handler, data):
240 feed = data.task_info.data["feed"]
241 feed.props.status = straw.FS_IDLE
243 if not data.result:
244 return
246 self.dao.tx_begin()
248 for item in data.result.items:
249 item.feed_id = feed.id
251 if not self.feed_item_exists(item):
252 self.dao.save(item)
253 feed.props.unread_count += 1
254 self.emit("item-added", item)
256 self.dao.tx_commit()
258 # CATEGORIES
260 def save_category(self, category):
261 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
263 if category.parent and not category.parent_id:
264 category.parent_id = category.parent.id
266 self.dao.tx_begin()
268 self.dao.save(category)
269 self._model_add_node(category)
270 self.dao.save(category)
272 self.dao.tx_commit()
274 self.nodes[category.id] = category
276 self._setup_node_signals(category)
277 self.emit("category-added", category)
279 def get_feed_items(self, id):
280 feed = self.lookup_feed(id)
281 feed.items = self.dao.get(Item, " WHERE feed_id = %d" % feed.id)
282 for item in feed.items:
283 item.feed = feed
284 item.connect("is-read-changed", feed.on_is_read_changed)
285 item.connect("is-read-changed", self.on_item_is_read_changed)
286 return feed.items
288 def get_category_items(self, id):
289 category = self.lookup_category(id)
291 if not category:
292 return None
294 def _get_children_feeds(category_id):
295 feed_ids = [str(node.id) for node in self.lookup_category(category_id).children if node.type == "F"]
296 children_categories = [node.id for node in self.lookup_category(category_id).children if node.type == "C"]
298 for ctg_id in children_categories:
299 feed_ids.extend(_get_children_feeds(ctg_id))
301 return feed_ids
303 in_str = ", ".join(_get_children_feeds(id))
305 items = self.dao.get(Item, " WHERE feed_id IN(%s) ORDER BY id" % in_str)
307 for item in items:
308 feed = self.lookup_feed(item.feed_id)
309 item.feed = feed
310 item.connect("is-read-changed", feed.on_is_read_changed)
311 item.connect("is-read-changed", self.on_item_is_read_changed)
313 return items
315 def feed_item_exists(self, item):
316 result = self.dao.get(Item, " WHERE feed_id = ? AND title = ?", (item.feed_id, item.title,))
317 return len(result) > 0
319 def on_item_is_read_changed(self, obj, is_read):
320 self.dao.save(obj)
322 def on_parent_changed(self, obj, old_parent):
323 debug("parent changed, saving %d" % (obj.id))
324 self.dao.save(obj)
326 def on_norder_changed(self, obj, old_norder):
327 debug("norder changed, saving %d" % (obj.id))
328 self.dao.save(obj)
330 _instance = None
332 def _get_instance():
333 global _instance
334 if not _instance:
335 _instance = FeedManager()
336 return _instance