Improve error handling; including flaky comms and failed auth. MB#5352
[hermes] / package / src / gui.py
1 #!/usr/bin/env python
2
3 import gtk, gobject
4 import gnome.gconf
5 import hildon, osso
6 import traceback
7 import time
8 import thread
9 import contactview
10 import urllib2  
11 from hermes import Hermes
12
13 gobject.threads_init()
14
15 class HermesGUI:
16   """Provides the GUI for Hermes, allowing the syncing of Facebook and
17      Twitter friends' information with the Evolution contacts' database.
18        
19      Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
20      Released under the Artistic Licence."""
21
22
23   # -----------------------------------------------------------------------
24   def __init__(self):
25     """Constructor. Initialises the gconf connection."""
26     self.gc = gnome.gconf.client_get_default()
27
28
29   # -----------------------------------------------------------------------
30   def doSync(self, widget, force, main = True):
31     if main and not self.get_use_facebook() and not self.get_use_twitter():
32       saved = self.open_prefs()
33       if saved == gtk.RESPONSE_DELETE_EVENT:
34         return
35
36     if main:
37       self.window.set_property('sensitive', False)
38       thread.start_new_thread(self.doSync, (widget, force, False))
39     else:
40       try:
41         fb2c = Hermes(self,
42                       twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None,
43                       facebook = self.get_use_facebook())
44         fb2c.load_friends()
45         fb2c.sync_contacts(resync = force)
46         gobject.idle_add(self.open_summary, fb2c)
47
48       except urllib2.HTTPError, e:
49         traceback.print_exc()
50         if e.code == 401:
51           gobject.idle_add(self.report_error, 'Authentication problem. Check credentials.', True)
52         else:
53           gobject.idle_add(self.report_error, 'Network connection error. Check connectivity.')
54
55       except urllib2.URLError, e:
56         traceback.print_exc()
57         gobject.idle_add(self.report_error, 'Network connection error. Check connectivity.')
58       
59       except Exception, e:
60         traceback.print_exc()
61         gobject.idle_add(self.report_error, 'Something went wrong: ' + e.message)
62
63
64   # -----------------------------------------------------------------------
65   def open_summary(self, fb2c):
66     gobject.idle_add(self.window.set_property, 'sensitive', True)
67
68     dialog = gtk.Dialog('Summary', self.window)
69     dialog.add_button('Done', gtk.RESPONSE_OK)
70     
71     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
72                            title = 'Updated %d contacts' % (len(fb2c.updated)))
73     button.connect('clicked', self.show_contacts, fb2c.updated)
74     button.set_property('sensitive', len(fb2c.updated) > 0)
75     dialog.vbox.add(button)
76     
77     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
78                            title = 'Matched %d contacts' % (len(fb2c.matched)))
79     button.connect('clicked', self.show_contacts, fb2c.matched)
80     button.set_property('sensitive', len(fb2c.matched) > 0)
81     dialog.vbox.add(button)
82     
83     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
84                            title = '%d contacts unmatched' % (len(fb2c.unmatched)))
85     button.connect('clicked', self.show_contacts, fb2c.unmatched)
86     button.set_property('sensitive', len(fb2c.unmatched) > 0)
87     dialog.vbox.add(button)
88     
89     dialog.show_all()
90     dialog.run()
91     dialog.hide()
92   
93
94   # -----------------------------------------------------------------------
95   def show_contacts(self, widget, contacts):
96     view = contactview.ContactView(contacts)
97
98     dialog = gtk.Dialog('Contacts', self.window)
99     #view.connect('contact-activated', self.map_contact)
100     dialog.vbox.add(view)
101     dialog.show_all()
102     
103     dialog.run()
104     dialog.hide()
105     
106     
107   # -----------------------------------------------------------------------
108   def map_contact(self, widget, contact):
109     print widget, contact
110
111
112   # -----------------------------------------------------------------------
113   def open_prefs(self, widget = None):
114     dialog = gtk.Dialog('Accounts', self.window)
115     dialog.add_button('Save', gtk.RESPONSE_OK)
116
117     use_facebook = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
118     use_facebook.set_label('Use Facebook')
119     use_facebook.set_active(self.get_use_facebook())
120     dialog.vbox.add(use_facebook)
121
122     use_twitter = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
123     use_twitter.set_label('Use Twitter')
124     use_twitter.set_active(self.get_use_twitter())
125     dialog.vbox.add(use_twitter)
126
127     tw_indent = gtk.HBox()
128     tw_user = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
129     tw_user.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
130     tw_user.set_placeholder("Twitter username")
131     tw_user.set_property('is-focus', False)
132     tw_user.set_text(self.get_twitter_credentials()[0])
133     self.sync_edit(use_twitter, tw_user)
134     use_twitter.connect('toggled', self.sync_edit, tw_user)
135     tw_indent.pack_start(tw_user, padding=48)
136     dialog.vbox.add(tw_indent)
137
138     tw_indent = gtk.HBox()
139     tw_pass = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
140     tw_pass.set_placeholder("Twitter password")
141     tw_pass.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL |
142                                               gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
143     tw_pass.set_text(self.get_twitter_credentials()[1])
144     self.sync_edit(use_twitter, tw_pass)
145     use_twitter.connect('toggled', self.sync_edit, tw_pass)
146     tw_indent.pack_start(tw_pass, padding=48)
147     dialog.vbox.add(tw_indent)
148
149     dialog.show_all()
150     result = dialog.run()
151     dialog.hide()
152     if result == gtk.RESPONSE_OK:
153       self.set_use_facebook(use_facebook.get_active())
154       self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text())
155
156     return result
157     
158     
159   # -----------------------------------------------------------------------
160   def sync_edit(self, use_twitter, edit):
161     edit.set_property('sensitive', use_twitter.get_active())
162
163
164   # -----------------------------------------------------------------------
165   def need_auth(self, main = False):
166     if main:
167       hildon.hildon_banner_show_information(self.window, '', "Need to authenticate with Facebook")
168     else:
169       gobject.idle_add(self.need_auth, True)
170     
171
172   # -----------------------------------------------------------------------
173   def block_for_auth(self, main = False, lock = None):
174     if main:
175       note = gtk.Dialog('Facebook authorisation', self.window)
176       note.add_button("Validate", gtk.RESPONSE_OK)
177       note.vbox.add(gtk.Label("\nPress 'Validate' once Facebook has\nbeen authenticated in web browser.\n"))
178
179       note.show_all()
180       result = note.run()
181       note.hide()
182       lock.release()
183       
184     else:
185       time.sleep(2)
186       lock = thread.allocate_lock()
187       lock.acquire()
188       gobject.idle_add(self.block_for_auth, True, lock)
189       lock.acquire()
190       lock.release()
191
192
193   # -----------------------------------------------------------------------
194   def progress(self, i, j, main = False):
195     if main:
196       if i == 0:
197         self.progressbar = gtk.ProgressBar()
198         self.progressnote = gtk.Dialog("Fetching friends' info", self.window)
199         self.progressnote.vbox.add(self.progressbar)
200         hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1)
201         
202         self.progressnote.show_all()
203         
204       elif i < j:
205         if i == 1:
206           self.progressnote.set_title("Updating contacts")
207           hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0)
208           
209         self.progressbar.set_fraction(float(i) / float(j))
210         
211       else:
212         self.progressnote.destroy()
213         
214       print i,j
215     else:
216       gobject.idle_add(self.progress, i, j, True)
217
218
219   # -----------------------------------------------------------------------
220   def report_error(self, e, prefs = False):
221     if self.progressnote:
222       self.window.set_property('sensitive', True)
223       self.progressnote.destroy()
224
225     hildon.hildon_banner_show_information(self.window, '', e)
226     if prefs:
227       self.open_prefs()
228
229
230   # -----------------------------------------------------------------------
231   def main(self):
232     # -- Window and app...
233     #
234     self.app = hildon.Program()
235     self.window = hildon.Window()
236     gtk.set_application_name('Hermes')
237     self.osso_context = osso.Context('org.maemo.hermes', '0.0.5', False)
238     self.app.add_window(self.window)
239
240     self.window.connect("delete-event", gtk.main_quit)
241
242     # -- Main window buttons...
243     #
244     box = gtk.HButtonBox()
245     self.window.add(box)
246     
247     box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
248     button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
249                            title = 'Retrieve', value = "Get contacts' missing info")
250     button.set_property('width-request', 250)
251     button.connect('clicked', self.doSync, False)
252     box.add(button)
253
254     button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
255                            title = 'Update', value = "Update contacts' info")
256     button.set_property('width-request', 250)
257     button.connect('clicked', self.doSync, True)
258     box.add(button)
259     
260     # -- Application menu...
261     #
262     menu = hildon.AppMenu()
263     button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
264     button.set_label("Accounts")
265     button.connect("clicked", self.open_prefs)
266     menu.append(button)
267     menu.show_all()
268     self.window.set_app_menu(menu)
269   
270     self.window.show_all()
271     gtk.main()
272
273
274   def get_use_facebook(self):
275     return self.gc.get_bool("/apps/maemo/hermes/use_facebook")
276
277
278   def set_use_facebook(self, value):
279     self.gc.set_bool("/apps/maemo/hermes/use_facebook", value)
280
281
282   def get_use_twitter(self):
283     return self.gc.get_bool("/apps/maemo/hermes/use_twitter")
284
285
286   def set_use_twitter(self, value, user, password):
287     self.gc.set_bool("/apps/maemo/hermes/use_twitter", value)
288     self.gc.set_string("/apps/maemo/hermes/twitter_user", user)
289     self.gc.set_string("/apps/maemo/hermes/twitter_pwd", password)
290     
291   
292   def get_twitter_credentials(self):
293     return (self.gc.get_string("/apps/maemo/hermes/twitter_user") or '',
294             self.gc.get_string("/apps/maemo/hermes/twitter_pwd") or '')
295
296
297 # -------------------------------------------------------------------------
298 if __name__ == '__main__':
299   HermesGUI().main()
300