Put FeedManager.move_node operation in a transaction.
[straw.git] / straw / FeedManager.py
blobec5e13f90bc0682941596041effd3e9dd83190b2
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_running
51 def update_nodes(nodes, observers = {}):
52 fm = _get_instance()
53 return fm.update_nodes(nodes, observers)
55 def save_category(category):
56 fm = _get_instance()
57 return fm.save_category(category)
59 def save_feed(feed):
60 fm = _get_instance()
61 result = fm.save_feed(feed)
62 return result
64 def save_all(save_list):
65 fm = _get_instance()
66 fm.save_all(save_list)
68 def get_model():
69 fm = _get_instance()
70 return fm.nodes
72 def get_children_feeds(node_id):
73 node = lookup_category(node_id)
75 if not node:
76 return None
78 return [_node for _node in node.all_children() if _node.type == "F"]
80 def categories(root_id = 1):
81 nodes = get_model()
83 if not nodes.has_key(root_id):
84 return
86 yield nodes[root_id]
88 for node in nodes[root_id].children:
89 if node.type == "C":
90 for child in categories(node.id):
91 yield child
93 def move_node(node, target_path):
94 fm = _get_instance()
95 fm.move_node(node, target_path)
97 def delete_nodes(node_ids):
98 fm = _get_instance()
99 fm.delete_nodes(node_ids)
101 class FeedManager(GObject):
102 __gsignals__ = {
103 "item-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
104 "feed-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
105 "category-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
106 "update-all-done" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
109 def __init__(self):
110 GObject.__init__(self)
112 self.storage = None
113 self.update_all_running = False
115 def emit(self, *args):
116 gobject.idle_add(gobject.GObject.emit, self, *args)
118 def init_storage(self, path):
119 self.storage = Storage(path)
120 self.dao = DAO(self.storage)
121 self.nodes = self.dao.get_nodes()
123 self._prepare_model()
125 def _prepare_model(self):
126 for node in self.nodes.values():
127 self._setup_node_signals(node)
129 if node.parent_id != None:
130 node.parent = self.nodes[node.parent_id]
131 node.parent.append_child(node)
132 else:
133 node.parent = None
135 def _setup_node_signals(self, node):
136 node.connect("parent-changed", self.on_parent_changed)
137 node.connect("norder-changed", self.on_norder_changed)
139 def import_opml(self, path, category):
140 job = Job("opml-parse")
141 job.data = (path, category)
142 job.observers = [ { "job-done": [ self.import_opml_save ] } ]
144 JobManager.start_job(job)
146 def import_opml_save(self, handler, opml_data):
147 save_list, category = opml_data
149 self.dao.tx_begin()
150 self.save_all(save_list)
151 self.dao.tx_commit()
153 def export_opml(self, root_id, filename):
154 if not self.nodes.has_key(root_id):
155 return None
157 opml.export(self.nodes[root_id], filename)
159 def _model_add_node(self, node):
160 self.nodes[node.parent_id].add_child(node)
162 def move_node(self, node, target_path):
163 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
164 new_parent = self.nodes[1].get_by_path(target_path[:-1])
166 if not new_parent:
167 new_parent = self.nodes[1]
169 self.dao.tx_begin()
170 node.move(new_parent, target_path[-1:][0])
171 self.dao.tx_commit()
173 def delete_nodes(self, node_ids):
174 self.dao.tx_begin()
176 for id in node_ids:
177 self.delete_node(id)
179 self.dao.tx_commit()
181 def delete_node(self, id):
182 if not self.nodes.has_key(id):
183 return
185 node = self.nodes[id]
187 children = list(node.children)
188 children.reverse()
190 for child_node in children:
191 self.delete_node(child_node.id)
193 node.parent.remove_child(node)
194 del self.nodes[id]
196 if node.type == "F":
197 debug("deleting F %s" % id)
198 self.dao.delete_feed(id)
199 elif node.type == "C":
200 debug("deleting C %s" % id)
201 self.dao.delete_category(id)
203 def save_all(self, save_list):
204 for item in save_list:
205 if isinstance(item, Feed):
206 item.parent_id = 1
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_feed(item)
215 else:
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_category(item)
224 def lookup_feed(self, id):
225 return self.nodes[id]
227 def lookup_category(self, id):
228 return self.nodes[id]
230 def save_feed(self, feed):
231 if not feed.parent_id:
232 feed.parent_id = 1
234 category = self.lookup_category(feed.parent_id)
236 if not category:
237 return None
239 if feed.id:
240 result = self.dao.save(feed)
241 else:
242 result = self.dao.save(feed)
243 self._model_add_node(feed)
244 self.dao.save(feed)
246 self.nodes[feed.id] = feed
248 self._setup_node_signals(feed)
249 self.emit("feed-added", feed)
251 return result
253 def update_all_feeds(self, observers):
254 feeds = [node for node in self.nodes.values() if node.type == "F"]
256 self.update_all_running = True
258 FeedUpdater.update_feeds(feeds, [{
259 "job-done": [ self._on_update_all_done ],
260 "task-start": [ self._on_update_feed_start ],
261 "task-done": [ self._on_update_feed_done ]
262 }, observers])
264 def update_nodes(self, nodes, observers):
265 feeds = []
267 for node in nodes:
268 if node.type == "F":
269 feeds.append(node)
270 else:
271 feeds.extend([child_node for child_node in node.all_children() if child_node.type == "F"])
273 FeedUpdater.update_feeds(feeds, [{
274 "task-start": [ self._on_update_feed_start ],
275 "task-done": [ self._on_update_feed_done ]
276 }, observers])
278 def _on_update_all_done(self, handler, data):
279 self.update_all_running = False
280 self.emit("update-all-done")
282 def _on_update_feed_start(self, handler, feed):
283 feed.props.status = straw.FS_UPDATING
285 def _on_update_feed_done(self, handler, data):
286 feed = data.task_info.data["feed"]
287 feed.props.status = straw.FS_IDLE
289 if not data.result:
290 return
292 self.dao.tx_begin()
294 # Some metadata could change between the updates.
295 self.save_feed(feed)
297 for item in data.result.items:
298 item.feed_id = feed.id
300 if not ItemManager.feed_item_exists(item):
301 self.dao.save(item)
302 feed.props.unread_count += 1
303 self.emit("item-added", item)
305 self.dao.tx_commit()
307 def save_category(self, category):
308 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
310 if category.parent and not category.parent_id:
311 category.parent_id = category.parent.id
313 self.dao.tx_begin()
315 self.dao.save(category)
316 self._model_add_node(category)
317 self.dao.save(category)
319 self.dao.tx_commit()
321 self.nodes[category.id] = category
323 self._setup_node_signals(category)
324 self.emit("category-added", category)
326 def on_parent_changed(self, obj, old_parent):
327 debug("parent changed, saving %d" % (obj.id))
328 self.dao.save(obj)
330 def on_norder_changed(self, obj, old_norder):
331 debug("norder changed, saving %d" % (obj.id))
332 self.dao.save(obj)
334 _instance = None
336 def _get_instance():
337 global _instance
338 if not _instance:
339 _instance = FeedManager()
340 return _instance