Merge lp:~dobey/ubuntuone-client/http-threaded into lp:ubuntuone-client

Proposed by dobey
Status: Merged
Approved by: Tim Cole
Approved revision: 454
Merged at revision: not available
Proposed branch: lp:~dobey/ubuntuone-client/http-threaded
Merge into: lp:ubuntuone-client
Diff against target: 193 lines (+56/-35)
2 files modified
bin/ubuntuone-preferences (+54/-33)
ubuntuone/oauthdesktop/main.py (+2/-2)
To merge this branch: bzr merge lp:~dobey/ubuntuone-client/http-threaded
Reviewer Review Type Date Requested Status
Joshua Hoover (community) ran on lucid Approve
Tim Cole (community) Approve
Natalia Bidart (community) Approve
Review via email: mp+22464@code.launchpad.net

Commit message

Fix the HTTP REST calls to load from threads, instead of GIOChannels
  - This avoids blocking the main loop for network activity
Fix the callback handlers for the REST calls to use obj.get method
  - Avoids potential KeyError conditions
Fix oauthdesktop to call ensure_access_token in a timeout instead of thread
  - Avoids threadlock due to changes in libgnome-keyring

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

There is a lint issue:

== Python Lint Notices ==

./bin/ubuntuone-preferences:
    27: 'glib' imported but unused

454. By dobey

Fix the lint warning

Revision history for this message
Natalia Bidart (nataliabidart) :
review: Approve
Revision history for this message
Tim Cole (tcole) wrote :

Hmm... this should work I think. As an alternative to calling callback(result), you might want to consider using g_add_idle to have the callback run in the event thread; that may make things less surprising down the road.

review: Approve
Revision history for this message
Tim Cole (tcole) wrote :

Well. Not sure it's necessary going through add_idle here. But do consider using the gtk.gdk.lock context manager rather than explicit calls to threads_enter/threads_exit; it's probably more important here than in the other branch, since a generic callback is (in principle) more likely to raise an exception.

Revision history for this message
Joshua Hoover (joshuahoover) wrote :

Tested on Lucid. Performance is much improved versus previous branches where interface froze for up to 30 seconds after launching. Worked even when network was turned off.

