Added "Add child" context menu option, more fixes on tree view manipulation...
[straw.git] / straw / FeedManager.py
bloba98ecab43c31da6b711e7e0b7fbd8c4469d40b49
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 class FeedManager(GObject):
86 __gsignals__ = {
87 "item-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
88 "feed-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
89 "category-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
92 def __init__(self):
93 GObject.__init__(self)
95 self.storage = None
97 def init_storage(self, path):
98 self.storage = Storage(path)
99 self.dao = DAO(self.storage)
100 self.nodes = self.dao.get_nodes()
102 self._prepare_model()
104 def _prepare_model(self):
105 for node in self.nodes.values():
106 self._setup_node_signals(node)
108 if node.parent_id != None:
109 node.parent = self.nodes[node.parent_id]
110 node.parent.append_child(node)
111 else:
112 node.parent = None
114 def _setup_node_signals(self, node):
115 node.connect("parent-changed", self.on_parent_changed)
116 node.connect("norder-changed", self.on_norder_changed)
118 def import_opml(self, path, category):
119 job = Job("opml-parse")
120 job.data = (path, category)
121 job.observers = [ { "job-done": [ self.import_opml_save ] } ]
123 JobManager.start_job(job)
125 def import_opml_save(self, handler, opml_data):
126 save_list, category = opml_data
128 self.dao.tx_begin()
129 self.save_all(save_list)
130 self.dao.tx_commit()
132 def _model_add_node(self, node):
133 self.nodes[node.parent_id].add_child(node)
135 def move_node(self, node, target_path):
136 debug("fm:move_node %d to %s" % (node.id, str(target_path)))
137 new_parent = self.nodes[1].get_by_path(target_path[:-1])
139 if not new_parent:
140 new_parent = self.nodes[1]
142 node.move(new_parent, target_path[-1:][0])
144 def save_all(self, save_list):
145 for item in save_list:
146 if isinstance(item, Feed):
147 item.parent_id = 1
149 if item.parent:
150 item.parent_id = item.parent.id
151 else:
152 item.parent_id = 1
153 item.parent = self.nodes[1]
155 self.save_feed(item)
156 else:
157 if item.parent:
158 item.parent_id = item.parent.id
159 else:
160 item.parent_id = 1
161 item.parent = self.nodes[1]
163 self.save_category(item)
165 def lookup_feed(self, id):
166 return self.nodes[id]
168 def lookup_category(self, id):
169 return self.nodes[id]
171 def save_feed(self, feed):
172 if not feed.parent_id:
173 feed.parent_id = 1
175 category = self.lookup_category(feed.parent_id)
177 if not category:
178 return None
180 result = self.dao.save(feed)
182 self.nodes[feed.id] = feed
183 self._model_add_node(feed)
184 self._setup_node_signals(feed)
185 self.emit("feed-added", feed)
187 return result
189 def update_all_feeds(self, observers, feeds = None):
190 if not feeds:
191 feeds = [node for node in self.nodes.values() if node.type == "F"]
193 FeedUpdater.update_feeds(feeds, [{
194 "job-done": [ self.update_all_feeds_done ],
195 "task-start": [ self._on_update_feed_start ],
196 "task-done": [ self._on_update_feed_done ]
197 }, observers])
199 def update_all_feeds_done(self, handler, data):
200 pass
202 def _on_update_feed_start(self, handler, feed):
203 feed.props.status = straw.FS_UPDATING
205 def _on_update_feed_done(self, handler, data):
206 feed = data.task_info.data["feed"]
207 feed.props.status = straw.FS_IDLE
209 if not data.result:
210 return
212 self.dao.tx_begin()
214 for item in data.result.items:
215 item.feed_id = feed.id
217 if not self.feed_item_exists(item):
218 self.dao.save(item)
219 feed.props.unread_count += 1
220 self.emit("item-added", item)
222 self.dao.tx_commit()
224 # CATEGORIES
226 def save_category(self, category):
227 debug("saving category %s with parent %s" % (category.name, str(category.parent_id)))
229 if category.parent and not category.parent_id:
230 category.parent_id = category.parent.id
232 self.dao.tx_begin()
234 self.dao.save(category)
235 self._model_add_node(category)
236 self.dao.save(category)
238 self.dao.tx_commit()
240 self.nodes[category.id] = category
242 self._setup_node_signals(category)
243 self.emit("category-added", category)
245 def get_feed_items(self, id):
246 feed = self.lookup_feed(id)
247 feed.items = self.dao.get(Item, " WHERE feed_id = %d" % feed.id)
248 for item in feed.items:
249 item.feed = feed
250 item.connect("is-read-changed", feed.on_is_read_changed)
251 item.connect("is-read-changed", self.on_item_is_read_changed)
252 return feed.items
254 def get_category_items(self, id):
255 category = self.lookup_category(id)
257 if not category:
258 return None
260 def _get_children_feeds(category_id):
261 feed_ids = [str(node.id) for node in self.lookup_category(category_id).children if node.type == "F"]
262 children_categories = [node.id for node in self.lookup_category(category_id).children if node.type == "C"]
264 for ctg_id in children_categories:
265 feed_ids.extend(_get_children_feeds(ctg_id))
267 return feed_ids
269 in_str = ", ".join(_get_children_feeds(id))
271 items = self.dao.get(Item, " WHERE feed_id IN(%s) ORDER BY id" % in_str)
273 for item in items:
274 feed = self.lookup_feed(item.feed_id)
275 item.feed = feed
276 item.connect("is-read-changed", feed.on_is_read_changed)
277 item.connect("is-read-changed", self.on_item_is_read_changed)
279 return items
281 def feed_item_exists(self, item):
282 result = self.dao.get(Item, " WHERE feed_id = ? AND title = ?", (item.feed_id, item.title,))
283 return len(result) > 0
285 def on_item_is_read_changed(self, obj, is_read):
286 self.dao.save(obj)
288 def on_parent_changed(self, obj, old_parent):
289 debug("parent changed, saving %d" % (obj.id))
290 self.dao.save(obj)
292 def on_norder_changed(self, obj, old_norder):
293 debug("norder changed, saving %d" % (obj.id))
294 self.dao.save(obj)
296 _instance = None
298 def _get_instance():
299 global _instance
300 if not _instance:
301 _instance = FeedManager()
302 return _instance