Merge lp:~chipaca/ubuntuone-client/devices-tab into lp:ubuntuone-client

Proposed by dobey
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
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

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Getting keyring errors

Revision history for this message
Eric Casteleijn (thisfred) wrote :
Download full text (8.5 KiB)

===============================================================================
[ERROR]: tests.test_preferences.PreferencesTests.test_bw_throttling

Traceback (most recent call last):
  File "/home/eric/canonical/ubuntuone-client/devices-tab/contrib/mocker.py", line 102, in test_method_wrapper
    result = test_method()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/tests/test_preferences.py", line 96, in test_bw_throttling
    dialog = self.u1prefs.UbuntuOneDialog()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 473, in __init__
    self.__construct()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 686, in __construct
    self.devices.get_devices()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 258, in get_devices
    token = get_access_token()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 84, in get_access_token
    'oauth-consumer-key': 'ubuntuone'})
gnomekeyring.IOError:
===============================================================================
[ERROR]: tests.test_preferences.PreferencesTests.test_quota_display

Traceback (most recent call last):
  File "/home/eric/canonical/ubuntuone-client/devices-tab/contrib/mocker.py", line 102, in test_method_wrapper
    result = test_method()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/tests/test_preferences.py", line 107, in test_quota_display
    dialog = self.u1prefs.UbuntuOneDialog()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 473, in __init__
    self.__construct()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 686, in __construct
    self.devices.get_devices()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 258, in get_devices
    token = get_access_token()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 84, in get_access_token
    'oauth-consumer-key': 'ubuntuone'})
gnomekeyring.IOError:
===============================================================================
[ERROR]: tests.test_preferences.PreferencesTests.test_request_account_info

Traceback (most recent call last):
  File "/home/eric/canonical/ubuntuone-client/devices-tab/contrib/mocker.py", line 102, in test_method_wrapper
    result = test_method()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/tests/test_preferences.py", line 135, in test_request_account_info
    dialog = self.u1prefs.UbuntuOneDialog()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 473, in __init__
    self.__construct()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 686, in __construct
    self.devices.get_devices()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 258, in get_devices
    token = get_access_token()
  File "/home/eric/canonical/ubuntuone-client/devices-tab/bin/ubuntuone-preferences", line 84, in get_access_token
    'oauth-consumer...

Read more...

review: Needs Fixing
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

[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-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()

Subscribers

People subscribed via source and target branches