Merge lp:~chipaca/ubuntuone-client/devices-tab into lp:ubuntuone-client
- devices-tab
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~chipaca/ubuntuone-client/devices-tab |
Merge into: | lp:ubuntuone-client |
Diff against target: |
917 lines (+514/-216) 3 files modified
bin/ubuntuone-preferences (+373/-163) tests/test_preferences.py (+139/-53) ubuntuone/syncdaemon/dbus_interface.py (+2/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntuone-client/devices-tab |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Needs Fixing | ||
Review via email: mp+20608@code.launchpad.net |
This proposal has been superseded by a proposal from 2010-03-05.
Commit message
Add other devices and management ui to Devices tab
Description of the change
Eric Casteleijn (thisfred) wrote : | # |
Eric Casteleijn (thisfred) wrote : | # |
=======
[ERROR]: tests.test_
Traceback (most recent call last):
File "/home/
result = test_method()
File "/home/
dialog = self.u1prefs.
File "/home/
self.
File "/home/
self.
File "/home/
token = get_access_token()
File "/home/
'oauth-
gnomekeyring.
=======
[ERROR]: tests.test_
Traceback (most recent call last):
File "/home/
result = test_method()
File "/home/
dialog = self.u1prefs.
File "/home/
self.
File "/home/
self.
File "/home/
token = get_access_token()
File "/home/
'oauth-
gnomekeyring.
=======
[ERROR]: tests.test_
Traceback (most recent call last):
File "/home/
result = test_method()
File "/home/
dialog = self.u1prefs.
File "/home/
self.
File "/home/
self.
File "/home/
token = get_access_token()
File "/home/
'oauth-
- 403. By John Lenton
-
merged with trunk
- 404. By John Lenton
-
merged with trunk (again)
- 405. By John Lenton
-
minor refactoring (to get tests working again) and tests. Still TODO: test the REST calls are done
- 406. By John Lenton
-
worked around a bug in the rest api
- 407. By John Lenton
-
fixed issues raised by dobey
- 408. By John Lenton
-
fixed issues raised by nessa
- 409. By John Lenton
-
more changed by dobey (and some older ones)
Unmerged revisions
Preview Diff
1 | === modified file 'bin/ubuntuone-preferences' |
2 | --- bin/ubuntuone-preferences 2010-03-01 22:10:22 +0000 |
3 | +++ bin/ubuntuone-preferences 2010-03-05 17:04:28 +0000 |
4 | @@ -23,6 +23,7 @@ |
5 | import pygtk |
6 | pygtk.require('2.0') |
7 | import gobject |
8 | +import glib |
9 | import gtk |
10 | import os |
11 | import gettext |
12 | @@ -32,6 +33,11 @@ |
13 | from oauth import oauth |
14 | from ubuntuone import clientdefs |
15 | from ubuntuone.syncdaemon.tools import SyncDaemonTool |
16 | +from ubuntuone.syncdaemon.logger import LOGFOLDER |
17 | + |
18 | +import logging |
19 | +import sys |
20 | +import httplib, urlparse, socket |
21 | |
22 | import dbus.service |
23 | from ConfigParser import ConfigParser |
24 | @@ -39,6 +45,11 @@ |
25 | from dbus.mainloop.glib import DBusGMainLoop |
26 | from xdg.BaseDirectory import xdg_config_home |
27 | |
28 | +logging.basicConfig( |
29 | + filename=os.path.join(LOGFOLDER, 'u1-prefs.log'), |
30 | + level=logging.DEBUG, |
31 | + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") |
32 | +logger = logging.getLogger("ubuntuone-preferences") |
33 | DBusGMainLoop(set_as_default=True) |
34 | |
35 | _ = gettext.gettext |
36 | @@ -65,10 +76,358 @@ |
37 | pass |
38 | |
39 | |
40 | +def get_access_token(keyring): |
41 | + items = [] |
42 | + items = keyring.find_items_sync( |
43 | + keyring.ITEM_GENERIC_SECRET, |
44 | + {'ubuntuone-realm': "https://ubuntuone.com", |
45 | + 'oauth-consumer-key': 'ubuntuone'}) |
46 | + secret = items[0].secret |
47 | + return oauth.OAuthToken.from_string(secret) |
48 | + |
49 | + |
50 | +class DevicesWidget(gtk.Table): |
51 | + """ |
52 | + the Devices tab |
53 | + """ |
54 | + def __init__(self, |
55 | + bus, |
56 | + keyring=gnomekeyring, |
57 | + realm='https://ubuntuone.com', |
58 | + consumer_key='ubuntuone', |
59 | + url='https://one.ubuntu.com/api/1.0/devices/'): |
60 | + super(DevicesWidget, self).__init__(rows=2, columns=3) |
61 | + self.bus = bus |
62 | + self.keyring = keyring |
63 | + self.sdtool = SyncDaemonTool(bus) |
64 | + self.set_border_width(6) |
65 | + self.set_row_spacings(6) |
66 | + self.set_col_spacings(6) |
67 | + self.devices = None |
68 | + self.realm = realm |
69 | + self.consumer_key = consumer_key |
70 | + self.base_url = url |
71 | + self.conn = None |
72 | + self.consumer = None |
73 | + self.table_widgets = [] |
74 | + |
75 | + self.connected = None # i.e. unknown |
76 | + self.conn_btn = None |
77 | + self.up_spin = None |
78 | + self.dn_spin = None |
79 | + self.bw_chk = None |
80 | + self.bw_limited = False |
81 | + self.up_limit = 2097152 |
82 | + self.dn_limit = 2097152 |
83 | + |
84 | + self._update_id = 0 |
85 | + |
86 | + self.status_label = gtk.Label("Please wait...") |
87 | + self.attach(self.status_label, 0, 3, 2, 3) |
88 | + |
89 | + self.description = gtk.Label("The devices connected to with your personal cloud network are listed below") |
90 | + self.description.set_alignment(0., .5) |
91 | + self.description.set_line_wrap(True) |
92 | + self.attach(self.description, 0, 3, 0, 1, xpadding=12, ypadding=12) |
93 | + |
94 | + def update_bw_settings(self): |
95 | + """ |
96 | + Push the bandwidth settings at syncdaemon |
97 | + """ |
98 | + try: |
99 | + client = self.bus.get_object(DBUS_IFACE_NAME, "/config", |
100 | + follow_name_owner_changes=True) |
101 | + iface = dbus.Interface(client, DBUS_IFACE_CONFIG_NAME) |
102 | + iface.set_throttling_limits(self.dn_limit, self.up_limit, |
103 | + reply_handler=dbus_async, |
104 | + error_handler=self.error) |
105 | + if self.bw_limited: |
106 | + iface.enable_bandwidth_throttling(reply_handler=dbus_async, |
107 | + error_handler=self.error) |
108 | + else: |
109 | + iface.disable_bandwidth_throttling(reply_handler=dbus_async, |
110 | + error_handler=self.error) |
111 | + except DBusException, e: |
112 | + self.error(str(e)) |
113 | + |
114 | + def handle_bw_controls_changed(self, *a): |
115 | + """ |
116 | + Sync the bandwidth throttling model with the view. |
117 | + |
118 | + Start a timer to sync with syncdaemon too. |
119 | + """ |
120 | + # Remove the timeout ... |
121 | + if self._update_id != 0: |
122 | + gobject.source_remove(self._update_id) |
123 | + |
124 | + # sync the model ... |
125 | + self.bw_limited = self.bw_chk.get_active() |
126 | + self.up_limit = self.up_spin.get_value_as_int() * 1024 |
127 | + self.dn_limit = self.dn_spin.get_value_as_int() * 1024 |
128 | + |
129 | + # ... and add the timeout back |
130 | + self._update_id = gobject.timeout_add_seconds( |
131 | + 1, self.update_bw_settings) |
132 | + |
133 | + def handle_bw_checkbox_toggled(self, checkbox, *widgets): |
134 | + """ |
135 | + Callback for the bandwidth togglebutton |
136 | + """ |
137 | + active = checkbox.get_active() |
138 | + for widget in widgets: |
139 | + widget.set_sensitive(active) |
140 | + self.handle_bw_controls_changed() |
141 | + |
142 | + def handle_limits(self, limits): |
143 | + """ |
144 | + Callback for when syncdaemon tells us its throttling limits |
145 | + """ |
146 | + self.up_limit = int(limits['upload']) |
147 | + self.dn_limit = int(limits['download']) |
148 | + if self.up_spin is not None and self.dn_spin is not None: |
149 | + self.up_spin.set_value(self.up_limit / 1024) |
150 | + self.dn_spin.set_value(self.dn_limit / 1024) |
151 | + |
152 | + def handle_throttling_enabled(self, enabled): |
153 | + """ |
154 | + Callback for when syncdaemon tells us whether throttling is enabled |
155 | + """ |
156 | + self.bw_limited = enabled |
157 | + if self.bw_chk is not None: |
158 | + self.bw_chk.set_active(enabled) |
159 | + |
160 | + def handle_state_change(self, new_state): |
161 | + """ |
162 | + Callback for when syncdaemon's state changes |
163 | + """ |
164 | + if new_state['is_error']: |
165 | + # this syncdaemon isn't going to connect no more |
166 | + self.connected = None |
167 | + else: |
168 | + self.connected = new_state['is_connected'] |
169 | + if self.conn_btn is not None: |
170 | + if self.connected: |
171 | + self.conn_btn.set_label(_("_Disconnect")) |
172 | + else: |
173 | + self.conn_btn.set_label(_("_Connect")) |
174 | + if self.connected is None: |
175 | + self.conn_btn.set_sensitive(False) |
176 | + else: |
177 | + self.conn_btn.set_sensitive(True) |
178 | + |
179 | + def error(self, msg): |
180 | + """ |
181 | + Clear the table and show the error message in its place. |
182 | + |
183 | + This might be better as an error dialo. |
184 | + """ |
185 | + self.clear_devices_view() |
186 | + self.status_label.set_markup("<b>Error:</b> %s" % msg) |
187 | + |
188 | + def request(self, path='', method='GET'): |
189 | + """ |
190 | + Helper that makes an oauth-wrapped rest request. |
191 | + |
192 | + XXX duplication with request_REST_info (but this one should be async) |
193 | + """ |
194 | + url = self.base_url + path |
195 | + |
196 | + token = get_access_token(self.keyring) |
197 | + |
198 | + oauth_request = oauth.OAuthRequest.from_consumer_and_token( |
199 | + http_url=url, |
200 | + http_method=method, |
201 | + oauth_consumer=self.consumer, |
202 | + token=token, |
203 | + parameters='') |
204 | + oauth_request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), |
205 | + self.consumer, token) |
206 | + |
207 | + scheme, netloc, path, query, fragment = urlparse.urlsplit(url) |
208 | + |
209 | + conn = httplib.HTTPSConnection(netloc) |
210 | + try: |
211 | + conn.request(method, path, headers=oauth_request.to_header()) |
212 | + except socket.error: |
213 | + return None |
214 | + return conn |
215 | + |
216 | + def get_devices(self): |
217 | + """ |
218 | + Ask the server for a list of devices, and hook up |
219 | + parse_devices to run on the result (when it gets here). |
220 | + """ |
221 | + try: |
222 | + token = get_access_token(self.keyring) |
223 | + except gnomekeyring.NoMatchError: |
224 | + self.error("No token in the keyring") |
225 | + self.devices = [] |
226 | + else: |
227 | + self.consumer = oauth.OAuthConsumer("ubuntuone", "hammertime") |
228 | + |
229 | + self.conn = self.request() |
230 | + if self.conn is None: |
231 | + self.clear_devices_view() |
232 | + self.error('Unable to connect') |
233 | + else: |
234 | + glib.io_add_watch( |
235 | + self.conn.sock, |
236 | + glib.IO_IN | glib.IO_PRI | glib.IO_ERR | glib.IO_HUP, |
237 | + self.parse_devices) |
238 | + |
239 | + def parse_devices(self, *a): |
240 | + """ |
241 | + Parse the list of devices, and hook up list_devices if it worked |
242 | + """ |
243 | + response = self.conn.getresponse() # shouldn't block |
244 | + if response.status == 200: |
245 | + response = response.read() # neither shouold this |
246 | + self.devices = simplejson.loads(response) |
247 | + gobject.idle_add(self.list_devices) |
248 | + else: |
249 | + self.clear_devices_view() |
250 | + self.error(response.reason) |
251 | + return False |
252 | + |
253 | + def clear_devices_view(self): |
254 | + """ |
255 | + Clear out all the widgets except from the table, the |
256 | + description and the status_label |
257 | + """ |
258 | + for i in self.get_children(): |
259 | + if i not in (self.description, self.status_label): |
260 | + i.destroy() |
261 | + self.conn_btn = None |
262 | + self.up_spin = None |
263 | + self.dn_spin = None |
264 | + self.bw_chk = None |
265 | + |
266 | + def list_devices(self): |
267 | + """ |
268 | + Populate the table with the list of devices. |
269 | + |
270 | + If the list of devices is empty, make a fake one that refers |
271 | + to the local machine (to get the connect/restart buttons). |
272 | + """ |
273 | + self.resize(len(self.devices)+1, 3) |
274 | + |
275 | + self.clear_devices_view() |
276 | + |
277 | + token = get_access_token(self.keyring) |
278 | + |
279 | + if not self.devices: |
280 | + # a stopgap device so you can at least try to connect |
281 | + self.devices = [{'kind': 'Computer', |
282 | + 'description': _("<LOCAL MACHINE>"), |
283 | + 'token': token.key, |
284 | + 'FAKE': 'YES'}] |
285 | + |
286 | + name_label = gtk.Label() |
287 | + name_label.set_markup("<b>Name</b>") |
288 | + name_label.set_alignment(0., .5) |
289 | + self.attach(name_label, 1, 2, 1, 2) |
290 | + self.status_label.set_label("") |
291 | + |
292 | + i = 1 |
293 | + for row in self.devices: |
294 | + i += 1 |
295 | + img = gtk.Image() |
296 | + img.set_from_icon_name(row['kind'].lower(), gtk.ICON_SIZE_DND) |
297 | + desc = gtk.Label(row['description']) |
298 | + desc.set_alignment(0., .5) |
299 | + self.attach(img, 0, 1, i, i+1) |
300 | + self.attach(desc, 1, 2, i, i+1) |
301 | + if 'FAKE' not in row: |
302 | + # we don't include the "Remove" button for the fake entry :) |
303 | + butn = gtk.Button('Remove') |
304 | + butn.connect('clicked', self.remove, |
305 | + row['kind'], row['token']) |
306 | + self.attach(butn, 2, 3, i, i+1, xoptions=0, yoptions=0) |
307 | + if row['token'] == token.key: |
308 | + self.bw_chk = ck_btn = gtk.CheckButton( |
309 | + _("_Limit Bandwidth Usage")) |
310 | + ck_btn.set_active(self.bw_limited) |
311 | + up_lbl = gtk.Label(_("Upload (kB/s):")) |
312 | + up_lbl.set_alignment(0., .5) |
313 | + adj = gtk.Adjustment(value=self.up_limit/1024., |
314 | + lower=0.0, upper=4096.0, |
315 | + step_incr=1.0, page_incr=16.0) |
316 | + self.up_spin = up_btn = gtk.SpinButton(adj) |
317 | + up_btn.connect("value-changed", self.handle_bw_controls_changed) |
318 | + dn_lbl = gtk.Label(_("Download (kB/s):")) |
319 | + dn_lbl.set_alignment(0., .5) |
320 | + adj = gtk.Adjustment(value=self.dn_limit/1024., |
321 | + lower=0.0, upper=4096.0, |
322 | + step_incr=1.0, page_incr=16.0) |
323 | + self.dn_spin = dn_btn = gtk.SpinButton(adj) |
324 | + dn_btn.connect("value-changed", self.handle_bw_controls_changed) |
325 | + ck_btn.connect('toggled', self.handle_bw_checkbox_toggled, |
326 | + up_lbl, up_btn, dn_lbl, dn_btn) |
327 | + self.handle_bw_checkbox_toggled(ck_btn, |
328 | + up_lbl, up_btn, dn_lbl, dn_btn) |
329 | + |
330 | + self.conn_btn = gtk.Button(_('_Connect')) |
331 | + if self.connected is None: |
332 | + self.conn_btn.set_sensitive(False) |
333 | + elif self.connected: |
334 | + self.conn_btn.set_label(_('_Disconnect')) |
335 | + self.conn_btn.connect('clicked', self.handle_connect_button) |
336 | + restart_btn = gtk.Button(_('_Restart')) |
337 | + restart_btn.connect('clicked', self.handle_restart_button) |
338 | + btn_box = gtk.HButtonBox() |
339 | + btn_box.add(self.conn_btn) |
340 | + btn_box.add(restart_btn) |
341 | + |
342 | + i += 1 |
343 | + self.attach(ck_btn, 1, 3, i, i+1) |
344 | + i += 1 |
345 | + self.attach(up_lbl, 1, 2, i, i+1) |
346 | + self.attach(up_btn, 2, 3, i, i+1) |
347 | + i += 1 |
348 | + self.attach(dn_lbl, 1, 2, i, i+1) |
349 | + self.attach(dn_btn, 2, 3, i, i+1) |
350 | + i += 1 |
351 | + self.attach(btn_box, 1, 3, i, i+1) |
352 | + i += 2 |
353 | + self.show_all() |
354 | + |
355 | + def handle_connect_button(self, *a): |
356 | + """ |
357 | + Callback for the Connect/Disconnect button |
358 | + """ |
359 | + self.conn_btn.set_sensitive(False) |
360 | + if self.connected: |
361 | + d = self.sdtool.disconnect() |
362 | + else: |
363 | + d = self.sdtool.connect() |
364 | + |
365 | + def handle_restart_button(self, *a): |
366 | + """ |
367 | + Callback for the Restart button |
368 | + """ |
369 | + self.sdtool.quit().addCallbacks(lambda _: self.sdtool.start()) |
370 | + |
371 | + def remove(self, button, kind, token): |
372 | + """ |
373 | + Callback for the Remove button. Starts an async request to |
374 | + remove a device. |
375 | + """ |
376 | + self.conn = self.request('remove/%s/%s' % (kind.lower(), token)) |
377 | + if self.conn is None: |
378 | + self.clear_devices_view() |
379 | + self.error('Unable to connect') |
380 | + else: |
381 | + glib.io_add_watch( |
382 | + self.conn.sock, |
383 | + glib.IO_IN | glib.IO_PRI | glib.IO_ERR | glib.IO_HUP, |
384 | + self.parse_devices) |
385 | + |
386 | + |
387 | + |
388 | class UbuntuOneDialog(gtk.Dialog): |
389 | """Preferences dialog.""" |
390 | |
391 | - def __init__(self, config=None, *args, **kw): |
392 | + def __init__(self, config=None, keyring=gnomekeyring, *args, **kw): |
393 | """Initializes our config dialog.""" |
394 | super(UbuntuOneDialog, self).__init__(*args, **kw) |
395 | self.set_title(_("Ubuntu One Preferences")) |
396 | @@ -80,12 +439,8 @@ |
397 | self.connect("close", self.__handle_response, gtk.RESPONSE_CLOSE) |
398 | self.connect("response", self.__handle_response) |
399 | |
400 | - self.bw_enabled = False |
401 | - self.up_limit = 2097152 |
402 | - self.dn_limit = 2097152 |
403 | - |
404 | self.__bus = dbus.SessionBus() |
405 | - self.keyring = gnomekeyring |
406 | + self.keyring = keyring |
407 | |
408 | self.__bus.add_signal_receiver( |
409 | handler_function=self.__got_state, |
410 | @@ -108,15 +463,13 @@ |
411 | # Timeout ID to avoid spamming DBus from spinbutton changes |
412 | self.__update_id = 0 |
413 | |
414 | - # Connectivity status |
415 | - self.connected = False |
416 | - |
417 | # SD Tool object |
418 | self.sdtool = SyncDaemonTool(self.__bus) |
419 | self.sdtool.get_status().addCallbacks(lambda _: self.__got_state, |
420 | self.__sd_error) |
421 | # Build the dialog |
422 | self.__construct() |
423 | + logger.debug("starting") |
424 | |
425 | def quit(self): |
426 | """Exit the main loop.""" |
427 | @@ -132,97 +485,23 @@ |
428 | |
429 | def __got_state(self, state): |
430 | """Got the state of syncdaemon.""" |
431 | - self.connected = bool(state['is_connected']) |
432 | - if self.connected: |
433 | - self.conn_btn.set_label(_("Disconnect")) |
434 | - else: |
435 | - self.conn_btn.set_label(_("Connect")) |
436 | - self.conn_btn.set_sensitive(True) |
437 | + self.devices.handle_state_change(state) |
438 | |
439 | def __got_limits(self, limits): |
440 | """Got the throttling limits.""" |
441 | - self.up_limit = int(limits['upload']) |
442 | - self.dn_limit = int(limits['download']) |
443 | - self.up_spin.set_value(self.up_limit / 1024) |
444 | - self.dn_spin.set_value(self.dn_limit / 1024) |
445 | + logger.debug("got limits: %s" % (limits,)) |
446 | + self.devices.handle_limits(limits) |
447 | |
448 | def __got_enabled(self, enabled): |
449 | """Got the throttling enabled config.""" |
450 | - self.bw_enabled = bool(enabled) |
451 | - self.limit_check.set_active(self.bw_enabled) |
452 | - |
453 | - def __update_bw_settings(self): |
454 | - """Update the bandwidth throttling config in syncdaemon.""" |
455 | - self.bw_enabled = self.limit_check.get_active() |
456 | - self.up_limit = self.up_spin.get_value_as_int() * 1024 |
457 | - self.dn_limit = self.dn_spin.get_value_as_int() * 1024 |
458 | - |
459 | - try: |
460 | - client = self.__bus.get_object(DBUS_IFACE_NAME, "/config", |
461 | - follow_name_owner_changes=True) |
462 | - iface = dbus.Interface(client, DBUS_IFACE_CONFIG_NAME) |
463 | - iface.set_throttling_limits(self.dn_limit, self.up_limit, |
464 | - reply_handler=dbus_async, |
465 | - error_handler=self.__dbus_error) |
466 | - if self.bw_enabled: |
467 | - iface.enable_bandwidth_throttling( |
468 | - reply_handler=dbus_async, |
469 | - error_handler=self.__dbus_error) |
470 | - else: |
471 | - iface.disable_bandwidth_throttling( |
472 | - reply_handler=dbus_async, |
473 | - error_handler=self.__dbus_error) |
474 | - except DBusException, e: |
475 | - self.__dbus_error(e) |
476 | + self.devices.handle_throttling_enabled(enabled) |
477 | |
478 | def __handle_response(self, dialog, response): |
479 | """Handle the dialog's response.""" |
480 | self.hide() |
481 | - self.__update_bw_settings() |
482 | + self.devices.update_bw_settings() |
483 | gtk.main_quit() |
484 | |
485 | - def __bw_limit_toggled(self, button, data=None): |
486 | - """Toggle the bw limit panel.""" |
487 | - self.bw_enabled = self.limit_check.get_active() |
488 | - self.bw_table.set_sensitive(self.bw_enabled) |
489 | - try: |
490 | - client = self.__bus.get_object(DBUS_IFACE_NAME, "/config", |
491 | - follow_name_owner_changes=True) |
492 | - iface = dbus.Interface(client, DBUS_IFACE_CONFIG_NAME) |
493 | - iface.set_throttling_limits(self.dn_limit, self.up_limit, |
494 | - reply_handler=dbus_async, |
495 | - error_handler=self.__dbus_error) |
496 | - if self.bw_enabled: |
497 | - iface.enable_bandwidth_throttling( |
498 | - reply_handler=dbus_async, |
499 | - error_handler=self.__dbus_error) |
500 | - else: |
501 | - iface.disable_bandwidth_throttling( |
502 | - reply_handler=dbus_async, |
503 | - error_handler=self.__dbus_error) |
504 | - except DBusException, e: |
505 | - self.__dbus_error(e) |
506 | - |
507 | - def __spinner_changed(self, button, data=None): |
508 | - """Remove timeout and add anew.""" |
509 | - if self.__update_id != 0: |
510 | - gobject.source_remove(self.__update_id) |
511 | - |
512 | - self.__update_id = gobject.timeout_add_seconds( |
513 | - 1, self.__update_bw_settings) |
514 | - |
515 | - def __connect_toggled(self, button, data=None): |
516 | - """Toggle the connection state...""" |
517 | - self.conn_btn.set_sensitive(False) |
518 | - if self.connected: |
519 | - self.sdtool.start().addCallbacks( |
520 | - lambda _: self.sdtool.disconnect(), |
521 | - self.__sd_error) |
522 | - else: |
523 | - self.sdtool.start().addCallbacks( |
524 | - lambda _: self.sdtool.connect(), |
525 | - self.__sd_error) |
526 | - |
527 | def _format_for_gb_display(self, bytes): |
528 | """Format bytes into reasonable gb display.""" |
529 | gb = bytes / 1024 / 1024 / 1024 |
530 | @@ -247,12 +526,7 @@ |
531 | def request_REST_info(self, url, method): |
532 | """Make a REST request and return the resulting dict, or None.""" |
533 | consumer = oauth.OAuthConsumer("ubuntuone", "hammertime") |
534 | - items = [] |
535 | - items = self.keyring.find_items_sync( |
536 | - gnomekeyring.ITEM_GENERIC_SECRET, |
537 | - {'ubuntuone-realm': "https://ubuntuone.com", |
538 | - 'oauth-consumer-key': consumer.key}) |
539 | - token = oauth.OAuthToken.from_string(items[0].secret) |
540 | + token = get_access_token(self.keyring) |
541 | request = oauth.OAuthRequest.from_consumer_and_token( |
542 | http_url=url, http_method=method, oauth_consumer=consumer, |
543 | token=token) |
544 | @@ -403,75 +677,11 @@ |
545 | self.mail_label.show() |
546 | |
547 | # Devices tab |
548 | - devices = gtk.VBox(spacing=12) |
549 | - devices.set_border_width(6) |
550 | - self.notebook.append_page(devices) |
551 | - self.notebook.set_tab_label_text(devices, _("Devices")) |
552 | - devices.show() |
553 | - |
554 | - # Bandwidth limiting |
555 | - self.limit_check = gtk.CheckButton(_("_Limit Bandwidth Usage")) |
556 | - self.limit_check.connect("toggled", self.__bw_limit_toggled) |
557 | - devices.pack_start(self.limit_check, False, False) |
558 | - self.limit_check.show() |
559 | - |
560 | - hbox = gtk.HBox(spacing=12) |
561 | - devices.pack_start(hbox, False, False) |
562 | - hbox.show() |
563 | - |
564 | - label = gtk.Label() |
565 | - hbox.pack_start(label, False, False) |
566 | - label.show() |
567 | - |
568 | - rbox = gtk.VBox(spacing=12) |
569 | - hbox.pack_start(rbox, False, False) |
570 | - rbox.show() |
571 | - |
572 | - # Now put the bw limit bits in a table too |
573 | - self.bw_table = gtk.Table(rows=2, columns=2) |
574 | - self.bw_table.set_row_spacings(6) |
575 | - self.bw_table.set_col_spacings(6) |
576 | - self.bw_table.set_sensitive(False) |
577 | - rbox.pack_start(self.bw_table, False, False) |
578 | - self.bw_table.show() |
579 | - |
580 | - # Upload speed |
581 | - label = gtk.Label(_("Maximum _upload speed (KB/s):")) |
582 | - label.set_use_underline(True) |
583 | - label.set_alignment(0, 0.5) |
584 | - self.bw_table.attach(label, 0, 1, 0, 1) |
585 | - label.show() |
586 | - |
587 | - adjustment = gtk.Adjustment(value=2048.0, lower=0.0, upper=4096.0, |
588 | - step_incr=64.0, page_incr=128.0) |
589 | - self.up_spin = gtk.SpinButton(adjustment) |
590 | - self.up_spin.connect("value-changed", self.__spinner_changed) |
591 | - label.set_mnemonic_widget(self.up_spin) |
592 | - self.bw_table.attach(self.up_spin, 1, 2, 0, 1) |
593 | - self.up_spin.show() |
594 | - |
595 | - # Download speed |
596 | - label = gtk.Label(_("Maximum _download speed (KB/s):")) |
597 | - label.set_use_underline(True) |
598 | - label.set_alignment(0, 0.5) |
599 | - self.bw_table.attach(label, 0, 1, 1, 2) |
600 | - label.show() |
601 | - adjustment = gtk.Adjustment(value=2048.0, lower=64.0, upper=8192.0, |
602 | - step_incr=64.0, page_incr=128.0) |
603 | - self.dn_spin = gtk.SpinButton(adjustment) |
604 | - self.dn_spin.connect("value-changed", self.__spinner_changed) |
605 | - label.set_mnemonic_widget(self.dn_spin) |
606 | - self.bw_table.attach(self.dn_spin, 1, 2, 1, 2) |
607 | - self.dn_spin.show() |
608 | - |
609 | - alignment = gtk.Alignment(1.0, 0.5) |
610 | - rbox.pack_end(alignment, False, False) |
611 | - alignment.show() |
612 | - |
613 | - self.conn_btn = gtk.Button(_("Connect")) |
614 | - self.conn_btn.connect('clicked', self.__connect_toggled) |
615 | - alignment.add(self.conn_btn) |
616 | - self.conn_btn.show() |
617 | + self.devices = DevicesWidget(self.__bus, self.keyring) |
618 | + self.notebook.append_page(self.devices) |
619 | + self.notebook.set_tab_label_text(self.devices, _("Devices")) |
620 | + self.devices.show_all() |
621 | + self.devices.get_devices() |
622 | |
623 | # Services tab |
624 | services = gtk.VBox(spacing=12) |
625 | |
626 | === modified file 'tests/test_preferences.py' |
627 | --- tests/test_preferences.py 2010-02-18 16:02:23 +0000 |
628 | +++ tests/test_preferences.py 2010-03-05 17:04:28 +0000 |
629 | @@ -53,26 +53,24 @@ |
630 | |
631 | self.item_id = 999 |
632 | |
633 | - ex = self.expect(self.item.item_id) |
634 | - ex.result(self.item_id) |
635 | - ex.count(0, None) |
636 | - |
637 | - ex = self.expect(self.item.secret) |
638 | - ex.result('oauth_token=access_key&oauth_token_secret=access_secret') |
639 | - ex.count(0, None) |
640 | - |
641 | - def expect_token_query(self): |
642 | - """Expects the keyring to be queried for a token.""" |
643 | - return self.expect( |
644 | - self.keyring.find_items_sync( |
645 | - gnomekeyring.ITEM_GENERIC_SECRET, |
646 | - {'ubuntuone-realm': 'https://ubuntuone.com', |
647 | - 'oauth-consumer-key': 'ubuntuone'}) |
648 | - ) |
649 | - |
650 | - def mock_has_token(self): |
651 | - """Mocks a cached token in the keyring.""" |
652 | - self.expect_token_query().result([self.item]) |
653 | + self.item.item_id |
654 | + self.mocker.result(self.item_id) |
655 | + self.mocker.count(0, None) |
656 | + |
657 | + self.item.secret |
658 | + self.mocker.result('oauth_token=access_key' |
659 | + '&oauth_token_secret=access_secret') |
660 | + self.mocker.count(0, None) |
661 | + |
662 | + self.keyring.find_items_sync( |
663 | + None, |
664 | + {'ubuntuone-realm': 'https://ubuntuone.com', |
665 | + 'oauth-consumer-key': 'ubuntuone'}) |
666 | + self.mocker.count(0, None) |
667 | + self.mocker.result([self.item]) |
668 | + self.keyring.ITEM_GENERIC_SECRET |
669 | + self.mocker.count(0, None) |
670 | + self.mocker.result(None) |
671 | |
672 | def tearDown(self): |
673 | # collect all signal receivers registered during the test |
674 | @@ -93,18 +91,108 @@ |
675 | def test_bw_throttling(self): |
676 | """Test that toggling bw throttling works correctly.""" |
677 | self.mocker.replay() |
678 | - dialog = self.u1prefs.UbuntuOneDialog() |
679 | - self.assertTrue(dialog is not None) |
680 | - dialog.notebook.set_current_page(1) |
681 | - self.assertFalse(dialog.bw_table.get_property('sensitive')) |
682 | - dialog.limit_check.set_active(True) |
683 | - self.assertTrue(dialog.bw_table.get_property('sensitive')) |
684 | - dialog.destroy() |
685 | + widget = self.u1prefs.DevicesWidget(None, keyring=self.keyring) |
686 | + try: |
687 | + widget.devices = [] |
688 | + widget.list_devices() |
689 | + self.assertFalse(widget.bw_limited, |
690 | + "the bandwidth should start out not limited") |
691 | + self.assertTrue(widget.bw_chk, |
692 | + "the checkbox should be present") |
693 | + self.assertFalse(widget.bw_chk.get_active(), |
694 | + "the checkbox should start out unchecked") |
695 | + self.assertFalse(widget.up_spin.get_property('sensitive') or |
696 | + widget.dn_spin.get_property('sensitive'), |
697 | + "the spinbuttons should start out unsensitive") |
698 | + widget.bw_chk.set_active(True) |
699 | + self.assertTrue(widget.bw_chk.get_active(), |
700 | + "the checkbox should now be checked") |
701 | + self.assertTrue(widget.up_spin.get_property('sensitive') and |
702 | + widget.dn_spin.get_property('sensitive'), |
703 | + "the spinbuttons should now be sensitive") |
704 | + finally: |
705 | + widget.destroy() |
706 | + |
707 | + def test_list_devices_fills_devices_list_with_fake_result_when_empty(self): |
708 | + self.mocker.replay() |
709 | + widget = self.u1prefs.DevicesWidget(None, keyring=self.keyring) |
710 | + try: |
711 | + widget.devices = [] |
712 | + widget.list_devices() |
713 | + # the devices list is no longer empty |
714 | + self.assertTrue(widget.devices) |
715 | + # it has 'fake' data (referring to the local machine) |
716 | + self.assertTrue('FAKE' in widget.devices[0]) |
717 | + finally: |
718 | + widget.destroy() |
719 | + |
720 | + def test_list_devices_shows_devices_list(self): |
721 | + self.mocker.replay() |
722 | + widget = self.u1prefs.DevicesWidget(None, keyring=self.keyring) |
723 | + try: |
724 | + widget.devices = [] |
725 | + widget.list_devices() |
726 | + # fake data now in devices |
727 | + interesting = [] |
728 | + for i in widget.get_children(): |
729 | + clsname = i.__class__.__name__ |
730 | + if clsname == 'Image': |
731 | + interesting.append((clsname, i.get_icon_name()[0])) |
732 | + if clsname in ('Label', 'Button', 'CheckButton'): |
733 | + interesting.append((clsname, i.get_label())) |
734 | + # check there is an image of a computer in there |
735 | + self.assertTrue(('Image', 'computer') in interesting) |
736 | + # check a placeholder for the local machine description is there |
737 | + self.assertTrue(('Label', '<LOCAL MACHINE>') in interesting) |
738 | + # check the bw limitation stuff is there |
739 | + self.assertTrue(('CheckButton', '_Limit Bandwidth Usage') |
740 | + in interesting) |
741 | + self.assertTrue(('Label', 'Download (kB/s):') in interesting) |
742 | + self.assertTrue(('Label', 'Upload (kB/s):') in interesting) |
743 | + # check the 'Remove' button is *not* there |
744 | + self.assertTrue(('Button', 'Remove') not in interesting) |
745 | + finally: |
746 | + widget.destroy() |
747 | + |
748 | + def test_list_devices_shows_real_devices_list(self): |
749 | + self.mocker.replay() |
750 | + widget = self.u1prefs.DevicesWidget(None, keyring=self.keyring) |
751 | + try: |
752 | + widget.devices = [{'kind': 'Computer', |
753 | + 'description': 'xyzzy', |
754 | + 'token': 'blah'}, |
755 | + {'kind': 'Phone', |
756 | + 'description': 'quux', |
757 | + 'token': '1234'}] |
758 | + widget.list_devices() |
759 | + # fake data now in devices |
760 | + interesting = [] |
761 | + for i in widget.get_children(): |
762 | + clsname = i.__class__.__name__ |
763 | + if clsname == 'Image': |
764 | + interesting.append((clsname, i.get_icon_name()[0])) |
765 | + if clsname in ('Label', 'Button', 'CheckButton'): |
766 | + interesting.append((clsname, i.get_label())) |
767 | + # check there is an image of a computer in there |
768 | + self.assertTrue(('Image', 'computer') in interesting) |
769 | + # and of a phone |
770 | + self.assertTrue(('Image', 'phone') in interesting) |
771 | + # check a label of the local machine description is there |
772 | + self.assertTrue(('Label', 'xyzzy') in interesting) |
773 | + # check the bw limitation stuff is not there (no local machine) |
774 | + self.assertTrue(('CheckButton', '_Limit Bandwidth Usage') |
775 | + not in interesting) |
776 | + self.assertTrue(('Label', 'Download (kB/s):') not in interesting) |
777 | + self.assertTrue(('Label', 'Upload (kB/s):') not in interesting) |
778 | + # check the 'Remove' button is there |
779 | + self.assertTrue(('Button', 'Remove') in interesting) |
780 | + finally: |
781 | + widget.destroy() |
782 | |
783 | def test_quota_display(self): |
784 | """Test that quota display works correctly.""" |
785 | self.mocker.replay() |
786 | - dialog = self.u1prefs.UbuntuOneDialog() |
787 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
788 | self.assertTrue(dialog is not None) |
789 | self.assertEqual(dialog.usage_graph.get_fraction(), 0.0) |
790 | dialog.update_quota_display(1024, 2048) |
791 | @@ -113,11 +201,6 @@ |
792 | |
793 | def test_request_quota_info(self): |
794 | """Test that we can request the quota info properly.""" |
795 | - self.mock_has_token() |
796 | - dialog = self.u1prefs.UbuntuOneDialog() |
797 | - self.assertTrue(dialog is not None) |
798 | - dialog.keyring = self.keyring |
799 | - self.assertEqual(dialog.usage_graph.get_fraction(), 0.0) |
800 | response = { 'status' : '200' } |
801 | content = '{"total":2048, "used":1024}' |
802 | client = self.mocker.mock() |
803 | @@ -125,16 +208,15 @@ |
804 | self.expect(client.request('https://one.ubuntu.com/api/quota/', |
805 | 'GET', KWARGS)).result((response, content)) |
806 | self.mocker.replay() |
807 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
808 | + self.assertTrue(dialog is not None) |
809 | + self.assertEqual(dialog.usage_graph.get_fraction(), 0.0) |
810 | dialog.request_quota_info() |
811 | self.assertEqual(dialog.usage_graph.get_fraction(), 0.5) |
812 | dialog.destroy() |
813 | |
814 | def test_request_account_info(self): |
815 | """Test that we can request the account info properly.""" |
816 | - self.mock_has_token() |
817 | - dialog = self.u1prefs.UbuntuOneDialog() |
818 | - self.assertTrue(dialog is not None) |
819 | - dialog.keyring = self.keyring |
820 | response = { 'status' : '200' } |
821 | content = '''{"username": "ubuntuone", "nickname": "Ubuntu One", |
822 | "email": "uone@example.com"} |
823 | @@ -144,6 +226,8 @@ |
824 | self.expect(client.request('https://one.ubuntu.com/api/account/', |
825 | 'GET', KWARGS)).result((response, content)) |
826 | self.mocker.replay() |
827 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
828 | + self.assertTrue(dialog is not None) |
829 | dialog.request_account_info() |
830 | self.assertEqual(dialog.name_label.get_text(), 'Ubuntu One') |
831 | self.assertEqual(dialog.user_label.get_text(), 'ubuntuone') |
832 | @@ -152,13 +236,14 @@ |
833 | |
834 | def test_toggle_bookmarks(self): |
835 | """Test toggling the bookmarks service on/off.""" |
836 | - dialog = self.u1prefs.UbuntuOneDialog() |
837 | + toggle_db_sync = self.mocker.mock() |
838 | + self.expect(toggle_db_sync('bookmarks', False)) |
839 | + self.expect(toggle_db_sync('bookmarks', True)) |
840 | + self.expect(toggle_db_sync('bookmarks', False)) |
841 | + self.mocker.replay() |
842 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
843 | self.assertTrue(dialog is not None) |
844 | - dialog.toggle_db_sync = self.mocker.mock() |
845 | - self.expect(dialog.toggle_db_sync('bookmarks', False)) |
846 | - self.expect(dialog.toggle_db_sync('bookmarks', True)) |
847 | - self.expect(dialog.toggle_db_sync('bookmarks', False)) |
848 | - self.mocker.replay() |
849 | + dialog.toggle_db_sync = toggle_db_sync |
850 | dialog.bookmarks_check.set_active(True) |
851 | self.assertTrue(dialog.bookmarks_check.get_active()) |
852 | dialog.bookmarks_check.set_active(False) |
853 | @@ -168,13 +253,14 @@ |
854 | |
855 | def test_toggle_contacts(self): |
856 | """Test toggling the contacts service on/off.""" |
857 | - dialog = self.u1prefs.UbuntuOneDialog() |
858 | + toggle_db_sync = self.mocker.mock() |
859 | + self.expect(toggle_db_sync('contacts', False)) |
860 | + self.expect(toggle_db_sync('contacts', True)) |
861 | + self.expect(toggle_db_sync('contacts', False)) |
862 | + self.mocker.replay() |
863 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
864 | self.assertTrue(dialog is not None) |
865 | - dialog.toggle_db_sync = self.mocker.mock() |
866 | - self.expect(dialog.toggle_db_sync('contacts', False)) |
867 | - self.expect(dialog.toggle_db_sync('contacts', True)) |
868 | - self.expect(dialog.toggle_db_sync('contacts', False)) |
869 | - self.mocker.replay() |
870 | + dialog.toggle_db_sync = toggle_db_sync |
871 | dialog.abook_check.set_active(True) |
872 | self.assertTrue(dialog.abook_check.get_active()) |
873 | dialog.abook_check.set_active(False) |
874 | @@ -184,9 +270,9 @@ |
875 | |
876 | def test_toggle_files(self): |
877 | """Test toggling the files service on/off.""" |
878 | - dialog = self.u1prefs.UbuntuOneDialog() |
879 | + self.mocker.replay() |
880 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
881 | self.assertTrue(dialog is not None) |
882 | - self.mocker.replay() |
883 | dialog.files_check.set_active(True) |
884 | self.assertTrue(dialog.files_check.get_active()) |
885 | dialog.files_check.set_active(False) |
886 | @@ -196,9 +282,9 @@ |
887 | |
888 | def test_toggle_files_and_music(self): |
889 | """Test toggling the files and music services on/off.""" |
890 | - dialog = self.u1prefs.UbuntuOneDialog() |
891 | + self.mocker.replay() |
892 | + dialog = self.u1prefs.UbuntuOneDialog(keyring=self.keyring) |
893 | self.assertTrue(dialog is not None) |
894 | - self.mocker.replay() |
895 | dialog.files_check.set_active(False) |
896 | self.assertFalse(dialog.files_check.get_active()) |
897 | self.assertFalse(dialog.music_check.props.sensitive) |
898 | |
899 | === modified file 'ubuntuone/syncdaemon/dbus_interface.py' |
900 | --- ubuntuone/syncdaemon/dbus_interface.py 2010-03-03 15:04:02 +0000 |
901 | +++ ubuntuone/syncdaemon/dbus_interface.py 2010-03-05 17:04:28 +0000 |
902 | @@ -1010,6 +1010,7 @@ |
903 | configured. |
904 | The values are bytes/second |
905 | """ |
906 | + logger.debug("called get_throttling_limits") |
907 | try: |
908 | aq = self.dbus_iface.action_queue |
909 | download = -1 |
910 | @@ -1037,6 +1038,7 @@ |
911 | def set_throttling_limits(self, download, upload, |
912 | reply_handler=None, error_handler=None): |
913 | """Set the read and write limits. The expected values are bytes/sec.""" |
914 | + logger.debug("called set_throttling_limits") |
915 | try: |
916 | # modify and save the config file |
917 | user_config = config.get_user_config() |
Getting keyring errors