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