Work on feed discovery dialog.
[straw/fork.git] / straw / subscribe.py
blob659b87695614641cdc60bb6285923034fbd589dc
1 """ subscribe.py
3 Module for handling feed subscription process.
4 """
5 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
6 __license__ = """ GNU General Public License
8 This program is free software; you can redistribute it and/or modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation; either version 2 of the License, or (at your option) any later
11 version.
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along with
18 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 Place - Suite 330, Boston, MA 02111-1307, USA. """
22 from gtk.glade import XML
23 from straw import helpers
24 import Config
25 import FeedManager
26 import MVP
27 import SummaryParser
28 import TreeViewManager
29 import error
30 import gettext
31 import gobject
32 import gtk
33 import os, os.path
34 import pygtk
35 import straw
36 import time
37 import urllib
38 import urlparse
39 import xml
41 pygtk.require('2.0')
43 STATE_INTRO = 1
44 STATE_FINISH = 2
46 class SubscribeView(MVP.GladeView):
47 def _initialize(self):
48 self._window = self._widget.get_widget('subscribe_dialog')
49 self._button1 = self._widget.get_widget('button1')
50 self._button2 = self._widget.get_widget('button2')
51 self._button3 = self._widget.get_widget('button3')
52 self._progress_bar = self._widget.get_widget('progress_bar')
53 self._notebook = self._widget.get_widget('notebook')
54 self._location_entry = self._widget.get_widget('feed_location_entry')
55 self._username_entry = self._widget.get_widget('username_entry')
56 self._password_entry = self._widget.get_widget('password_entry')
57 self._category_cb = self._widget.get_widget('category_cb')
58 self._error_text = self._widget.get_widget('error_text')
59 self._error_box = self._widget.get_widget('error_box')
60 self._result_tree = self._widget.get_widget('result_tree')
62 def _populate_tree(self, parent_id, path = ""):
63 if not self.nodes.has_key(parent_id):
64 return
66 for node in self.nodes[parent_id]:
67 if node.type == "C":
68 #current_parent = self._create_row(node, parent_iter, path)
69 if path != "":
70 path = path + " > " + node.title
71 else:
72 path = node.title
74 self.category_store.append([path])
76 if self.nodes.has_key(node.obj_id):
77 self._populate_tree(node.obj_id, path)
79 def _on_forward(self):
80 location = self._location_entry.get_text()
81 if not location:
82 self.display_error(_("Feed Location must not be empty"))
83 return False
84 #location = "file:///home/ppawel/Desktop/test.rss"
85 username = self._username_entry.get_text()
86 username = username.strip()
87 password = self._password_entry.get_text()
88 password = password.strip()
90 config = Config.get_instance()
91 if config.offline:
92 self._window.hide()
93 response = helpers.report_offline_status(self._window)
94 if response == gtk.RESPONSE_CANCEL:
95 self.show()
96 return False
97 config.offline = not config.offline
99 import FeedDiscovery
100 FeedDiscovery.discover(location, { "job-done": [ self._discovery_finished ] })
102 #self._presenter.subscribe(location, username, password)
103 return True
105 def progress_pulse(self, obj):
106 self._progress_bar.pulse()
107 return True
109 def progress_destroy(self):
110 if self.timer != 0:
111 gobject.source_remove(self.timer)
112 self.timer = 0
114 def _setup_entries(self):
115 self._location_entry.set_text("http://")
116 self._username_entry.delete_text(0,-1)
117 self._password_entry.delete_text(0,-1)
119 self._location_entry.grab_focus()
121 def show(self):
122 self.state = STATE_INTRO
123 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
124 self.timer = 0
126 self._setup_entries()
127 self._error_box.set_property('visible',False)
128 self._notebook.set_current_page(0)
129 self._window.show()
131 renderer = gtk.CellRendererToggle()
132 column = gtk.TreeViewColumn(_('Keep'), renderer, active = 0)
133 self._result_tree.append_column(column)
134 #renderer.connect('toggled', self._sticky_toggled)
136 renderer = gtk.CellRendererText()
137 column = gtk.TreeViewColumn(_('_Title'), renderer, markup = 1)
138 self._result_tree.append_column(column)
140 self.store = gtk.ListStore(bool, str)
142 self._result_tree.set_model(self.store)
144 self.setup_combobox()
146 def setup_combobox(self):
147 self.category_store = gtk.ListStore(str)
148 cell = gtk.CellRendererText()
149 self._category_cb.pack_start(cell, True)
150 self._category_cb.add_attribute(cell, 'text', 0)
152 self.categories, self.feeds = FeedManager.get_model()
153 self.nodes = TreeViewManager.get_nodes()
155 self._category_cb.set_model(self.category_store)
157 def display_error(self, text):
158 self._error_box.set_property('visible',True)
159 self._error_text.set_text(text)
161 def set_location_entry(self, location):
162 self._location_entry.set_text(location)
164 def set_parent(self, parent):
165 self._window.set_transient_for(parent)
167 def _on_button1_clicked(self, *args):
168 self.progress_destroy()
169 self._window.hide()
171 def _on_button2_clicked(self, *args):
172 if self.state == STATE_INTRO:
173 if self._on_forward():
174 self._progress_bar.props.visible = True
175 self.timer = gobject.timeout_add(100, self.progress_pulse, self)
176 self._button2.set_sensitive(False)
177 elif self.state == STATE_FINISH:
178 self.state = STATE_INTRO
179 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
180 self._notebook.set_current_page(0)
182 def _on_button3_clicked(self, *args):
183 if self.state == STATE_FINISH:
184 pass
186 def _discovery_finished(self, handler, feeds):
187 gtk.gdk.threads_enter()
189 for feed in feeds:
190 label = xml.sax.saxutils.escape(feed.title) + '\n<span size="smaller">' + xml.sax.saxutils.escape(feed.location) + '</span>'
191 self.store.append([False, label])
193 self._progress_bar.props.visible = False
194 self._button2.set_sensitive(True)
195 self.progress_destroy()
196 self.state = STATE_FINISH
197 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_BACK, gtk.STOCK_APPLY)
198 self._notebook.set_current_page(1)
200 self._populate_tree(1)
201 self._category_cb.set_active(0)
203 gtk.gdk.threads_leave()
205 def set_state(self, b1, b2, b3):
206 if b1 == None:
207 self._button1.hide()
208 else:
209 self._button1.set_label(b1)
210 self._button1.show()
212 if b2 == None:
213 self._button2.hide()
214 else:
215 self._button2.set_label(b2)
216 self._button2.show()
218 if b3 == None:
219 self._button3.hide()
220 else:
221 self._button3.set_label(b3)
222 self._button3.show()
224 def _on_feed_location_entry_key_press_event(self, widget, event):
225 if event.keyval == gtk.keysyms.Return:
226 self._presenter.subscribe(self._location_entry.get_text())
228 class SubscribePresenter(MVP.BasicPresenter):
230 def _normalize(self, url):
231 u = urlparse.urlsplit(url.strip())
232 # we check if 'scheme' is not empty here because URIs like
233 # "google.com" IS valid but, in this case, 'scheme' is empty because
234 # urlsplit() expects urls are in the format of scheme://netloc/...
235 if not u[0] or (u[0] != "http" and u[0] != "feed"):
236 return None
237 if u[0] == 'feed':
238 u = urlparse.urlsplit(u[2])
239 # .. if that happens then we munge the url by adding a // and assume
240 # that 'http' is the scheme.
241 if u[1] == '':
242 u = urlparse.urlsplit("//" + url, 'http')
243 return u
245 def subscribe(self, location, username=None, password=None):
246 self._username = username
247 self._password = password
248 self._location = location
250 import FeedManager
251 import model
253 feed = model.Feed()
254 feed.location = location
255 feed.title = location
256 FeedManager.save_feed(feed)
258 def set_parent(self, parent):
259 self.view.set_parent(parent)
261 def get_credential(self):
262 return (self._location, self._username, self._password)
264 def show(self, url=None):
265 if url:
266 self.view.set_location_entry(url)
267 self.view.show()
269 # def set_url_dnd(self, url):
270 # self._window.show()
271 # self._feed_location_presenter.view.find_feed(url)
273 def set_location_from_clipboard(self):
274 def _clipboard_cb(cboard, url, data=None):
275 if url:
276 url = self._normalize(url)
277 if url and url[0] == "http":
278 url = urlparse.urlunsplit(url)
279 self.view.set_location_entry(url)
280 clipboard = gtk.clipboard_get(selection="CLIPBOARD")
281 clipboard.request_text(_clipboard_cb, None)
283 class SubscribeConsumer:
285 def __init__(self, presenter):
286 self._presenter = presenter
288 def http_results(self, status, data):
289 self.process(data)
291 def process(self, data):
292 url, user, password = self._presenter.get_credential()
293 feed = feeds.Feed.create_new_feed("", url, user, password)
294 parsed = SummaryParser.parse(data, feed)
295 if parsed.title == "":
296 # store url loc in title in case it's empty
297 parsed.title = urlparse.urlsplit(url.strip())[1]
298 feed.title = helpers.convert_entities(parsed.title)
299 feed.channel_description = helpers.convert_entities(parsed.description)
300 feed.access_info = (url, user, password)
301 feed.last_poll = int(time.time())
302 fclist = feeds.category_list
303 fclist.append_feed(feed)
304 feed.router.route_all(parsed)
305 feed.poll_done()
307 def http_failed(self, exception):
308 # XXX signal to the user that subscription failed? incl. erros
309 print "subscribe failed", exception
311 def operation_stopped(self):
312 # XXX signal that it stopped, why it stopped, and stop all progress
313 # bars, etc...
314 print "subscribe operation stopped"
316 def subscribe_show(url=None, parent=None):
317 gladefile = XML(os.path.join(straw.STRAW_DATA_DIR, "subscribe.glade"))
318 _dialog = SubscribePresenter(view=SubscribeView(gladefile))
319 _dialog.set_parent(parent)
320 _dialog.set_location_from_clipboard()
321 _dialog.show()