review: Approve (ran on lucid)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/ubuntuone-preferences'
2--- bin/ubuntuone-preferences 2010-03-29 22:50:39 +0000
3+++ bin/ubuntuone-preferences 2010-03-30 17:34:28 +0000
4@@ -24,12 +24,12 @@
5 import pygtk
6 pygtk.require('2.0')
7 import gobject
8-import glib
9 import gtk
10 import os
11 import gettext
12 import gnomekeyring
13 import simplejson
14+from threading import Thread
15 from oauth import oauth
16 from ubuntuone import clientdefs
17 from ubuntuone.syncdaemon.tools import SyncDaemonTool
18@@ -97,26 +97,11 @@
19 secret = items[0].secret
20 return oauth.OAuthToken.from_string(secret)
21
22-def rest_response_handler(source, condition, conn, callback):
23+def do_rest_request(url, method, token, callback):
24 """
25 Helper that handles the REST response.
26 """
27 result = None
28- response = conn.getresponse() # shouldn't block
29- if response.status == 200:
30- data = response.read() # neither should this
31- result = simplejson.loads(data)
32- else:
33- result = response.reason
34- callback(result)
35- return False
36-
37-def make_rest_request(url=None, method='GET',
38- callback=None, keyring=None):
39- """
40- Helper that makes an oauth-wrapped REST request.
41- """
42- token = get_access_token(keyring)
43 consumer = oauth.OAuthConsumer('ubuntuone', 'hammertime')
44
45 oauth_request = oauth.OAuthRequest.from_consumer_and_token(
46@@ -133,13 +118,27 @@
47 conn = httplib.HTTPSConnection(netloc)
48 try:
49 conn.request(method, path, headers=oauth_request.to_header())
50- except socket.error:
51- callback(None)
52- else:
53- glib.io_add_watch(
54- conn.sock,
55- glib.IO_IN | glib.IO_PRI | glib.IO_ERR | glib.IO_HUP,
56- rest_response_handler, conn, callback)
57+ response = conn.getresponse() # shouldn't block
58+ if response.status == 200:
59+ data = response.read() # neither should this
60+ result = simplejson.loads(data)
61+ else:
62+ result = {'status' : response.status,
63+ 'reason' : response.reason}
64+ except socket.error, e:
65+ result = {'error' : e}
66+
67+ gtk.gdk.threads_enter()
68+ callback(result)
69+ gtk.gdk.threads_leave()
70+
71+def make_rest_request(url=None, method='GET',
72+ callback=None, keyring=None):
73+ """
74+ Helper that makes an oauth-wrapped REST request.
75+ """
76+ token = get_access_token(keyring)
77+ Thread(target=do_rest_request, args=(url, method, token, callback)).start()
78
79
80 class DevicesWidget(gtk.Table):
81@@ -301,12 +300,27 @@
82 """
83 Parse the list of devices, and hook up list_devices if it worked.
84 """
85- if result:
86+ is_error = False
87+ error = None
88+ if result and isinstance(result, list):
89 self.devices = result
90- gobject.idle_add(self.list_devices)
91+ elif isinstance(result, dict):
92+ error = result.get('error', None)
93+ if not error and result.get('status', None):
94+ is_error = True
95+ error = result.get('reason', None)
96+ else:
97+ is_error = True
98 else:
99- self.clear_devices_view()
100- self.error(result)
101+ error = "Got empty result for devices list."
102+ is_error = True
103+
104+ if is_error:
105+ self.devices = None
106+ self.error(error)
107+
108+ gobject.idle_add(self.list_devices)
109+
110
111 def clear_devices_view(self):
112 """
113@@ -330,8 +344,6 @@
114 If the list of devices is empty, make a fake one that refers
115 to the local machine (to get the connect/restart buttons).
116 """
117- self.resize(len(self.devices)+1, 3)
118-
119 self.clear_devices_view()
120
121 token = get_access_token(self.keyring)
122@@ -342,6 +354,8 @@
123 'description': _("<LOCAL MACHINE>"),
124 'token': token.key,
125 'FAKE': 'YES'}]
126+ else:
127+ self.resize(len(self.devices)+1, 3)
128
129 self.status_label.set_label("")
130
131@@ -599,7 +613,9 @@
132 def got_quota_info(self, quota):
133 """Handle the result from the quota REST call."""
134 if quota:
135- self.update_quota_display(quota['used'], quota['total'])
136+ used = quota.get('used', 0)
137+ total = quota.get('total', 2)
138+ self.update_quota_display(used, total)
139
140 def request_quota_info(self):
141 """Request new quota info from server, and update display."""
142@@ -610,8 +626,8 @@
143 def got_account_info(self, user):
144 """Handle the result from the account REST call."""
145 if user:
146- self.name_label.set_text(user['nickname'])
147- self.mail_label.set_text(user['email'])
148+ self.name_label.set_text(user.get('nickname', _("Unknown")))
149+ self.mail_label.set_text(user.get('email', _("Unknown")))
150
151 def request_account_info(self):
152 """Request account info from server, and update display."""
153@@ -994,13 +1010,18 @@
154 gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
155 gettext.textdomain(clientdefs.GETTEXT_PACKAGE)
156
157+ gobject.threads_init()
158+ gtk.gdk.threads_init()
159+
160 gtk.rc_parse_string(RCSTYLE)
161 gobject.set_application_name("ubuntuone-preferences")
162
163 try:
164+ gtk.gdk.threads_enter()
165 login = UbuntuoneLoginHandler()
166 login.register_signal_handlers()
167 login.do_login_check()
168 gtk.main()
169+ gtk.gdk.threads_leave()
170 except KeyboardInterrupt:
171 pass
172
173=== modified file 'ubuntuone/oauthdesktop/main.py'
174--- ubuntuone/oauthdesktop/main.py 2010-01-18 12:29:29 +0000
175+++ ubuntuone/oauthdesktop/main.py 2010-03-30 17:34:28 +0000
176@@ -25,7 +25,7 @@
177 signal so they can retrieve the new token.
178 """
179
180-import dbus.service, urlparse, time
181+import dbus.service, urlparse, time, gobject
182 import pynotify
183
184 from dbus.mainloop.glib import DBusGMainLoop
185@@ -91,7 +91,7 @@
186 do_login=do_login)
187
188 logger.debug("Calling auth.client.ensure_access_token in thread")
189- deferToThread(client.ensure_access_token).addErrback(self.error_handler)
190+ gobject.timeout_add_seconds(1, client.ensure_access_token)
191
192 def clear_token(self, realm, consumer_key):
193 """Remove the currently stored OAuth token from the keyring."""

Subscribers

People subscribed via source and target branches