Merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.9.5 into lp:ubuntu/natty/ubuntuone-control-panel
- Natty (11.04)
- ubuntuone-control-panel-0.9.5
- Merge into natty
Status: | Merged |
---|---|
Merge reported by: | Sebastien Bacher |
Merged at revision: | not available |
Proposed branch: | lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.9.5 |
Merge into: | lp:ubuntu/natty/ubuntuone-control-panel |
Diff against target: |
2658 lines (+1102/-207) 26 files modified
PKG-INFO (+1/-1) bin/ubuntuone-control-panel-gtk (+8/-20) debian/changelog (+42/-0) po/POTFILES.in (+1/-0) run-tests (+5/-15) setup.py (+1/-1) ubuntuone-control-panel-gtk.desktop.in (+0/-6) ubuntuone/controlpanel/backend.py (+154/-49) ubuntuone/controlpanel/dbus_client.py (+0/-1) ubuntuone/controlpanel/dbus_service.py (+55/-34) ubuntuone/controlpanel/gtk/__init__.py (+4/-2) ubuntuone/controlpanel/gtk/gui.py (+184/-35) ubuntuone/controlpanel/gtk/package_manager.py (+3/-2) ubuntuone/controlpanel/gtk/tests/__init__.py (+13/-1) ubuntuone/controlpanel/gtk/tests/test_gui.py (+76/-1) ubuntuone/controlpanel/gtk/tests/test_gui_basic.py (+129/-5) ubuntuone/controlpanel/gtk/tests/test_package_manager.py (+2/-2) ubuntuone/controlpanel/integrationtests/__init__.py (+1/-2) ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+67/-3) ubuntuone/controlpanel/integrationtests/test_gui_service.py (+98/-0) ubuntuone/controlpanel/integrationtests/test_webclient.py (+11/-1) ubuntuone/controlpanel/replication_client.py (+1/-1) ubuntuone/controlpanel/tests/__init__.py (+24/-12) ubuntuone/controlpanel/tests/test_backend.py (+205/-8) ubuntuone/controlpanel/utils.py (+1/-1) ubuntuone/controlpanel/webclient.py (+16/-4) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.9.5 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Sponsors | Pending | ||
Review via email: mp+56995@code.launchpad.net |
Commit message
Description of the change
* New upstream release:
[ <email address hidden> ]
- Now that we set the launcher urgency from ubuntuone-client, the control
panel needs to remove it when its window receives focus (LP: #747677).
- changed default value for switch_to to empty string, and now don't call
switch_to method when the value is empty string (or anything else falsy)
(LP: #752943).
- Added proper defaults to the command line arguments (LP: #746489).
- Fixed issue where closing the panel resulted in a runtime error
(LP: #745987).
- This adds a method to the dbus service that allows switching between
panels, and/or drawing attention to the control panel (LP: #742008).
- Removed the shortcut group that causes two Ubuntu One entries to appear
in the messaging menu when syncdaemon is not running (LP: #721525).
[ Natalia B. Bidart <email address hidden> ]
- If servers reply with a 401, clear credentials and ask user to
authenticate (LP: #726612).
- Moving style_check down so the exit code from u1trial is not hidden by
&& operator.
- Unify disable/enable file sync functionality among Services tab and
global file sync status (LP: #729301).
- Cloud Folders tab is now disabled when the file sync service is
(LP: #747482).
- Improving legend for plugin installation to ease translations
(LP: #746374).
- Added volumes.ui to the translation list (LP: #746370).
- Small improvement to show something else besides the generic "Value can
not be retrieved." error (LP: #722485).
- Made the backend robust against possible None values (or any non
basestring instance) sent from the API server (LP: #745790).
- Decoupled device list retrieved from the web from the local settings
retrieved from syncdaemon (LP: #720704).
- Stop the control panel backend once the UI is done
(LP: #704434).
- After initial computer adding, syncdameon is asked to connect
(LP: #715873).
- 21. By Natalia Bidart
-
Fixing editor's email address.
Preview Diff
1 | === modified file 'PKG-INFO' |
2 | --- PKG-INFO 2011-03-23 20:33:42 +0000 |
3 | +++ PKG-INFO 2011-04-08 19:43:13 +0000 |
4 | @@ -1,6 +1,6 @@ |
5 | Metadata-Version: 1.1 |
6 | Name: ubuntuone-control-panel |
7 | -Version: 0.9.4 |
8 | +Version: 0.9.5 |
9 | Summary: Ubuntu One Control Panel |
10 | Home-page: https://launchpad.net/ubuntuone-control-panel |
11 | Author: Natalia Bidart |
12 | |
13 | === modified file 'bin/ubuntuone-control-panel-gtk' |
14 | --- bin/ubuntuone-control-panel-gtk 2011-03-23 16:06:41 +0000 |
15 | +++ bin/ubuntuone-control-panel-gtk 2011-04-08 19:43:13 +0000 |
16 | @@ -2,6 +2,7 @@ |
17 | # -*- coding: utf-8 -*- |
18 | |
19 | # Authors: Natalia B Bidart <natalia.bidart@canonical.com> |
20 | +# Eric Casteleijn <eric.casteleijn@canonical.com> |
21 | # |
22 | # Copyright 2010 Canonical Ltd. |
23 | # |
24 | @@ -20,20 +21,16 @@ |
25 | |
26 | # Invalid name "ubuntuone-control-panel-gtk", pylint: disable=C0103 |
27 | |
28 | +import gettext |
29 | import sys |
30 | |
31 | -import dbus.mainloop.glib |
32 | -import gettext |
33 | - |
34 | from optparse import OptionParser |
35 | |
36 | -from ubuntuone.controlpanel.gtk import DBUS_BUS_NAME, TRANSLATION_DOMAIN |
37 | +from ubuntuone.controlpanel.gtk import TRANSLATION_DOMAIN |
38 | |
39 | -dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
40 | gettext.textdomain(TRANSLATION_DOMAIN) |
41 | - |
42 | # import the GUI after the translation domain has been set |
43 | -from ubuntuone.controlpanel.gtk.gui import ControlPanelWindow |
44 | +from ubuntuone.controlpanel.gtk.gui import main |
45 | |
46 | |
47 | def parser_options(): |
48 | @@ -41,26 +38,17 @@ |
49 | usage = "Usage: %prog [option]" |
50 | result = OptionParser(usage=usage) |
51 | result.add_option("", "--switch-to", dest="switch_to", type="string", |
52 | - metavar="PANEL_NAME", |
53 | + metavar="PANEL_NAME", default="", |
54 | help="Start the Ubuntu One Control Panel (GTK) in the " |
55 | "PANEL_NAME tab. Possible values are: " |
56 | "dashboard, volumes, devices, applications") |
57 | result.add_option("-a", "--alert", dest="alert", action="store_true", |
58 | - help="Start the Ubuntu One Control Panel (GTK) alerting " |
59 | - "the user to its presence.") |
60 | + default=False, help="Start the Ubuntu One Control Panel " |
61 | + "(GTK) alerting the user to its presence.") |
62 | return result |
63 | |
64 | |
65 | if __name__ == "__main__": |
66 | - bus = dbus.SessionBus() |
67 | - name = bus.request_name(DBUS_BUS_NAME, |
68 | - dbus.bus.NAME_FLAG_DO_NOT_QUEUE) |
69 | - if name == dbus.bus.REQUEST_NAME_REPLY_EXISTS: |
70 | - sys.exit(0) |
71 | - |
72 | - bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus()) |
73 | parser = parser_options() |
74 | (options, args) = parser.parse_args(sys.argv) |
75 | - gui = ControlPanelWindow( |
76 | - switch_to=options.switch_to, alert=options.alert) |
77 | - gui.main() |
78 | + main(switch_to=options.switch_to, alert=options.alert) |
79 | |
80 | === modified file 'debian/changelog' |
81 | --- debian/changelog 2011-03-23 20:44:08 +0000 |
82 | +++ debian/changelog 2011-04-08 19:43:13 +0000 |
83 | @@ -1,3 +1,45 @@ |
84 | +ubuntuone-control-panel (0.9.5-0ubuntu1) UNRELEASED; urgency=low |
85 | + |
86 | + * New upstream release: |
87 | + |
88 | + [ eric.casteleijn@canonical.com ] |
89 | + - Now that we set the launcher urgency from ubuntuone-client, the control |
90 | + panel needs to remove it when its window receives focus (LP: #747677). |
91 | + - changed default value for switch_to to empty string, and now don't call |
92 | + switch_to method when the value is empty string (or anything else falsy) |
93 | + (LP: #752943). |
94 | + - Added proper defaults to the command line arguments (LP: #746489). |
95 | + - Fixed issue where closing the panel resulted in a runtime error |
96 | + (LP: #745987). |
97 | + - This adds a method to the dbus service that allows switching between |
98 | + panels, and/or drawing attention to the control panel (LP: #742008). |
99 | + - Removed the shortcut group that causes two Ubuntu One entries to appear |
100 | + in the messaging menu when syncdaemon is not running (LP: #721525). |
101 | + [ Natalia B. Bidart <natalia.bidart@canonical.com> ] |
102 | + - If servers reply with a 401, clear credentials and ask user to |
103 | + authenticate (LP: #726612). |
104 | + - Moving style_check down so the exit code from u1trial is not hidden by |
105 | + && operator. |
106 | + - Unify disable/enable file sync functionality among Services tab and |
107 | + global file sync status (LP: #729301). |
108 | + - Cloud Folders tab is now disabled when the file sync service is |
109 | + (LP: #747482). |
110 | + - Improving legend for plugin installation to ease translations |
111 | + (LP: #746374). |
112 | + - Added volumes.ui to the translation list (LP: #746370). |
113 | + - Small improvement to show something else besides the generic "Value can |
114 | + not be retrieved." error (LP: #722485). |
115 | + - Made the backend robust against possible None values (or any non |
116 | + basestring instance) sent from the API server (LP: #745790). |
117 | + - Decoupled device list retrieved from the web from the local settings |
118 | + retrieved from syncdaemon (LP: #720704). |
119 | + - Stop the control panel backend once the UI is done |
120 | + (LP: #704434). |
121 | + - After initial computer adding, syncdameon is asked to connect |
122 | + (LP: #715873). |
123 | + |
124 | + -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Fri, 08 Apr 2011 15:53:02 -0300 |
125 | + |
126 | ubuntuone-control-panel (0.9.4-0ubuntu1) natty; urgency=low |
127 | |
128 | * New upstream release: |
129 | |
130 | === modified file 'po/POTFILES.in' |
131 | --- po/POTFILES.in 2011-01-07 20:07:39 +0000 |
132 | +++ po/POTFILES.in 2011-04-08 19:43:13 +0000 |
133 | @@ -7,3 +7,4 @@ |
134 | [type: gettext/glade] data/management.ui |
135 | [type: gettext/glade] data/overview.ui |
136 | [type: gettext/glade] data/services.ui |
137 | +[type: gettext/glade] data/volumes.ui |
138 | |
139 | === modified file 'run-tests' |
140 | --- run-tests 2010-12-06 12:27:11 +0000 |
141 | +++ run-tests 2011-04-08 19:43:13 +0000 |
142 | @@ -19,19 +19,8 @@ |
143 | set -e |
144 | |
145 | if [ $# -ne 0 ]; then |
146 | - # an extra argument was given |
147 | - if [ $1 == "--integration" ]; then |
148 | - # run only integration tests |
149 | - MODULE="ubuntuone/controlpanel/integrationtests" |
150 | - else |
151 | - if [ $1 == "--unittests" ]; then |
152 | - # run only non-integration tests (unittests) |
153 | - MODULE="ubuntuone/controlpanel/tests" |
154 | - else |
155 | - # run specific module given by the caller |
156 | - MODULE="$@" |
157 | - fi |
158 | - fi |
159 | + # run specific module given by the caller |
160 | + MODULE="$@" |
161 | else |
162 | # run all tests, useful for tarmac and reviews |
163 | MODULE="ubuntuone/controlpanel" |
164 | @@ -40,12 +29,13 @@ |
165 | style_check() { |
166 | pylint ubuntuone/ |
167 | if [ -x `which pep8` ]; then |
168 | - pep8 --repeat ubuntuone/ |
169 | + pep8 --repeat bin/ $MODULE |
170 | else |
171 | echo "Please install the 'pep8' package." |
172 | fi |
173 | } |
174 | |
175 | echo "Running test suite for ""$MODULE" |
176 | -`which xvfb-run` u1trial "$MODULE" && style_check |
177 | +`which xvfb-run` u1trial "$MODULE" |
178 | +style_check |
179 | rm -rf _trial_temp |
180 | |
181 | === modified file 'setup.py' |
182 | --- setup.py 2011-03-23 20:33:42 +0000 |
183 | +++ setup.py 2011-04-08 19:43:13 +0000 |
184 | @@ -79,7 +79,7 @@ |
185 | |
186 | DistUtilsExtra.auto.setup( |
187 | name='ubuntuone-control-panel', |
188 | - version='0.9.4', |
189 | + version='0.9.5', |
190 | license='GPL v3', |
191 | author='Natalia Bidart', |
192 | author_email='natalia.bidart@canonical.com', |
193 | |
194 | === modified file 'ubuntuone-control-panel-gtk.desktop.in' |
195 | --- ubuntuone-control-panel-gtk.desktop.in 2011-02-23 13:57:42 +0000 |
196 | +++ ubuntuone-control-panel-gtk.desktop.in 2011-04-08 19:43:13 +0000 |
197 | @@ -7,10 +7,4 @@ |
198 | Type=Application |
199 | Categories=GNOME;GTK;Settings; |
200 | StartupNotify=true |
201 | -X-Ayatana-Desktop-Shortcuts=U1 |
202 | X-Ayatana-Appmenu-Show-Stubs=False |
203 | - |
204 | -[U1 Shortcut Group] |
205 | -Name=Ubuntu One |
206 | -Exec=ubuntuone-control-panel-gtk |
207 | -OnlyShowIn=Messaging Menu |
208 | |
209 | === modified file 'ubuntuone/controlpanel/backend.py' |
210 | --- ubuntuone/controlpanel/backend.py 2011-03-10 02:59:44 +0000 |
211 | +++ ubuntuone/controlpanel/backend.py 2011-04-08 19:43:13 +0000 |
212 | @@ -20,14 +20,17 @@ |
213 | """A backend for the Ubuntu One Control Panel.""" |
214 | |
215 | from collections import defaultdict |
216 | +from functools import wraps |
217 | |
218 | from twisted.internet.defer import inlineCallbacks, returnValue |
219 | |
220 | from ubuntuone.controlpanel import dbus_client |
221 | from ubuntuone.controlpanel import replication_client |
222 | from ubuntuone.controlpanel.logger import setup_logging, log_call |
223 | -from ubuntuone.controlpanel.webclient import WebClient |
224 | - |
225 | +# pylint: disable=W0611 |
226 | +from ubuntuone.controlpanel.webclient import (UnauthorizedError, |
227 | + WebClient, WebClientError) |
228 | +# pylint: enable=W0611 |
229 | |
230 | logger = setup_logging('backend') |
231 | |
232 | @@ -61,6 +64,35 @@ |
233 | return 'True' if value else '' |
234 | |
235 | |
236 | +def filter_field(info, field): |
237 | + """Return a copy of 'info' where each item has 'field' hidden.""" |
238 | + result = [] |
239 | + for item in info: |
240 | + item = item.copy() |
241 | + item[field] = '<hidden>' |
242 | + result.append(item) |
243 | + return result |
244 | + |
245 | + |
246 | +def process_unauthorized(f): |
247 | + """Decorator to catch UnauthorizedError from the webclient and act upon.""" |
248 | + |
249 | + @inlineCallbacks |
250 | + @wraps(f) |
251 | + def inner(*args, **kwargs): |
252 | + """Handle UnauthorizedError and clear credentials.""" |
253 | + try: |
254 | + result = yield f(*args, **kwargs) |
255 | + except UnauthorizedError, e: |
256 | + logger.exception('process_unauthorized (clearing credentials):') |
257 | + yield dbus_client.clear_credentials() |
258 | + raise e |
259 | + |
260 | + returnValue(result) |
261 | + |
262 | + return inner |
263 | + |
264 | + |
265 | class ControlBackend(object): |
266 | """The control panel backend.""" |
267 | |
268 | @@ -68,14 +100,17 @@ |
269 | FOLDER_TYPE = u'UDF' |
270 | SHARE_TYPE = u'SHARE' |
271 | NAME_NOT_SET = u'ENAMENOTSET' |
272 | + STATUS_DISABLED = {MSG_KEY: '', STATUS_KEY: FILE_SYNC_DISABLED} |
273 | |
274 | - def __init__(self): |
275 | + def __init__(self, shutdown_func=None): |
276 | """Initialize the webclient.""" |
277 | + self.shutdown_func = shutdown_func |
278 | self.wc = WebClient(dbus_client.get_credentials) |
279 | self._status_changed_handler = None |
280 | self.status_changed_handler = lambda *a: None |
281 | |
282 | self._volumes = {} # cache last known volume info |
283 | + self.file_sync_disabled = False |
284 | |
285 | def _process_file_sync_status(self, status): |
286 | """Process raw file sync status into custom format. |
287 | @@ -89,7 +124,8 @@ |
288 | |
289 | """ |
290 | if not status: |
291 | - return {MSG_KEY: '', STATUS_KEY: FILE_SYNC_DISABLED} |
292 | + self.file_sync_disabled = True |
293 | + return self.STATUS_DISABLED |
294 | |
295 | msg = '%s (%s)' % (status['description'], status['name']) |
296 | result = {MSG_KEY: msg} |
297 | @@ -113,6 +149,7 @@ |
298 | elif is_disconnected: |
299 | result[STATUS_KEY] = FILE_SYNC_DISCONNECTED |
300 | elif is_starting: |
301 | + self.file_sync_disabled = False |
302 | result[STATUS_KEY] = FILE_SYNC_STARTING |
303 | elif is_stopped: |
304 | result[STATUS_KEY] = FILE_SYNC_STOPPED |
305 | @@ -120,7 +157,10 @@ |
306 | logger.warning('file_sync_status: unknown (got %r)', status) |
307 | result[STATUS_KEY] = FILE_SYNC_UNKNOWN |
308 | |
309 | - return result |
310 | + if self.file_sync_disabled: |
311 | + return self.STATUS_DISABLED |
312 | + else: |
313 | + return result |
314 | |
315 | def _set_status_changed_handler(self, handler): |
316 | """Set 'handler' to be called when file sync status changes.""" |
317 | @@ -142,6 +182,56 @@ |
318 | _set_status_changed_handler) |
319 | |
320 | @inlineCallbacks |
321 | + def _process_device_web_info(self, devices, enabled, limit_bw, limits, |
322 | + show_notifs): |
323 | + """Return a lis of processed devices.""" |
324 | + result = [] |
325 | + for d in devices: |
326 | + di = {} |
327 | + di["type"] = d["kind"] |
328 | + di["name"] = d["description"] |
329 | + di["configurable"] = '' |
330 | + if di["type"] == DEVICE_TYPE_COMPUTER: |
331 | + di["device_id"] = di["type"] + d["token"] |
332 | + if di["type"] == DEVICE_TYPE_PHONE: |
333 | + di["device_id"] = di["type"] + str(d["id"]) |
334 | + |
335 | + is_local = yield self.device_is_local(di["device_id"]) |
336 | + di["is_local"] = bool_str(is_local) |
337 | + # currently, only local devices are configurable. |
338 | + # eventually, more devices will be configurable. |
339 | + di["configurable"] = bool_str(is_local and enabled) |
340 | + |
341 | + if bool(di["configurable"]): |
342 | + di["limit_bandwidth"] = bool_str(limit_bw) |
343 | + di["show_all_notifications"] = bool_str(show_notifs) |
344 | + upload = limits["upload"] |
345 | + download = limits["download"] |
346 | + di[UPLOAD_KEY] = str(upload) |
347 | + di[DOWNLOAD_KEY] = str(download) |
348 | + |
349 | + # date_added is not in the webservice yet (LP: #673668) |
350 | + # di["date_added"] = "" |
351 | + |
352 | + # missing values (LP: #673668) |
353 | + # di["available_services"] = "" |
354 | + # di["enabled_services"] = "" |
355 | + |
356 | + # make a sanity check |
357 | + for key, val in di.iteritems(): |
358 | + if not isinstance(val, basestring): |
359 | + logger.warning('_process_device_web_info: (key %r), ' |
360 | + 'val %r is not a basestring.', key, val) |
361 | + di[key] = repr(val) |
362 | + |
363 | + if is_local: # prepend the local device! |
364 | + result.insert(0, di) |
365 | + else: |
366 | + result.append(di) |
367 | + |
368 | + returnValue(result) |
369 | + |
370 | + @inlineCallbacks |
371 | def get_token(self): |
372 | """Return the token from the credentials.""" |
373 | credentials = yield dbus_client.get_credentials() |
374 | @@ -153,10 +243,10 @@ |
375 | dtype, did = self.type_n_id(device_id) |
376 | local_token = yield self.get_token() |
377 | is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token) |
378 | - logger.info('device_is_local: result %r, ', is_local) |
379 | returnValue(is_local) |
380 | |
381 | @log_call(logger.debug) |
382 | + @process_unauthorized |
383 | @inlineCallbacks |
384 | def account_info(self): |
385 | """Get the user account info.""" |
386 | @@ -184,50 +274,55 @@ |
387 | returnValue(result) |
388 | |
389 | @log_call(logger.debug) |
390 | + @process_unauthorized |
391 | @inlineCallbacks |
392 | def devices_info(self): |
393 | """Get the user devices info.""" |
394 | - result = [] |
395 | - limit_bw = yield dbus_client.bandwidth_throttling_enabled() |
396 | - show_all_notif = yield dbus_client.show_all_notifications_enabled() |
397 | - limits = yield dbus_client.get_throttling_limits() |
398 | - |
399 | - devices = yield self.wc.call_api(DEVICES_API) |
400 | - for d in devices: |
401 | - di = {} |
402 | - di["type"] = d["kind"] |
403 | - di["name"] = d["description"] |
404 | - di["configurable"] = '' |
405 | - if di["type"] == DEVICE_TYPE_COMPUTER: |
406 | - di["device_id"] = di["type"] + d["token"] |
407 | - if di["type"] == DEVICE_TYPE_PHONE: |
408 | - di["device_id"] = di["type"] + str(d["id"]) |
409 | - |
410 | - is_local = yield self.device_is_local(di["device_id"]) |
411 | - di["is_local"] = bool_str(is_local) |
412 | - # currently, only local devices are configurable. |
413 | - # eventually, more the devices will be configurable. |
414 | - di["configurable"] = bool_str(is_local) |
415 | - |
416 | - if bool(di["configurable"]): |
417 | - di["limit_bandwidth"] = bool_str(limit_bw) |
418 | - di["show_all_notifications"] = bool_str(show_all_notif) |
419 | + result = limit_bw = limits = show_notifs = None |
420 | + enabled = yield dbus_client.files_sync_enabled() |
421 | + if enabled: |
422 | + limit_bw = yield dbus_client.bandwidth_throttling_enabled() |
423 | + show_notifs = yield dbus_client.show_all_notifications_enabled() |
424 | + limits = yield dbus_client.get_throttling_limits() |
425 | + |
426 | + logger.debug('devices_info: file sync enabled? %s limit_bw %s, limits ' |
427 | + '%s, show_notifs %s', |
428 | + enabled, limit_bw, limits, show_notifs) |
429 | + |
430 | + try: |
431 | + devices = yield self.wc.call_api(DEVICES_API) |
432 | + except UnauthorizedError: |
433 | + raise |
434 | + except WebClientError: |
435 | + logger.exception('devices_info: web client failure:') |
436 | + else: |
437 | + result = yield self._process_device_web_info(devices, enabled, |
438 | + limit_bw, limits, |
439 | + show_notifs) |
440 | + if result is None: |
441 | + logger.info('devices_info: result is None after calling ' |
442 | + 'devices/ API, building the local device.') |
443 | + credentials = yield dbus_client.get_credentials() |
444 | + local_device = {} |
445 | + local_device["type"] = DEVICE_TYPE_COMPUTER |
446 | + local_device["name"] = credentials['name'] |
447 | + device_id = local_device["type"] + credentials["token"] |
448 | + local_device["device_id"] = device_id |
449 | + local_device["is_local"] = bool_str(True) |
450 | + local_device["configurable"] = bool_str(enabled) |
451 | + if bool(local_device["configurable"]): |
452 | + local_device["limit_bandwidth"] = bool_str(limit_bw) |
453 | + show_notifs = bool_str(show_notifs) |
454 | + local_device["show_all_notifications"] = show_notifs |
455 | upload = limits["upload"] |
456 | download = limits["download"] |
457 | - di[UPLOAD_KEY] = str(upload) |
458 | - di[DOWNLOAD_KEY] = str(download) |
459 | - |
460 | - # date_added is not in the webservice yet (LP: #673668) |
461 | - # di["date_added"] = "" |
462 | - |
463 | - # missing values (LP: #673668) |
464 | - # di["available_services"] = "" |
465 | - # di["enabled_services"] = "" |
466 | - |
467 | - if is_local: # prepend the local device! |
468 | - result.insert(0, di) |
469 | - else: |
470 | - result.append(di) |
471 | + local_device[UPLOAD_KEY] = str(upload) |
472 | + local_device[DOWNLOAD_KEY] = str(download) |
473 | + result = [local_device] |
474 | + else: |
475 | + logger.info('devices_info: result is not None after calling ' |
476 | + 'devices/ API: %r', |
477 | + filter_field(result, field='device_id')) |
478 | |
479 | returnValue(result) |
480 | |
481 | @@ -239,7 +334,7 @@ |
482 | return DEVICE_TYPE_PHONE, device_id[5:] |
483 | return "No device", device_id |
484 | |
485 | - @log_call(logger.info) |
486 | + @log_call(logger.info, with_args=False) |
487 | @inlineCallbacks |
488 | def change_device_settings(self, device_id, settings): |
489 | """Change the settings for the given device.""" |
490 | @@ -275,7 +370,8 @@ |
491 | # still pending: more work on the settings dict (LP: #673674) |
492 | returnValue(device_id) |
493 | |
494 | - @log_call(logger.warning) |
495 | + @log_call(logger.warning, with_args=False) |
496 | + @process_unauthorized |
497 | @inlineCallbacks |
498 | def remove_device(self, device_id): |
499 | """Remove a device's tokens from the sso server.""" |
500 | @@ -286,8 +382,8 @@ |
501 | yield self.wc.call_api(api) |
502 | |
503 | if is_local: |
504 | - logger.warning('remove_device: device is local, id %r, ' |
505 | - 'clearing credentials.', device_id) |
506 | + logger.warning('remove_device: device is local! removing and ' |
507 | + 'clearing credentials.') |
508 | yield dbus_client.clear_credentials() |
509 | |
510 | returnValue(device_id) |
511 | @@ -308,12 +404,14 @@ |
512 | def enable_files(self): |
513 | """Enable the files service.""" |
514 | yield dbus_client.set_files_sync_enabled(True) |
515 | + self.file_sync_disabled = False |
516 | |
517 | @log_call(logger.debug) |
518 | @inlineCallbacks |
519 | def disable_files(self): |
520 | """Enable the files service.""" |
521 | yield dbus_client.set_files_sync_enabled(False) |
522 | + self.file_sync_disabled = True |
523 | |
524 | @log_call(logger.debug) |
525 | @inlineCallbacks |
526 | @@ -478,3 +576,10 @@ |
527 | """Install the extension to sync bookmarks.""" |
528 | # still pending (LP: #673673) |
529 | returnValue(None) |
530 | + |
531 | + @log_call(logger.info) |
532 | + def shutdown(self): |
533 | + """Stop this service.""" |
534 | + # do any other needed cleanup |
535 | + if self.shutdown_func is not None: |
536 | + self.shutdown_func() |
537 | |
538 | === modified file 'ubuntuone/controlpanel/dbus_client.py' |
539 | --- ubuntuone/controlpanel/dbus_client.py 2011-02-23 13:57:42 +0000 |
540 | +++ ubuntuone/controlpanel/dbus_client.py 2011-04-08 19:43:13 +0000 |
541 | @@ -64,7 +64,6 @@ |
542 | |
543 | def found_credentials(app_name, creds): |
544 | """Credentials have been found.""" |
545 | - logger.debug('credentials were found for app_name %r.', app_name) |
546 | if app_name == APP_NAME: |
547 | logger.info('credentials were found! (%r).', APP_NAME) |
548 | d.callback(creds) |
549 | |
550 | === modified file 'ubuntuone/controlpanel/dbus_service.py' |
551 | --- ubuntuone/controlpanel/dbus_service.py 2011-01-25 19:08:59 +0000 |
552 | +++ ubuntuone/controlpanel/dbus_service.py 2011-04-08 19:43:13 +0000 |
553 | @@ -32,7 +32,8 @@ |
554 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
555 | DBUS_PREFERENCES_IFACE) |
556 | from ubuntuone.controlpanel.backend import ( |
557 | - ControlBackend, FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED, |
558 | + ControlBackend, filter_field, UnauthorizedError, |
559 | + FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED, |
560 | FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_STOPPED, |
561 | FILE_SYNC_SYNCING, |
562 | MSG_KEY, STATUS_KEY, |
563 | @@ -77,7 +78,7 @@ |
564 | return result |
565 | |
566 | |
567 | -def transform_failure(f): |
568 | +def transform_failure(f, auth_error=None): |
569 | """Decorator to apply to DBus error signals. |
570 | |
571 | With this call, a Failure is transformed into a string-string dict. |
572 | @@ -85,8 +86,11 @@ |
573 | """ |
574 | def inner(error, _=None): |
575 | """Do the Failure transformation.""" |
576 | + logger.error('processing failure: %r', error.printTraceback()) |
577 | error_dict = error_handler(error) |
578 | - if _ is not None: |
579 | + if auth_error is not None and error.check(UnauthorizedError): |
580 | + result = auth_error(error_dict) |
581 | + elif _ is not None: |
582 | result = f(_, error_dict) |
583 | else: |
584 | result = f(error_dict) |
585 | @@ -115,19 +119,25 @@ |
586 | super(ControlPanelBackend, self).__init__(*args, **kwargs) |
587 | self.backend = backend |
588 | self.backend.status_changed_handler = self.process_status |
589 | + self.transform = lambda f: transform_failure(f, self.UnauthorizedError) |
590 | logger.debug('ControlPanelBackend: created with %r, %r.\n' |
591 | 'status_changed_handler is %r.', |
592 | args, kwargs, self.process_status) |
593 | |
594 | # pylint: disable=C0103 |
595 | |
596 | + @log_call(logger.error) |
597 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
598 | + def UnauthorizedError(self, error): |
599 | + """The credentials are not valid.""" |
600 | + |
601 | @log_call(logger.debug) |
602 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
603 | def account_info(self): |
604 | """Find out the account info for the current logged in user.""" |
605 | d = self.backend.account_info() |
606 | d.addCallback(self.AccountInfoReady) |
607 | - d.addErrback(transform_failure(self.AccountInfoError)) |
608 | + d.addErrback(self.transform(self.AccountInfoError)) |
609 | |
610 | @log_call(logger.debug) |
611 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
612 | @@ -147,12 +157,13 @@ |
613 | """Find out the devices info for the logged in user.""" |
614 | d = self.backend.devices_info() |
615 | d.addCallback(self.DevicesInfoReady) |
616 | - d.addErrback(transform_failure(self.DevicesInfoError)) |
617 | + d.addErrback(self.transform(self.DevicesInfoError)) |
618 | |
619 | - @log_call(logger.debug) |
620 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
621 | def DevicesInfoReady(self, info): |
622 | """The info for the devices is available right now.""" |
623 | + logger.debug('DevicesInfoReady: args %r', |
624 | + filter_field(info, field='device_id')) |
625 | |
626 | @log_call(logger.error) |
627 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
628 | @@ -161,41 +172,41 @@ |
629 | |
630 | #--- |
631 | |
632 | - @log_call(logger.info) |
633 | + @log_call(logger.info, with_args=False) |
634 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}") |
635 | def change_device_settings(self, device_id, settings): |
636 | """Configure a given device.""" |
637 | d = self.backend.change_device_settings(device_id, settings) |
638 | d.addCallback(self.DeviceSettingsChanged) |
639 | - d.addErrback(transform_failure(self.DeviceSettingsChangeError), |
640 | + d.addErrback(self.transform(self.DeviceSettingsChangeError), |
641 | device_id) |
642 | |
643 | - @log_call(logger.info) |
644 | + @log_call(logger.info, with_args=False) |
645 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
646 | def DeviceSettingsChanged(self, device_id): |
647 | """The settings for the device were changed.""" |
648 | |
649 | - @log_call(logger.error) |
650 | + @log_call(logger.error, with_args=False) |
651 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}") |
652 | def DeviceSettingsChangeError(self, device_id, error): |
653 | """Problem changing settings for the device.""" |
654 | |
655 | #--- |
656 | |
657 | - @log_call(logger.warning) |
658 | + @log_call(logger.warning, with_args=False) |
659 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s") |
660 | def remove_device(self, device_id): |
661 | """Remove a given device.""" |
662 | d = self.backend.remove_device(device_id) |
663 | d.addCallback(self.DeviceRemoved) |
664 | - d.addErrback(transform_failure(self.DeviceRemovalError), device_id) |
665 | + d.addErrback(self.transform(self.DeviceRemovalError), device_id) |
666 | |
667 | - @log_call(logger.warning) |
668 | + @log_call(logger.warning, with_args=False) |
669 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
670 | def DeviceRemoved(self, device_id): |
671 | """The removal for the device was completed.""" |
672 | |
673 | - @log_call(logger.error) |
674 | + @log_call(logger.error, with_args=False) |
675 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}") |
676 | def DeviceRemovalError(self, device_id, error): |
677 | """Problem removing the device.""" |
678 | @@ -234,7 +245,7 @@ |
679 | """Get the status of the file sync service.""" |
680 | d = self.backend.file_sync_status() |
681 | d.addCallback(self.process_status) |
682 | - d.addErrback(transform_failure(self.FileSyncStatusError)) |
683 | + d.addErrback(self.transform(self.FileSyncStatusError)) |
684 | |
685 | @log_call(logger.debug) |
686 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
687 | @@ -284,7 +295,7 @@ |
688 | """Enable the files service.""" |
689 | d = self.backend.enable_files() |
690 | d.addCallback(lambda _: self.FilesEnabled()) |
691 | - d.addErrback(transform_failure(self.FilesEnableError)) |
692 | + d.addErrback(self.transform(self.FilesEnableError)) |
693 | |
694 | @log_call(logger.debug) |
695 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
696 | @@ -304,7 +315,7 @@ |
697 | """Disable the files service.""" |
698 | d = self.backend.disable_files() |
699 | d.addCallback(lambda _: self.FilesDisabled()) |
700 | - d.addErrback(transform_failure(self.FilesDisableError)) |
701 | + d.addErrback(self.transform(self.FilesDisableError)) |
702 | |
703 | @log_call(logger.debug) |
704 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
705 | @@ -324,7 +335,7 @@ |
706 | """Connect the files service.""" |
707 | d = self.backend.connect_files() |
708 | d.addCallback(lambda _: self.FilesConnected()) |
709 | - d.addErrback(transform_failure(self.FilesConnectError)) |
710 | + d.addErrback(self.transform(self.FilesConnectError)) |
711 | |
712 | @log_call(logger.debug) |
713 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
714 | @@ -344,7 +355,7 @@ |
715 | """Disconnect the files service.""" |
716 | d = self.backend.disconnect_files() |
717 | d.addCallback(lambda _: self.FilesDisconnected()) |
718 | - d.addErrback(transform_failure(self.FilesDisconnectError)) |
719 | + d.addErrback(self.transform(self.FilesDisconnectError)) |
720 | |
721 | @log_call(logger.debug) |
722 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
723 | @@ -364,7 +375,7 @@ |
724 | """Restart the files service.""" |
725 | d = self.backend.restart_files() |
726 | d.addCallback(lambda _: self.FilesRestarted()) |
727 | - d.addErrback(transform_failure(self.FilesRestartError)) |
728 | + d.addErrback(self.transform(self.FilesRestartError)) |
729 | |
730 | @log_call(logger.debug) |
731 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
732 | @@ -384,7 +395,7 @@ |
733 | """Start the files service.""" |
734 | d = self.backend.start_files() |
735 | d.addCallback(lambda _: self.FilesStarted()) |
736 | - d.addErrback(transform_failure(self.FilesStartError)) |
737 | + d.addErrback(self.transform(self.FilesStartError)) |
738 | |
739 | @log_call(logger.debug) |
740 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
741 | @@ -404,7 +415,7 @@ |
742 | """Stop the files service.""" |
743 | d = self.backend.stop_files() |
744 | d.addCallback(lambda _: self.FilesStopped()) |
745 | - d.addErrback(transform_failure(self.FilesStopError)) |
746 | + d.addErrback(self.transform(self.FilesStopError)) |
747 | |
748 | @log_call(logger.debug) |
749 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
750 | @@ -424,7 +435,7 @@ |
751 | """Find out the volumes info for the logged in user.""" |
752 | d = self.backend.volumes_info() |
753 | d.addCallback(self.VolumesInfoReady) |
754 | - d.addErrback(transform_failure(self.VolumesInfoError)) |
755 | + d.addErrback(self.transform(self.VolumesInfoError)) |
756 | |
757 | @log_call(logger.debug) |
758 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a(ssaa{ss})") |
759 | @@ -444,7 +455,7 @@ |
760 | """Configure a given volume.""" |
761 | d = self.backend.change_volume_settings(volume_id, settings) |
762 | d.addCallback(self.VolumeSettingsChanged) |
763 | - d.addErrback(transform_failure(self.VolumeSettingsChangeError), |
764 | + d.addErrback(self.transform(self.VolumeSettingsChangeError), |
765 | volume_id) |
766 | |
767 | @log_call(logger.info) |
768 | @@ -465,7 +476,7 @@ |
769 | """Return the replications info.""" |
770 | d = self.backend.replications_info() |
771 | d.addCallback(self.ReplicationsInfoReady) |
772 | - d.addErrback(transform_failure(self.ReplicationsInfoError)) |
773 | + d.addErrback(self.transform(self.ReplicationsInfoError)) |
774 | |
775 | @log_call(logger.debug) |
776 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
777 | @@ -485,7 +496,7 @@ |
778 | """Configure a given replication.""" |
779 | d = self.backend.change_replication_settings(replication_id, settings) |
780 | d.addCallback(self.ReplicationSettingsChanged) |
781 | - d.addErrback(transform_failure(self.ReplicationSettingsChangeError), |
782 | + d.addErrback(self.transform(self.ReplicationSettingsChangeError), |
783 | replication_id) |
784 | |
785 | @log_call(logger.info) |
786 | @@ -506,7 +517,7 @@ |
787 | """Check if the extension to sync bookmarks is installed.""" |
788 | d = self.backend.query_bookmark_extension() |
789 | d.addCallback(self.QueryBookmarksResult) |
790 | - d.addErrback(transform_failure(self.QueryBookmarksError)) |
791 | + d.addErrback(self.transform(self.QueryBookmarksError)) |
792 | |
793 | @log_call(logger.debug) |
794 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b") |
795 | @@ -526,7 +537,7 @@ |
796 | """Install the extension to sync bookmarks.""" |
797 | d = self.backend.install_bookmarks_extension() |
798 | d.addCallback(lambda _: self.InstallBookmarksSuccess()) |
799 | - d.addErrback(transform_failure(self.InstallBookmarksError)) |
800 | + d.addErrback(self.transform(self.InstallBookmarksError)) |
801 | |
802 | @log_call(logger.info) |
803 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="") |
804 | @@ -538,15 +549,24 @@ |
805 | def InstallBookmarksError(self, error): |
806 | """Problem installing the extension to sync bookmarks.""" |
807 | |
808 | + #--- |
809 | + |
810 | + @log_call(logger.info) |
811 | + @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
812 | + def shutdown(self): |
813 | + """Shutdown this service.""" |
814 | + self.backend.shutdown() |
815 | + |
816 | |
817 | def init_mainloop(): |
818 | """Start the DBus mainloop.""" |
819 | DBusGMainLoop(set_as_default=True) |
820 | |
821 | |
822 | -def run_mainloop(): |
823 | +def run_mainloop(loop=None): |
824 | """Run the gobject main loop.""" |
825 | - loop = gobject.MainLoop() |
826 | + if loop is None: |
827 | + loop = gobject.MainLoop() |
828 | loop.run() |
829 | |
830 | |
831 | @@ -566,10 +586,10 @@ |
832 | return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus()) |
833 | |
834 | |
835 | -def publish_backend(backend=None): |
836 | +def publish_backend(backend=None, shutdown_func=None): |
837 | """Publish the backend on the DBus.""" |
838 | if backend is None: |
839 | - backend = ControlBackend() |
840 | + backend = ControlBackend(shutdown_func=shutdown_func) |
841 | return ControlPanelBackend(backend=backend, |
842 | object_path=DBUS_PREFERENCES_PATH, |
843 | bus_name=get_busname()) |
844 | @@ -579,7 +599,8 @@ |
845 | """Hook the DBus listeners and start the main loop.""" |
846 | init_mainloop() |
847 | if register_service(): |
848 | - publish_backend() |
849 | - run_mainloop() |
850 | + loop = gobject.MainLoop() |
851 | + publish_backend(shutdown_func=loop.quit) |
852 | + run_mainloop(loop=loop) |
853 | else: |
854 | print "Control panel backend already running." |
855 | |
856 | === modified file 'ubuntuone/controlpanel/gtk/__init__.py' |
857 | --- ubuntuone/controlpanel/gtk/__init__.py 2011-03-23 16:06:41 +0000 |
858 | +++ ubuntuone/controlpanel/gtk/__init__.py 2011-04-08 19:43:13 +0000 |
859 | @@ -18,5 +18,7 @@ |
860 | |
861 | """The GTK graphical interface for the control panel for Ubuntu One.""" |
862 | |
863 | -DBUS_BUS_NAME = "com.ubuntuone.controlpanel.gui" |
864 | -TRANSLATION_DOMAIN = "ubuntuone-control-panel" |
865 | +DBUS_BUS_NAME = 'com.ubuntuone.controlpanel.gui' |
866 | +DBUS_PATH = '/gui' |
867 | +DBUS_IFACE_GUI = 'com.ubuntuone.controlpanel.gui' |
868 | +TRANSLATION_DOMAIN = 'ubuntuone-control-panel' |
869 | |
870 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' |
871 | --- ubuntuone/controlpanel/gtk/gui.py 2011-03-23 16:06:41 +0000 |
872 | +++ ubuntuone/controlpanel/gtk/gui.py 2011-04-08 19:43:13 +0000 |
873 | @@ -1,6 +1,7 @@ |
874 | # -*- coding: utf-8 -*- |
875 | |
876 | # Authors: Natalia B Bidart <natalia.bidart@canonical.com> |
877 | +# Eric Casteleijn <eric.casteleijn@canonical.com> |
878 | # |
879 | # Copyright 2010 Canonical Ltd. |
880 | # |
881 | @@ -31,6 +32,7 @@ |
882 | import gobject |
883 | import ubuntu_sso |
884 | |
885 | +from dbus.mainloop.glib import DBusGMainLoop |
886 | from ubuntu_sso import networkstate |
887 | from ubuntu_sso.credentials import (TC_URL_KEY, HELP_TEXT_KEY, WINDOW_ID_KEY, |
888 | PING_URL_KEY) |
889 | @@ -41,6 +43,9 @@ |
890 | PING_URL as U1_PING_URL, DESCRIPTION as U1_DESCRIPTION) |
891 | # pylint: enable=E0611,F0401 |
892 | |
893 | +from ubuntuone.controlpanel.gtk import ( |
894 | + DBUS_IFACE_GUI, DBUS_BUS_NAME as DBUS_BUS_NAME_GUI, |
895 | + DBUS_PATH as DBUS_PATH_GUI) |
896 | from ubuntuone.controlpanel.gtk.widgets import LabelLoading, PanelTitle |
897 | # Use ubiquity package when ready (LP: #673665) |
898 | from ubuntuone.controlpanel.gtk.widgets import GreyableBin |
899 | @@ -50,10 +55,18 @@ |
900 | from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE, |
901 | DEVICE_TYPE_COMPUTER, bool_str) |
902 | from ubuntuone.controlpanel.logger import setup_logging, log_call |
903 | -from ubuntuone.controlpanel.utils import get_data_file |
904 | +from ubuntuone.controlpanel.utils import (get_data_file, |
905 | + ERROR_TYPE, ERROR_MESSAGE) |
906 | |
907 | from ubuntuone.controlpanel.gtk import package_manager, TRANSLATION_DOMAIN |
908 | |
909 | +try: |
910 | + from gi.repository import Unity # pylint: disable=E0611 |
911 | + USE_LIBUNITY = True |
912 | + U1_DOTDESKTOP = "ubuntuone-control-panel-gtk.desktop" |
913 | +except ImportError: |
914 | + USE_LIBUNITY = False |
915 | + |
916 | logger = setup_logging('gtk.gui') |
917 | _ = gettext.gettext |
918 | |
919 | @@ -63,6 +76,7 @@ |
920 | ERROR_COLOR = 'red' |
921 | LOADING = _('Loading...') |
922 | VALUE_ERROR = _('Value could not be retrieved.') |
923 | +UNKNOWN_ERROR = _('Unknown error') |
924 | WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ERROR_COLOR |
925 | KILOBYTES = 1024 |
926 | NO_OP = lambda *a, **kw: None |
927 | @@ -74,6 +88,49 @@ |
928 | logger.error('Error handler received: %r, %r', args, kwargs) |
929 | |
930 | |
931 | +def register_service(bus): |
932 | + """Try to register DBus service for making sure we run only one instance. |
933 | + |
934 | + Return True if succesfully registered, False if already running. |
935 | + """ |
936 | + name = bus.request_name(DBUS_BUS_NAME_GUI, |
937 | + dbus.bus.NAME_FLAG_DO_NOT_QUEUE) |
938 | + return name != dbus.bus.REQUEST_NAME_REPLY_EXISTS |
939 | + |
940 | + |
941 | +def publish_service(window=None, switch_to='', alert=False): |
942 | + """Publish the service on DBus.""" |
943 | + if window is None: |
944 | + window = ControlPanelWindow(switch_to=switch_to, alert=alert) |
945 | + return ControlPanelService(window) |
946 | + |
947 | + |
948 | +def main(switch_to='', alert=False): |
949 | + """Hook the DBus listeners and start the main loop.""" |
950 | + DBusGMainLoop(set_as_default=True) |
951 | + bus = dbus.SessionBus() |
952 | + if register_service(bus): |
953 | + publish_service(switch_to=switch_to, alert=alert) |
954 | + else: |
955 | + obj = bus.get_object(DBUS_BUS_NAME_GUI, DBUS_PATH_GUI) |
956 | + service = dbus.Interface(obj, dbus_interface=DBUS_IFACE_GUI) |
957 | + |
958 | + def gui_error_handler(*args, **kwargs): |
959 | + """Log errors when calling D-Bus methods in a async way.""" |
960 | + logger.error('Error handler received: %r, %r', args, kwargs) |
961 | + gtk.main_quit() |
962 | + |
963 | + def gui_reply_handler(*args, **kwargs): |
964 | + """Exit when done.""" |
965 | + gtk.main_quit() |
966 | + |
967 | + service.switch_to_alert( |
968 | + switch_to, alert, reply_handler=gui_reply_handler, |
969 | + error_handler=gui_error_handler) |
970 | + |
971 | + gtk.main() |
972 | + |
973 | + |
974 | def filter_by_app_name(f): |
975 | """Excecute 'f' filtering by app_name.""" |
976 | |
977 | @@ -192,10 +249,19 @@ |
978 | self.message.set_markup(message) |
979 | |
980 | @log_call(logger.error) |
981 | - def on_error(self, message=None): |
982 | + def on_error(self, message=None, error_dict=None): |
983 | """Use this callback to stop the Loading and set a warning message.""" |
984 | - if message == None: |
985 | + if message is None and error_dict is None: |
986 | message = VALUE_ERROR |
987 | + elif message is None and error_dict is not None: |
988 | + error_type = error_dict.get(ERROR_TYPE, UNKNOWN_ERROR) |
989 | + error_msg = error_dict.get(ERROR_MESSAGE) |
990 | + if error_msg: |
991 | + message = "%s (%s: %s)" % (VALUE_ERROR, error_type, error_msg) |
992 | + else: |
993 | + message = "%s (%s)" % (VALUE_ERROR, error_type) |
994 | + |
995 | + assert message is not None |
996 | |
997 | self.message.stop() |
998 | self.message.set_markup(WARNING_MARKUP % message) |
999 | @@ -492,6 +558,10 @@ |
1000 | elif name == self.MUSIC_DISPLAY_NAME: |
1001 | icon_name = self.MUSIC_ICON_NAME |
1002 | |
1003 | + if volume[u'path'] is None: |
1004 | + logger.warning('on_volumes_info_ready: about to store a ' |
1005 | + 'volume with None path: %r', volume) |
1006 | + |
1007 | row = (name, bool(volume[u'subscribed']), icon_name, True, |
1008 | sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'], |
1009 | volume[u'path']) |
1010 | @@ -509,7 +579,7 @@ |
1011 | @log_call(logger.error) |
1012 | def on_volumes_info_error(self, error_dict=None): |
1013 | """Backend notifies of an error when fetching volumes info.""" |
1014 | - self.on_error() |
1015 | + self.on_error(error_dict=error_dict) |
1016 | |
1017 | @log_call(logger.info) |
1018 | def on_volume_settings_changed(self, volume_id): |
1019 | @@ -548,7 +618,14 @@ |
1020 | """The user double clicked on a row.""" |
1021 | treeiter = self.volumes_store.get_iter(path) |
1022 | volume_path = self.volumes_store.get_value(treeiter, 7) |
1023 | - uri_hook(None, FILE_URI_PREFIX + volume_path) |
1024 | + if volume_path is None: |
1025 | + logger.warning('on_volumes_view_row_activated: volume_path for ' |
1026 | + 'tree_path %r is None', path) |
1027 | + elif not os.path.exists(volume_path): |
1028 | + logger.warning('on_volumes_view_row_activated: path %r ' |
1029 | + 'does not exist', volume_path) |
1030 | + else: |
1031 | + uri_hook(None, FILE_URI_PREFIX + volume_path) |
1032 | |
1033 | def load(self): |
1034 | """Load the volume list.""" |
1035 | @@ -827,7 +904,7 @@ |
1036 | @log_call(logger.error) |
1037 | def on_devices_info_error(self, error_dict=None): |
1038 | """Backend notifies of an error when fetching volumes info.""" |
1039 | - self.on_error() |
1040 | + self.on_error(error_dict=error_dict) |
1041 | self.is_processing = False |
1042 | |
1043 | @log_call(logger.warning) |
1044 | @@ -992,7 +1069,8 @@ |
1045 | def on_file_sync_status_changed(self, status): |
1046 | """File Sync status changed.""" |
1047 | enabled = status != backend.FILE_SYNC_DISABLED |
1048 | - logger.info('FileSyncService: enabled? %r', enabled) |
1049 | + logger.info('FileSyncService: on_file_sync_status_changed: ' |
1050 | + 'status %r, enabled? %r', status, enabled) |
1051 | self.check_button.set_active(enabled) |
1052 | # if service is disabled, disable the action_button |
1053 | self.action_button.set_sensitive(enabled) |
1054 | @@ -1025,8 +1103,8 @@ |
1055 | class DesktopcouchService(Service): |
1056 | """A desktopcouch service.""" |
1057 | |
1058 | - INSTALL_PACKAGE = _('Install the %(plugin_name)s ' |
1059 | - 'for %(service_name)s sync') |
1060 | + INSTALL_PACKAGE = _('Install the %(plugin_name)s for the sync service: ' |
1061 | + '%(service_name)s') |
1062 | |
1063 | def __init__(self, service_id, name, enabled, |
1064 | container, check_button, |
1065 | @@ -1152,6 +1230,7 @@ |
1066 | @log_call(logger.debug) |
1067 | def load(self): |
1068 | """Load info.""" |
1069 | + self.replications.hide() |
1070 | if self.install_box is not None: |
1071 | self.itself.remove(self.install_box) |
1072 | self.install_box = None |
1073 | @@ -1159,7 +1238,6 @@ |
1074 | logger.info('load: has_desktopcouch? %r', self.has_desktopcouch) |
1075 | if not self.has_desktopcouch: |
1076 | self.message.set_text('') |
1077 | - self.replications.hide() |
1078 | |
1079 | self.install_box = InstallPackage(self.DESKTOPCOUCH_PKG) |
1080 | self.install_box.connect('finished', self.load_replications) |
1081 | @@ -1211,7 +1289,7 @@ |
1082 | error_dict.get('error_type', None) == 'NoPairingRecord': |
1083 | self.on_error(self.NO_PAIRING_RECORD) |
1084 | else: |
1085 | - self.on_error() |
1086 | + self.on_error(error_dict=error_dict) |
1087 | |
1088 | |
1089 | class FileSyncStatus(gtk.HBox, ControlPanelMixin): |
1090 | @@ -1252,6 +1330,8 @@ |
1091 | self.button.connect('clicked', self._on_button_clicked) |
1092 | self.pack_start(self.button, expand=False) |
1093 | |
1094 | + self.show_all() |
1095 | + |
1096 | self.backend.connect_to_signal('FileSyncStatusDisabled', |
1097 | self.on_file_sync_status_disabled) |
1098 | self.backend.connect_to_signal('FileSyncStatusStarting', |
1099 | @@ -1268,10 +1348,10 @@ |
1100 | self.on_file_sync_status_error) |
1101 | self.backend.connect_to_signal('FilesStartError', |
1102 | self.on_files_start_error) |
1103 | - |
1104 | - self.backend.file_sync_status(reply_handler=NO_OP, |
1105 | - error_handler=error_handler) |
1106 | - self.show_all() |
1107 | + self.backend.connect_to_signal('FilesEnabled', |
1108 | + self.on_file_sync_status_starting) |
1109 | + self.backend.connect_to_signal('FilesDisabled', |
1110 | + self.on_file_sync_status_disabled) |
1111 | |
1112 | def _update_status(self, msg, action, callback, |
1113 | icon=None, color=None, tooltip=None): |
1114 | @@ -1296,42 +1376,42 @@ |
1115 | button.get_data('callback')(button) |
1116 | |
1117 | @log_call(logger.info) |
1118 | - def on_file_sync_status_disabled(self, msg): |
1119 | + def on_file_sync_status_disabled(self, msg=None): |
1120 | """Backend notifies of file sync status being disabled.""" |
1121 | self._update_status(self.FILE_SYNC_DISABLED, |
1122 | self.ENABLE, self.on_enable_clicked, |
1123 | '✘', 'red', self.ENABLE_TOOLTIP) |
1124 | |
1125 | @log_call(logger.info) |
1126 | - def on_file_sync_status_starting(self, msg): |
1127 | + def on_file_sync_status_starting(self, msg=None): |
1128 | """Backend notifies of file sync status being starting.""" |
1129 | self._update_status(self.FILE_SYNC_STARTING, |
1130 | self.STOP, self.on_stop_clicked, |
1131 | '⇅', ORANGE, self.STOP_TOOLTIP) |
1132 | |
1133 | @log_call(logger.info) |
1134 | - def on_file_sync_status_stopped(self, msg): |
1135 | + def on_file_sync_status_stopped(self, msg=None): |
1136 | """Backend notifies of file sync being stopped.""" |
1137 | self._update_status(self.FILE_SYNC_STOPPED, |
1138 | self.START, self.on_start_clicked, |
1139 | '✘', 'red', self.START_TOOLTIP) |
1140 | |
1141 | @log_call(logger.info) |
1142 | - def on_file_sync_status_disconnected(self, msg): |
1143 | + def on_file_sync_status_disconnected(self, msg=None): |
1144 | """Backend notifies of file sync status being ready.""" |
1145 | self._update_status(self.FILE_SYNC_DISCONNECTED, |
1146 | self.CONNECT, self.on_connect_clicked, |
1147 | '✘', 'red', self.CONNECT_TOOLTIP,) |
1148 | |
1149 | @log_call(logger.info) |
1150 | - def on_file_sync_status_syncing(self, msg): |
1151 | + def on_file_sync_status_syncing(self, msg=None): |
1152 | """Backend notifies of file sync status being syncing.""" |
1153 | self._update_status(self.FILE_SYNC_SYNCING, |
1154 | self.DISCONNECT, self.on_disconnect_clicked, |
1155 | '⇅', ORANGE, self.DISCONNECT_TOOLTIP) |
1156 | |
1157 | @log_call(logger.info) |
1158 | - def on_file_sync_status_idle(self, msg): |
1159 | + def on_file_sync_status_idle(self, msg=None): |
1160 | """Backend notifies of file sync status being idle.""" |
1161 | self._update_status(self.FILE_SYNC_IDLE, |
1162 | self.DISCONNECT, self.on_disconnect_clicked, |
1163 | @@ -1385,6 +1465,11 @@ |
1164 | self.backend.stop_files(reply_handler=NO_OP, |
1165 | error_handler=error_handler) |
1166 | |
1167 | + def load(self): |
1168 | + """Load the information.""" |
1169 | + self.backend.file_sync_status(reply_handler=NO_OP, |
1170 | + error_handler=error_handler) |
1171 | + |
1172 | |
1173 | class ManagementPanel(gtk.VBox, ControlPanelMixin): |
1174 | """The management panel. |
1175 | @@ -1396,6 +1481,7 @@ |
1176 | __gsignals__ = { |
1177 | 'local-device-removed': (gobject.SIGNAL_RUN_FIRST, |
1178 | gobject.TYPE_NONE, ()), |
1179 | + 'unauthorized': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()), |
1180 | } |
1181 | |
1182 | QUOTA_LABEL = _('Using %(used)s of %(total)s (%(percentage).0f%%)') |
1183 | @@ -1421,6 +1507,8 @@ |
1184 | self.on_account_info_ready) |
1185 | self.backend.connect_to_signal('AccountInfoError', |
1186 | self.on_account_info_error) |
1187 | + self.backend.connect_to_signal('UnauthorizedError', |
1188 | + self.on_unauthorized_error) |
1189 | |
1190 | self.quota_progressbar.set_sensitive(False) |
1191 | |
1192 | @@ -1461,7 +1549,11 @@ |
1193 | |
1194 | self.services_button.set_name(self.SERVICES_BUTTON_NAME) |
1195 | self.services_button.set_tooltip_text(self.SERVICES_BUTTON_TOOLTIP) |
1196 | - self.services.load() |
1197 | + |
1198 | + self.enable_volumes = lambda: self.volumes_button.set_sensitive(True) |
1199 | + self.disable_volumes = lambda: self.volumes_button.set_sensitive(False) |
1200 | + self.backend.connect_to_signal('FilesEnabled', self.enable_volumes) |
1201 | + self.backend.connect_to_signal('FilesDisabled', self.disable_volumes) |
1202 | |
1203 | def _update_quota(self, msg, data=None): |
1204 | """Update the quota info.""" |
1205 | @@ -1491,6 +1583,8 @@ |
1206 | """Load the account info and file sync status list.""" |
1207 | self.backend.account_info(reply_handler=NO_OP, |
1208 | error_handler=error_handler) |
1209 | + self.status_label.load() |
1210 | + self.services.load() |
1211 | |
1212 | @log_call(logger.debug) |
1213 | def on_account_info_ready(self, info): |
1214 | @@ -1506,15 +1600,22 @@ |
1215 | """Backend notifies of an error when fetching account info.""" |
1216 | self._update_quota(msg='') |
1217 | |
1218 | - |
1219 | -class ControlPanel(gtk.Notebook): |
1220 | + @log_call(logger.error) |
1221 | + def on_unauthorized_error(self, error_dict=None): |
1222 | + """Backend notifies that credentials are not valid.""" |
1223 | + self.emit('unauthorized') |
1224 | + |
1225 | + |
1226 | +class ControlPanel(gtk.Notebook, ControlPanelMixin): |
1227 | """The control panel per se, can be added into any other widget.""" |
1228 | |
1229 | # should not be any larger than 736x525 |
1230 | |
1231 | def __init__(self, main_window): |
1232 | gtk.Notebook.__init__(self) |
1233 | + ControlPanelMixin.__init__(self) |
1234 | gtk.link_button_set_uri_hook(uri_hook) |
1235 | + self.connect('destroy', self.shutdown) |
1236 | |
1237 | self.main_window = main_window |
1238 | |
1239 | @@ -1531,6 +1632,8 @@ |
1240 | self.on_show_management_panel) |
1241 | self.management.connect('local-device-removed', |
1242 | self.on_show_overview_panel) |
1243 | + self.management.connect('unauthorized', |
1244 | + self.on_show_overview_panel) |
1245 | |
1246 | self.show() |
1247 | self.on_show_overview_panel() |
1248 | @@ -1538,6 +1641,12 @@ |
1249 | logger.debug('%s: started (window size %r).', |
1250 | self.__class__.__name__, self.get_size_request()) |
1251 | |
1252 | + def shutdown(self, *args, **kwargs): |
1253 | + """Shutdown backend.""" |
1254 | + logger.info('Shutting down...') |
1255 | + self.backend.shutdown(reply_handler=NO_OP, |
1256 | + error_handler=error_handler) |
1257 | + |
1258 | def on_show_overview_panel(self, widget=None): |
1259 | """Show the overview panel.""" |
1260 | self.set_current_page(0) |
1261 | @@ -1550,18 +1659,42 @@ |
1262 | if credentials_are_new: |
1263 | # redirect user to services page to start using Ubuntu One |
1264 | self.management.services_button.clicked() |
1265 | + # instruct syncdaemon to connect |
1266 | + self.backend.connect_files(reply_handler=NO_OP, |
1267 | + error_handler=error_handler) |
1268 | |
1269 | self.next_page() |
1270 | |
1271 | |
1272 | +class ControlPanelService(dbus.service.Object): |
1273 | + """DBUS service that exposes some of the window's methods.""" |
1274 | + |
1275 | + def __init__(self, window): |
1276 | + self.window = window |
1277 | + bus_name = dbus.service.BusName( |
1278 | + DBUS_BUS_NAME_GUI, bus=dbus.SessionBus()) |
1279 | + dbus.service.Object.__init__( |
1280 | + self, bus_name=bus_name, object_path=DBUS_PATH_GUI) |
1281 | + |
1282 | + @log_call(logger.debug) |
1283 | + @dbus.service.method(dbus_interface=DBUS_IFACE_GUI, in_signature='sb') |
1284 | + def switch_to_alert(self, panel='', alert=False): |
1285 | + """Switch to named panel.""" |
1286 | + if panel: |
1287 | + self.window.switch_to(panel) |
1288 | + if alert: |
1289 | + self.window.draw_attention() |
1290 | + |
1291 | + |
1292 | class ControlPanelWindow(gtk.Window): |
1293 | """The main window for the Ubuntu One control panel.""" |
1294 | |
1295 | TITLE = _('%(app_name)s Control Panel') |
1296 | |
1297 | - def __init__(self, switch_to=None, alert=False): |
1298 | + def __init__(self, switch_to='', alert=False): |
1299 | super(ControlPanelWindow, self).__init__() |
1300 | |
1301 | + self.connect('focus-in-event', self.remove_urgency) |
1302 | self.set_title(self.TITLE % {'app_name': U1_APP_NAME}) |
1303 | self.set_position(gtk.WIN_POS_CENTER_ALWAYS) |
1304 | self.set_icon_name('ubuntuone') |
1305 | @@ -1569,9 +1702,7 @@ |
1306 | |
1307 | self.connect('delete-event', lambda w, e: gtk.main_quit()) |
1308 | if alert: |
1309 | - # NOTE this should prevent focus stealing but it does not :( |
1310 | - self.present_with_time(1) |
1311 | - self.set_urgency_hint(True) |
1312 | + self.draw_attention() |
1313 | else: |
1314 | self.present() |
1315 | |
1316 | @@ -1580,17 +1711,35 @@ |
1317 | |
1318 | logger.info('Starting %s pointing at panel: %r.', |
1319 | self.__class__.__name__, switch_to) |
1320 | - if switch_to is not None: |
1321 | - button = getattr(self.control_panel.management, |
1322 | - '%s_button' % switch_to, None) |
1323 | - if button is not None: |
1324 | - button.clicked() |
1325 | - else: |
1326 | - logger.warning('Could not start at panel: %r.', switch_to) |
1327 | + if switch_to: |
1328 | + self.switch_to(switch_to) |
1329 | |
1330 | logger.debug('%s: started (window size %r).', |
1331 | self.__class__.__name__, self.get_size_request()) |
1332 | |
1333 | + def remove_urgency(self, *args, **kwargs): |
1334 | + """Remove urgency from the launcher entry.""" |
1335 | + if not USE_LIBUNITY: |
1336 | + return |
1337 | + entry = Unity.LauncherEntry.get_for_desktop_id(U1_DOTDESKTOP) |
1338 | + if entry.props.urgent: |
1339 | + self.switch_to('volumes') |
1340 | + entry.props.urgent = False |
1341 | + |
1342 | + def draw_attention(self): |
1343 | + """Draw attention to the control panel.""" |
1344 | + self.present_with_time(1) |
1345 | + self.set_urgency_hint(True) |
1346 | + |
1347 | + def switch_to(self, panel): |
1348 | + """Switch to named panel.""" |
1349 | + button = getattr( |
1350 | + self.control_panel.management, '%s_button' % panel, None) |
1351 | + if button is not None: |
1352 | + button.clicked() |
1353 | + else: |
1354 | + logger.warning('Could not start at panel: %r.', panel) |
1355 | + |
1356 | def main(self): |
1357 | """Run the main loop of the widget toolkit.""" |
1358 | logger.debug('Starting GTK main loop.') |
1359 | |
1360 | === modified file 'ubuntuone/controlpanel/gtk/package_manager.py' |
1361 | --- ubuntuone/controlpanel/gtk/package_manager.py 2011-02-23 13:57:42 +0000 |
1362 | +++ ubuntuone/controlpanel/gtk/package_manager.py 2011-04-08 19:43:13 +0000 |
1363 | @@ -20,13 +20,14 @@ |
1364 | |
1365 | import apt |
1366 | import aptdaemon.client |
1367 | +# pylint: disable=W0404 |
1368 | import aptdaemon.enums |
1369 | |
1370 | try: |
1371 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
1372 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
1373 | from aptdaemon.defer import inline_callbacks, return_value |
1374 | except ImportError: |
1375 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
1376 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
1377 | from defer import inline_callbacks, return_value |
1378 | from aptdaemon.gtkwidgets import AptProgressBar |
1379 | |
1380 | |
1381 | === modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py' |
1382 | --- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-03-10 02:59:44 +0000 |
1383 | +++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-04-08 19:43:13 +0000 |
1384 | @@ -180,10 +180,19 @@ |
1385 | 'replications_info', 'change_replication_settings', # replications |
1386 | 'file_sync_status', 'enable_files', 'disable_files', # files |
1387 | 'connect_files', 'disconnect_files', |
1388 | - 'restart_files', 'start_files', 'stop_files', |
1389 | + 'restart_files', 'start_files', 'stop_files', 'shutdown', |
1390 | ] |
1391 | |
1392 | |
1393 | +class FakeControlPanelBackend(FakedDBusBackend): |
1394 | + """Fake a Control Panel Service, act as a dbus.Interface.""" |
1395 | + |
1396 | + bus_name = gui.DBUS_BUS_NAME_GUI |
1397 | + object_path = gui.DBUS_PATH_GUI |
1398 | + iface = gui.DBUS_IFACE_GUI |
1399 | + exposed_methods = ['draw_attention', 'switch_to'] |
1400 | + |
1401 | + |
1402 | class FakedSessionBus(object): |
1403 | """Fake a session bus.""" |
1404 | |
1405 | @@ -202,6 +211,9 @@ |
1406 | *args, **kwargs) |
1407 | if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE: |
1408 | return FakedSSOBackend(obj, dbus_interface, *args, **kwargs) |
1409 | + if dbus_interface == gui.DBUS_IFACE_GUI: |
1410 | + return FakeControlPanelBackend( |
1411 | + obj, dbus_interface, *args, **kwargs) |
1412 | |
1413 | |
1414 | class FakedPackageManager(object): |
1415 | |
1416 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' |
1417 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-03-10 02:59:44 +0000 |
1418 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-04-08 19:43:13 +0000 |
1419 | @@ -278,6 +278,7 @@ |
1420 | |
1421 | def test_clicking_on_row_opens_folder(self): |
1422 | """The folder activated is opened.""" |
1423 | + self.patch(gui.os.path, 'exists', lambda *a: True) |
1424 | self.patch(gui, 'uri_hook', self._set_called) |
1425 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
1426 | |
1427 | @@ -287,6 +288,33 @@ |
1428 | self.assertEqual(self._called, |
1429 | ((None, gui.FILE_URI_PREFIX + ROOT['path']), {})) |
1430 | |
1431 | + def test_clicking_on_row_handles_path_none(self): |
1432 | + """None paths are properly handled.""" |
1433 | + self.patch(gui, 'uri_hook', self._set_called) |
1434 | + self.patch(self.ui.volumes_store, 'get_value', lambda *a: None) |
1435 | + self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
1436 | + |
1437 | + self.ui.volumes_view.row_activated('0:0', |
1438 | + self.ui.volumes_view.get_column(0)) |
1439 | + |
1440 | + self.assertTrue(self.memento.check_warning('tree_path (0, 0)', |
1441 | + 'volume_path', 'is None')) |
1442 | + self.assertEqual(self._called, False) |
1443 | + |
1444 | + def test_clicking_on_row_handles_path_non_existent(self): |
1445 | + """Not-existent paths are properly handled.""" |
1446 | + self.patch(gui.os.path, 'exists', lambda *a: False) |
1447 | + self.patch(gui, 'uri_hook', self._set_called) |
1448 | + path = 'not-in-disk' |
1449 | + self.patch(self.ui.volumes_store, 'get_value', lambda *a: path) |
1450 | + self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
1451 | + |
1452 | + self.ui.volumes_view.row_activated('0:0', |
1453 | + self.ui.volumes_view.get_column(0)) |
1454 | + |
1455 | + self.assertTrue(self.memento.check_warning(path, 'does not exist')) |
1456 | + self.assertEqual(self._called, False) |
1457 | + |
1458 | def test_on_volumes_info_ready_with_music_folder(self): |
1459 | """The volumes info is processed when ready.""" |
1460 | info = [(u'', u'147852369', [ROOT] + [MUSIC_FOLDER])] |
1461 | @@ -1646,12 +1674,15 @@ |
1462 | ('FileSyncStatusIdle', [self.ui.on_file_sync_status_idle]), |
1463 | ('FileSyncStatusError', [self.ui.on_file_sync_status_error]), |
1464 | ('FilesStartError', [self.ui.on_files_start_error]), |
1465 | + ('FilesDisabled', [self.ui.on_file_sync_status_disabled]), |
1466 | + ('FilesEnabled', [self.ui.on_file_sync_status_starting]), |
1467 | ) |
1468 | for sig, handlers in matches: |
1469 | self.assertEqual(self.ui.backend._signals[sig], handlers) |
1470 | |
1471 | - def test_file_sync_status_is_requested(self): |
1472 | + def test_file_sync_status_is_requested_on_load(self): |
1473 | """The file sync status is requested to the backend.""" |
1474 | + self.ui.load() |
1475 | self.assert_backend_called('file_sync_status', ()) |
1476 | |
1477 | def test_on_file_sync_status_disabled(self): |
1478 | @@ -1882,11 +1913,32 @@ |
1479 | self.assertEqual(self.ui.backend._signals['AccountInfoError'], |
1480 | [self.ui.on_account_info_error]) |
1481 | |
1482 | + def test_backend_unauthorized_signal(self): |
1483 | + """The proper signals are connected to the backend.""" |
1484 | + self.assertEqual(self.ui.backend._signals['UnauthorizedError'], |
1485 | + [self.ui.on_unauthorized_error]) |
1486 | + |
1487 | + def test_no_backend_calls_before_load(self): |
1488 | + """No calls are made to the backend before load() is called.""" |
1489 | + self.assertEqual(self.ui.backend._called, {}) |
1490 | + |
1491 | def test_account_info_is_requested_on_load(self): |
1492 | """The account info is requested to the backend.""" |
1493 | self.ui.load() |
1494 | self.assert_backend_called('account_info', ()) |
1495 | |
1496 | + def test_file_sync_status_info_is_requested_on_load(self): |
1497 | + """The file sync status info is requested to the backend.""" |
1498 | + self.patch(self.ui.status_label, 'load', self._set_called) |
1499 | + self.ui.load() |
1500 | + self.assertEqual(self._called, ((), {})) |
1501 | + |
1502 | + def test_replications_info_is_requested_on_load(self): |
1503 | + """The replications info is requested to the backend.""" |
1504 | + self.patch(self.ui.services, 'load', self._set_called) |
1505 | + self.ui.load() |
1506 | + self.assertEqual(self._called, ((), {})) |
1507 | + |
1508 | def test_dashboard_panel_is_packed(self): |
1509 | """The dashboard panel is packed.""" |
1510 | self.assertIsInstance(self.ui.dashboard, gui.DashboardPanel) |
1511 | @@ -1999,6 +2051,23 @@ |
1512 | self.assertIsInstance(self.ui.status_label, gui.FileSyncStatus) |
1513 | self.assertIn(self.ui.status_label, self.ui.status_box.get_children()) |
1514 | |
1515 | + def test_backend_file_sync_signals(self): |
1516 | + """The proper signals are connected to the backend.""" |
1517 | + self.assertEqual(self.ui.backend._signals['FilesEnabled'], |
1518 | + [self.ui.enable_volumes]) |
1519 | + self.assertEqual(self.ui.backend._signals['FilesDisabled'], |
1520 | + [self.ui.disable_volumes]) |
1521 | + |
1522 | + def test_enable_volumes(self): |
1523 | + """The volumes tab is properly enabled.""" |
1524 | + self.ui.enable_volumes() |
1525 | + self.assertTrue(self.ui.volumes_button.get_sensitive()) |
1526 | + |
1527 | + def test_disable_volumes(self): |
1528 | + """The volumes tab is properly disabled.""" |
1529 | + self.ui.disable_volumes() |
1530 | + self.assertFalse(self.ui.volumes_button.get_sensitive()) |
1531 | + |
1532 | def test_local_device_removed_is_emitted(self): |
1533 | """Signal local-device-removed is sent when DevicesPanel emits it.""" |
1534 | self.ui.connect('local-device-removed', self._set_called) |
1535 | @@ -2024,3 +2093,9 @@ |
1536 | actual = getattr(self.ui, '%s_button' % tab).get_tooltip_text() |
1537 | expected = getattr(self.ui, '%s_BUTTON_TOOLTIP' % tab.upper()) |
1538 | self.assertEqual(actual, expected) |
1539 | + |
1540 | + def test_on_unauthorized_error(self): |
1541 | + """On invalid credentials, proper signal is sent.""" |
1542 | + self.ui.connect('unauthorized', self._set_called) |
1543 | + self.ui.on_unauthorized_error() |
1544 | + self.assertEqual(self._called, ((self.ui,), {})) |
1545 | |
1546 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui_basic.py' |
1547 | --- ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-03-23 16:06:41 +0000 |
1548 | +++ ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-04-08 19:43:13 +0000 |
1549 | @@ -29,6 +29,43 @@ |
1550 | # pylint: disable=W0201, W0212 |
1551 | |
1552 | |
1553 | +class FakeLauncherEntryProps(object): |
1554 | + """A fake Unity.LauncherEntry.props""" |
1555 | + |
1556 | + urgent = True |
1557 | + |
1558 | + |
1559 | +THE_FLEP = FakeLauncherEntryProps() |
1560 | + |
1561 | + |
1562 | +class FakeLauncherEntry(object): |
1563 | + """A fake Unity.LauncherEntry""" |
1564 | + |
1565 | + def __init__(self): |
1566 | + """Initialize this fake instance.""" |
1567 | + self.props = THE_FLEP |
1568 | + |
1569 | + @staticmethod |
1570 | + def get_for_desktop_id(dotdesktop): |
1571 | + """Find the LauncherEntry for a given dotdesktop.""" |
1572 | + return FakeLauncherEntry() |
1573 | + |
1574 | + |
1575 | +class FakeControlPanelService(object): |
1576 | + """Fake service.""" |
1577 | + def __init__(self, window): |
1578 | + self.window = window |
1579 | + self.called = [] |
1580 | + |
1581 | + def draw_attention(self): |
1582 | + """Draw attention to the control panel.""" |
1583 | + self.called.append('draw_attention') |
1584 | + |
1585 | + def switch_to(self, panel): |
1586 | + """Switch to named panel.""" |
1587 | + self.called.append(('switch_to', panel)) |
1588 | + |
1589 | + |
1590 | class ControlPanelMixinTestCase(BaseTestCase): |
1591 | """The test suite for the control panel widget.""" |
1592 | |
1593 | @@ -49,25 +86,30 @@ |
1594 | |
1595 | klass = gui.ControlPanelWindow |
1596 | |
1597 | + def setUp(self): |
1598 | + self.patch(gui, 'ControlPanelService', FakeControlPanelService) |
1599 | + super(ControlPanelWindowTestCase, self).setUp() |
1600 | + |
1601 | def test_is_a_window(self): |
1602 | """Inherits from gtk.Window.""" |
1603 | self.assertIsInstance(self.ui, gui.gtk.Window) |
1604 | |
1605 | def test_startup_visibility(self): |
1606 | """The widget is visible at startup.""" |
1607 | - self.assertTrue(self.ui.get_visible(), 'must be visible at startup.') |
1608 | + self.assertTrue(self.ui.get_visible(), 'was not visible at startup.') |
1609 | |
1610 | def test_main_start_gtk_main_loop(self): |
1611 | """The GTK main loop is started when calling main().""" |
1612 | self.patch(gui.gtk, 'main', self._set_called) |
1613 | self.ui.main() |
1614 | - self.assertEqual(self._called, ((), {}), 'gtk.main was called.') |
1615 | + self.assertEqual(self._called, ((), {}), 'gtk.main was not called.') |
1616 | |
1617 | def test_closing_stops_the_main_lopp(self): |
1618 | """The GTK main loop is stopped when closing the window.""" |
1619 | self.patch(gui.gtk, 'main_quit', self._set_called) |
1620 | self.ui.emit('delete-event', None) |
1621 | - self.assertEqual(self._called, ((), {}), 'gtk.main_quit was called.') |
1622 | + self.assertEqual( |
1623 | + self._called, ((), {}), 'gtk.main_quit was not called.') |
1624 | |
1625 | def test_title_is_correct(self): |
1626 | """The window title is correct.""" |
1627 | @@ -96,6 +138,32 @@ |
1628 | """Max size is not bigger than 736x525 (LP: #645526, LP: #683164).""" |
1629 | self.assertTrue(self.ui.get_size_request() <= (736, 525)) |
1630 | |
1631 | + def test_focus_handler(self): |
1632 | + """When the window receives focus, the handler is called.""" |
1633 | + THE_FLEP.urgent = True |
1634 | + self.patch(gui.Unity, "LauncherEntry", FakeLauncherEntry) |
1635 | + cp = gui.ControlPanelWindow() |
1636 | + cp.emit('focus-in-event', None) |
1637 | + self.assertEqual( |
1638 | + False, THE_FLEP.urgent, 'remove_urgency should have been called.') |
1639 | + |
1640 | + |
1641 | +class ControlPanelWindowAlertParamTestCase(BaseTestCase): |
1642 | + """The test suite for the control panel window when passing params.""" |
1643 | + |
1644 | + klass = gui.ControlPanelWindow |
1645 | + kwargs = {'alert': True} |
1646 | + |
1647 | + def setUp(self): |
1648 | + self.patch(gui, 'ControlPanelService', FakeControlPanelService) |
1649 | + self.patch(gui.ControlPanelWindow, 'draw_attention', self._set_called) |
1650 | + super(ControlPanelWindowAlertParamTestCase, self).setUp() |
1651 | + |
1652 | + def test_alert(self): |
1653 | + """Can pass a 'alert' parameter to draw attention to the window.""" |
1654 | + self.assertEqual( |
1655 | + ((), {}), self._called, 'draw_attention should have been called.') |
1656 | + |
1657 | |
1658 | class ControlPanelWindowParamsTestCase(ControlPanelWindowTestCase): |
1659 | """The test suite for the control panel window when passing params.""" |
1660 | @@ -169,8 +237,10 @@ |
1661 | |
1662 | def test_on_show_management_panel(self): |
1663 | """A ManagementPanel is shown when the callback is executed.""" |
1664 | + self.patch(self.ui.management, 'load', self._set_called) |
1665 | self.ui.on_show_management_panel() |
1666 | self.assert_current_tab_correct(self.ui.management) |
1667 | + self.assertEqual(self._called, ((), {})) |
1668 | |
1669 | def test_on_show_management_panel_is_idempotent(self): |
1670 | """Only one ManagementPanel is shown.""" |
1671 | @@ -179,7 +249,7 @@ |
1672 | |
1673 | self.assert_current_tab_correct(self.ui.management) |
1674 | |
1675 | - def test_credentials_found_shows_dashboard_management_panel(self): |
1676 | + def test_credentials_found_shows_dashboard_panel(self): |
1677 | """On 'credentials-found' signal, the management panel is shown. |
1678 | |
1679 | If first signal parameter is False, visible tab should be dashboard. |
1680 | @@ -193,7 +263,7 @@ |
1681 | self.ui.management.DASHBOARD_PAGE) |
1682 | self.assertEqual(self._called, ((), {})) |
1683 | |
1684 | - def test_credentials_found_shows_volumes_management_panel(self): |
1685 | + def test_credentials_found_shows_services_panel(self): |
1686 | """On 'credentials-found' signal, the management panel is shown. |
1687 | |
1688 | If first signal parameter is True, visible tab should be services. |
1689 | @@ -206,6 +276,12 @@ |
1690 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
1691 | self.ui.management.SERVICES_PAGE) |
1692 | |
1693 | + def test_credentials_found_connects_syncdaemon(self): |
1694 | + """On 'credentials-found' signal, ask syncdaemon to connect.""" |
1695 | + # credentials are new |
1696 | + self.ui.overview.emit('credentials-found', True, object()) |
1697 | + self.assert_backend_called('connect_files', ()) |
1698 | + |
1699 | def test_local_device_removed_shows_overview_panel(self): |
1700 | """On 'local-device-removed' signal, the overview panel is shown.""" |
1701 | self.ui.overview.emit('credentials-found', True, object()) |
1702 | @@ -213,6 +289,18 @@ |
1703 | |
1704 | self.assert_current_tab_correct(self.ui.overview) |
1705 | |
1706 | + def test_unauthorized_shows_overview_panel(self): |
1707 | + """On 'unauthorized' signal, the overview panel is shown.""" |
1708 | + self.ui.overview.emit('credentials-found', True, object()) |
1709 | + self.ui.management.emit('unauthorized') |
1710 | + |
1711 | + self.assert_current_tab_correct(self.ui.overview) |
1712 | + |
1713 | + def test_backend_is_shutdown_on_close(self): |
1714 | + """When the control panel is closed, the backend is shutdown.""" |
1715 | + self.ui.emit('destroy') |
1716 | + self.assert_backend_called('shutdown', ()) |
1717 | + |
1718 | |
1719 | class UbuntuOneBinTestCase(BaseTestCase): |
1720 | """The test suite for a Ubuntu One panel.""" |
1721 | @@ -276,6 +364,42 @@ |
1722 | self.assert_warning_correct(self.ui.message, msg) |
1723 | self.assertFalse(self.ui.message.active) |
1724 | |
1725 | + def test_on_error_with_error_dict(self): |
1726 | + """Callback to stop the Loading and show the error from error_dict.""" |
1727 | + msg = u'Qué mala suerte! <i>this does not rock</i>' |
1728 | + error_dict = {gui.ERROR_TYPE: 'YaddaError', gui.ERROR_MESSAGE: msg} |
1729 | + self.ui.on_error(error_dict=error_dict) |
1730 | + |
1731 | + expected_msg = "%s (%s: %s)" % (gui.VALUE_ERROR, 'YaddaError', msg) |
1732 | + self.assert_warning_correct(self.ui.message, expected_msg) |
1733 | + self.assertFalse(self.ui.message.active) |
1734 | + |
1735 | + def test_on_error_with_error_dict_without_error_type(self): |
1736 | + """Callback to stop the Loading and show the error from error_dict.""" |
1737 | + error_dict = {} |
1738 | + self.ui.on_error(error_dict=error_dict) |
1739 | + |
1740 | + expected_msg = "%s (%s)" % (gui.VALUE_ERROR, gui.UNKNOWN_ERROR) |
1741 | + self.assert_warning_correct(self.ui.message, expected_msg) |
1742 | + self.assertFalse(self.ui.message.active) |
1743 | + |
1744 | + def test_on_error_with_error_dict_without_error_message(self): |
1745 | + """Callback to stop the Loading and show the error from error_dict.""" |
1746 | + error_dict = {gui.ERROR_TYPE: 'YaddaError'} |
1747 | + self.ui.on_error(error_dict=error_dict) |
1748 | + |
1749 | + expected_msg = "%s (%s)" % (gui.VALUE_ERROR, 'YaddaError') |
1750 | + self.assert_warning_correct(self.ui.message, expected_msg) |
1751 | + self.assertFalse(self.ui.message.active) |
1752 | + |
1753 | + def test_on_error_with_message_and_error_dict(self): |
1754 | + """Callback to stop the Loading and show a info message.""" |
1755 | + error_dict = {gui.ERROR_TYPE: 'YaddaError', gui.ERROR_MESSAGE: 'test'} |
1756 | + msg = 'WOW! <i>this does not rock</i> :-/' |
1757 | + self.ui.on_error(message=msg, error_dict=error_dict) |
1758 | + self.assert_warning_correct(self.ui.message, msg) |
1759 | + self.assertFalse(self.ui.message.active) |
1760 | + |
1761 | def test_is_processing(self): |
1762 | """The flag 'is_processing' is False on start.""" |
1763 | self.assertFalse(self.ui.is_processing) |
1764 | |
1765 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_package_manager.py' |
1766 | --- ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-01-07 20:07:39 +0000 |
1767 | +++ ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-04-08 19:43:13 +0000 |
1768 | @@ -21,10 +21,10 @@ |
1769 | import collections |
1770 | |
1771 | try: |
1772 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
1773 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
1774 | from aptdaemon import defer |
1775 | except ImportError: |
1776 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
1777 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
1778 | import defer |
1779 | |
1780 | from ubuntuone.controlpanel.gtk import package_manager |
1781 | |
1782 | === modified file 'ubuntuone/controlpanel/integrationtests/__init__.py' |
1783 | --- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-22 13:33:25 +0000 |
1784 | +++ ubuntuone/controlpanel/integrationtests/__init__.py 2011-04-08 19:43:13 +0000 |
1785 | @@ -20,7 +20,6 @@ |
1786 | """Integration tests for the Ubuntu One Control Panel.""" |
1787 | |
1788 | import dbus |
1789 | -import dbus.service |
1790 | |
1791 | from ubuntuone.devtools.testcase import DBusTestCase as TestCase |
1792 | |
1793 | @@ -31,7 +30,7 @@ |
1794 | """A DBus exception to be used in tests.""" |
1795 | |
1796 | |
1797 | -class MockDBusNoMethods(dbus.service.Object): |
1798 | +class MockDBusNoMethods(dbus_service.dbus.service.Object): |
1799 | """A mock that fails at the DBus layer (because it's got no methods!).""" |
1800 | |
1801 | |
1802 | |
1803 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py' |
1804 | --- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-02-23 13:57:42 +0000 |
1805 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-04-08 19:43:13 +0000 |
1806 | @@ -84,12 +84,23 @@ |
1807 | rs = self.mocker.replace(rs_name) |
1808 | rs() |
1809 | self.mocker.result(True) |
1810 | + |
1811 | + mainloop = "ubuntuone.controlpanel.dbus_service.gobject.MainLoop" |
1812 | + mainloop = self.mocker.replace(mainloop) |
1813 | + mainloop() |
1814 | + loop = self.mocker.mock() |
1815 | + self.mocker.result(loop) |
1816 | + |
1817 | + shutdown_func = self.mocker.mock() |
1818 | + loop.quit # pylint: disable=W0104 |
1819 | + self.mocker.result(shutdown_func) |
1820 | + |
1821 | rml_name = "ubuntuone.controlpanel.dbus_service.run_mainloop" |
1822 | rml = self.mocker.replace(rml_name) |
1823 | - rml() |
1824 | + rml(loop=loop) |
1825 | pb_name = "ubuntuone.controlpanel.dbus_service.publish_backend" |
1826 | pb = self.mocker.replace(pb_name) |
1827 | - pb() |
1828 | + pb(shutdown_func=shutdown_func) |
1829 | self.mocker.replay() |
1830 | dbus_service.main() |
1831 | |
1832 | @@ -112,6 +123,10 @@ |
1833 | dbus_service.STATUS_KEY: dbus_service.FILE_SYNC_IDLE, |
1834 | } |
1835 | status_changed_handler = None |
1836 | + shutdown_func = None |
1837 | + |
1838 | + def __init__(self, shutdown_func=None): |
1839 | + MockBackend.shutdown_func = shutdown_func |
1840 | |
1841 | def _process(self, result): |
1842 | """Process the request with the given result.""" |
1843 | @@ -197,6 +212,10 @@ |
1844 | """Install the extension to sync bookmarks.""" |
1845 | return self._process(None) |
1846 | |
1847 | + def shutdown(self): |
1848 | + """Stop this service.""" |
1849 | + self.shutdown_func() |
1850 | + |
1851 | |
1852 | class DBusServiceTestCase(TestCase): |
1853 | """Test for the DBus service.""" |
1854 | @@ -289,7 +308,8 @@ |
1855 | def setUp(self): |
1856 | super(BaseTestCase, self).setUp() |
1857 | dbus_service.init_mainloop() |
1858 | - be = dbus_service.publish_backend(MockBackend()) |
1859 | + self.patch(dbus_service, 'ControlBackend', MockBackend) |
1860 | + be = dbus_service.publish_backend() |
1861 | self.addCleanup(be.remove_from_connection) |
1862 | bus = dbus.SessionBus() |
1863 | obj = bus.get_object(bus_name=DBUS_BUS_NAME, |
1864 | @@ -604,6 +624,35 @@ |
1865 | error_sig, success_sig, got_error_signal, method, *args) |
1866 | |
1867 | |
1868 | +class OperationsAuthErrorTestCase(OperationsTestCase): |
1869 | + """Test for the DBus service operations when UnauthorizedError happens.""" |
1870 | + |
1871 | + def setUp(self): |
1872 | + super(OperationsAuthErrorTestCase, self).setUp() |
1873 | + self.patch(MockBackend, 'exception', |
1874 | + dbus_service.UnauthorizedError) |
1875 | + |
1876 | + def assert_correct_method_call(self, success_sig, error_sig, success_cb, |
1877 | + method, *args): |
1878 | + """Call parent instance expecting UnauthorizedError signal.""" |
1879 | + |
1880 | + def inner_success_cb(*a): |
1881 | + """The success signal was received.""" |
1882 | + if len(a) == 1: |
1883 | + error_dict = a[0] |
1884 | + else: |
1885 | + an_id, error_dict = a |
1886 | + self.assertEqual(an_id, args[0]) |
1887 | + |
1888 | + self.assertEqual(error_dict[dbus_service.ERROR_TYPE], |
1889 | + 'UnauthorizedError') |
1890 | + self.deferred.callback('success') |
1891 | + |
1892 | + parent = super(OperationsAuthErrorTestCase, self) |
1893 | + return parent.assert_correct_method_call( |
1894 | + "UnauthorizedError", error_sig, inner_success_cb, method, *args) |
1895 | + |
1896 | + |
1897 | class FileSyncTestCase(BaseTestCase): |
1898 | """Test for the DBus service when requesting file sync status.""" |
1899 | |
1900 | @@ -691,3 +740,18 @@ |
1901 | cpbe = dbus_service.ControlPanelBackend(backend=be) |
1902 | |
1903 | self.assertEqual(be.status_changed_handler, cpbe.process_status) |
1904 | + |
1905 | + |
1906 | +class ShutdownTestCase(BaseTestCase): |
1907 | + """Test for the DBus service shurdown.""" |
1908 | + |
1909 | + @defer.inlineCallbacks |
1910 | + def test_shutdown(self): |
1911 | + """The service can be shutdown.""" |
1912 | + called = [] |
1913 | + MockBackend.shutdown_func = lambda *a: called.append('shutdown') |
1914 | + self.backend.shutdown(reply_handler=lambda: self.deferred.callback(1), |
1915 | + error_handler=self.got_error) |
1916 | + yield self.deferred |
1917 | + |
1918 | + self.assertEqual(called, ['shutdown']) |
1919 | |
1920 | === added file 'ubuntuone/controlpanel/integrationtests/test_gui_service.py' |
1921 | --- ubuntuone/controlpanel/integrationtests/test_gui_service.py 1970-01-01 00:00:00 +0000 |
1922 | +++ ubuntuone/controlpanel/integrationtests/test_gui_service.py 2011-04-08 19:43:13 +0000 |
1923 | @@ -0,0 +1,98 @@ |
1924 | +# -*- coding: utf-8 -*- |
1925 | + |
1926 | +# Authors: Alejandro J. Cura <alecu@canonical.com> |
1927 | +# Natalia B. Bidart <nataliabidart@canonical.com> |
1928 | +# Eric Casteleijn <eric.casteleijn@canonical.com> |
1929 | +# |
1930 | +# Copyright 2011 Canonical Ltd. |
1931 | +# |
1932 | +# This program is free software: you can redistribute it and/or modify it |
1933 | +# under the terms of the GNU General Public License version 3, as published |
1934 | +# by the Free Software Foundation. |
1935 | +# |
1936 | +# This program is distributed in the hope that it will be useful, but |
1937 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1938 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1939 | +# PURPOSE. See the GNU General Public License for more details. |
1940 | +# |
1941 | +# You should have received a copy of the GNU General Public License along |
1942 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1943 | + |
1944 | +"""Tests for the control panel backend DBus service.""" |
1945 | + |
1946 | +import dbus |
1947 | +import mocker |
1948 | + |
1949 | +from dbus.mainloop.glib import DBusGMainLoop |
1950 | + |
1951 | +from ubuntuone.controlpanel.gtk import gui |
1952 | +from ubuntuone.devtools.testcase import DBusTestCase |
1953 | +from twisted.trial.unittest import TestCase |
1954 | + |
1955 | + |
1956 | +class MockWindow(object): |
1957 | + """A mock backend.""" |
1958 | + |
1959 | + exception = None |
1960 | + |
1961 | + def __init__(self, switch_to=None, alert=False): |
1962 | + self.called = [] |
1963 | + |
1964 | + def draw_attention(self): |
1965 | + """Draw attention to the control panel.""" |
1966 | + self.called.append('draw_attention') |
1967 | + |
1968 | + def switch_to(self, panel): |
1969 | + """Switch to named panel.""" |
1970 | + self.called.append(('switch_to', panel)) |
1971 | + |
1972 | + |
1973 | +class DBusServiceMockTestCase(TestCase): |
1974 | + """Tests for the main function.""" |
1975 | + |
1976 | + def setUp(self): |
1977 | + self.mocker = mocker.Mocker() |
1978 | + |
1979 | + def tearDown(self): |
1980 | + self.mocker.restore() |
1981 | + self.mocker.verify() |
1982 | + |
1983 | + def test_dbus_service_main(self): |
1984 | + """The main method starts the loop and hooks up to DBus.""" |
1985 | + self.patch(gui, 'ControlPanelWindow', MockWindow) |
1986 | + dbus_gmain_loop = self.mocker.replace( |
1987 | + "dbus.mainloop.glib.DBusGMainLoop") |
1988 | + register_service = self.mocker.replace( |
1989 | + "ubuntuone.controlpanel.gtk.gui.register_service") |
1990 | + publish_service = self.mocker.replace( |
1991 | + "ubuntuone.controlpanel.gtk.gui.publish_service") |
1992 | + main = self.mocker.replace("gtk.main") |
1993 | + dbus_gmain_loop(set_as_default=True) |
1994 | + loop = self.mocker.mock() |
1995 | + self.mocker.result(loop) |
1996 | + register_service(mocker.ANY) |
1997 | + self.mocker.result(True) |
1998 | + publish_service(switch_to='', alert=False) |
1999 | + main() |
2000 | + self.mocker.replay() |
2001 | + gui.main() |
2002 | + |
2003 | + |
2004 | +class DBusServiceTestCase(DBusTestCase): |
2005 | + """Test for the DBus service.""" |
2006 | + |
2007 | + def _set_called(self, *args, **kwargs): |
2008 | + """Keep track of function calls, useful for monkeypatching.""" |
2009 | + self._called = (args, kwargs) |
2010 | + |
2011 | + def setUp(self): |
2012 | + """Initialize each test run.""" |
2013 | + super(DBusServiceTestCase, self).setUp() |
2014 | + DBusGMainLoop(set_as_default=True) |
2015 | + self._called = False |
2016 | + |
2017 | + def test_register_service(self): |
2018 | + """The DBus service is successfully registered.""" |
2019 | + bus = dbus.SessionBus() |
2020 | + ret = gui.register_service(bus) |
2021 | + self.assertTrue(ret) |
2022 | |
2023 | === modified file 'ubuntuone/controlpanel/integrationtests/test_webclient.py' |
2024 | --- ubuntuone/controlpanel/integrationtests/test_webclient.py 2010-12-06 12:27:11 +0000 |
2025 | +++ ubuntuone/controlpanel/integrationtests/test_webclient.py 2011-04-08 19:43:13 +0000 |
2026 | @@ -73,6 +73,10 @@ |
2027 | devices_resource.contents = SAMPLE_RESOURCE |
2028 | root.putChild("devices", devices_resource) |
2029 | root.putChild("throwerror", resource.NoResource()) |
2030 | + unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED, |
2031 | + "Unauthrorized", "Unauthrorized") |
2032 | + root.putChild("unauthorized", unauthorized) |
2033 | + |
2034 | site = server.Site(root) |
2035 | application = service.Application('web') |
2036 | self.service_collection = service.IServiceCollection(application) |
2037 | @@ -96,7 +100,7 @@ |
2038 | class WebClientTestCase(TestCase): |
2039 | """Test for the webservice client.""" |
2040 | |
2041 | - timeout = 5 |
2042 | + timeout = 8 |
2043 | |
2044 | def setUp(self): |
2045 | super(WebClientTestCase, self).setUp() |
2046 | @@ -123,6 +127,12 @@ |
2047 | yield self.assertFailure(self.wc.call_api("throwerror"), |
2048 | webclient.WebClientError) |
2049 | |
2050 | + @inlineCallbacks |
2051 | + def test_unauthorized(self): |
2052 | + """Detect when a request failed with UNAUTHORIZED.""" |
2053 | + yield self.assertFailure(self.wc.call_api("unauthorized"), |
2054 | + webclient.UnauthorizedError) |
2055 | + |
2056 | |
2057 | class OAuthTestCase(TestCase): |
2058 | """Test for the oauth signing code.""" |
2059 | |
2060 | === modified file 'ubuntuone/controlpanel/replication_client.py' |
2061 | --- ubuntuone/controlpanel/replication_client.py 2011-01-07 20:07:39 +0000 |
2062 | +++ ubuntuone/controlpanel/replication_client.py 2011-04-08 19:43:13 +0000 |
2063 | @@ -57,7 +57,7 @@ |
2064 | if replication_module is None: |
2065 | # delay import in case DC is not installed at module import time |
2066 | # Unable to import 'desktopcouch.application.replication_services' |
2067 | - # pylint: disable=F0401 |
2068 | + # pylint: disable=W0404 |
2069 | from desktopcouch.application.replication_services \ |
2070 | import ubuntuone as replication_module |
2071 | try: |
2072 | |
2073 | === modified file 'ubuntuone/controlpanel/tests/__init__.py' |
2074 | --- ubuntuone/controlpanel/tests/__init__.py 2011-03-10 02:59:44 +0000 |
2075 | +++ ubuntuone/controlpanel/tests/__init__.py 2011-04-08 19:43:13 +0000 |
2076 | @@ -23,7 +23,7 @@ |
2077 | |
2078 | TOKEN = {u'consumer_key': u'xQ7xDAz', |
2079 | u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy', |
2080 | - u'token_name': u'test', |
2081 | + u'name': u'Ubuntu One @ localhost', |
2082 | u'token': u'ABCDEF01234-localtoken', |
2083 | u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'} |
2084 | |
2085 | @@ -110,19 +110,31 @@ |
2086 | ] |
2087 | """ |
2088 | |
2089 | +EMPTY_DESCRIPTION_JSON = """ |
2090 | +[ |
2091 | + { |
2092 | + "token": "ABCDEF01234token", |
2093 | + "description": null, |
2094 | + "kind": "Computer" |
2095 | + } |
2096 | +] |
2097 | +""" |
2098 | + |
2099 | +LOCAL_DEVICE = { |
2100 | + 'is_local': 'True', |
2101 | + 'configurable': 'True', |
2102 | + 'device_id': 'ComputerABCDEF01234-localtoken', |
2103 | + 'limit_bandwidth': '', |
2104 | + 'show_all_notifications': 'True', |
2105 | + 'max_download_speed': '-1', |
2106 | + 'max_upload_speed': '-1', |
2107 | + 'name': 'Ubuntu One @ localhost', |
2108 | + 'type': 'Computer', |
2109 | +} |
2110 | + |
2111 | # note that local computer should be first, do not change! |
2112 | EXPECTED_DEVICES_INFO = [ |
2113 | - { |
2114 | - 'is_local': 'True', |
2115 | - 'configurable': 'True', |
2116 | - 'device_id': 'ComputerABCDEF01234-localtoken', |
2117 | - 'limit_bandwidth': '', |
2118 | - 'show_all_notifications': 'True', |
2119 | - 'max_download_speed': '-1', |
2120 | - 'max_upload_speed': '-1', |
2121 | - 'name': 'Ubuntu One @ localhost', |
2122 | - 'type': 'Computer', |
2123 | - }, |
2124 | + LOCAL_DEVICE, |
2125 | { |
2126 | "device_id": "ComputerABCDEF01234token", |
2127 | "name": "Ubuntu One @ darkstar", |
2128 | |
2129 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' |
2130 | --- ubuntuone/controlpanel/tests/test_backend.py 2011-03-10 02:59:44 +0000 |
2131 | +++ ubuntuone/controlpanel/tests/test_backend.py 2011-04-08 19:43:13 +0000 |
2132 | @@ -30,6 +30,7 @@ |
2133 | from ubuntuone.controlpanel import backend, replication_client |
2134 | from ubuntuone.controlpanel.backend import (bool_str, |
2135 | ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API, |
2136 | + DEVICE_TYPE_COMPUTER, |
2137 | FILE_SYNC_DISABLED, |
2138 | FILE_SYNC_DISCONNECTED, |
2139 | FILE_SYNC_ERROR, |
2140 | @@ -41,9 +42,11 @@ |
2141 | MSG_KEY, STATUS_KEY, |
2142 | ) |
2143 | from ubuntuone.controlpanel.tests import (TestCase, |
2144 | + EMPTY_DESCRIPTION_JSON, |
2145 | EXPECTED_ACCOUNT_INFO, |
2146 | EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN, |
2147 | EXPECTED_DEVICES_INFO, |
2148 | + LOCAL_DEVICE, |
2149 | ROOT_PATH, |
2150 | SAMPLE_ACCOUNT_NO_CURRENT_PLAN, |
2151 | SAMPLE_ACCOUNT_WITH_CURRENT_PLAN, |
2152 | @@ -56,7 +59,6 @@ |
2153 | SHARES_PATH_LINK, |
2154 | TOKEN, |
2155 | ) |
2156 | -from ubuntuone.controlpanel.webclient import WebClientError |
2157 | |
2158 | |
2159 | class MockWebClient(object): |
2160 | @@ -70,8 +72,10 @@ |
2161 | |
2162 | def call_api(self, method): |
2163 | """Get a given url from the webservice.""" |
2164 | - if self.failure: |
2165 | - return defer.fail(WebClientError(self.failure)) |
2166 | + if self.failure == 401: |
2167 | + return defer.fail(backend.UnauthorizedError(self.failure)) |
2168 | + elif self.failure: |
2169 | + return defer.fail(backend.WebClientError(self.failure)) |
2170 | else: |
2171 | result = simplejson.loads(self.results[method]) |
2172 | return defer.succeed(result) |
2173 | @@ -250,7 +254,7 @@ |
2174 | self.patch(backend, "WebClient", MockWebClient) |
2175 | self.patch(backend, "dbus_client", MockDBusClient()) |
2176 | self.patch(backend, "replication_client", MockReplicationClient()) |
2177 | - self.local_token = "Computer" + TOKEN["token"] |
2178 | + self.local_token = DEVICE_TYPE_COMPUTER + TOKEN["token"] |
2179 | self.be = backend.ControlBackend() |
2180 | |
2181 | self.memento = MementoHandler() |
2182 | @@ -278,6 +282,24 @@ |
2183 | result = yield self.be.device_is_local(did) |
2184 | self.assertFalse(result) |
2185 | |
2186 | + def test_shutdown_func(self): |
2187 | + """A shutdown_func can be passed as creation parameter.""" |
2188 | + f = lambda: None |
2189 | + be = backend.ControlBackend(shutdown_func=f) |
2190 | + self.assertEqual(be.shutdown_func, f) |
2191 | + |
2192 | + def test_shutdown_func_is_called_on_shutdown(self): |
2193 | + """The shutdown_func is called on shutdown.""" |
2194 | + self.be.shutdown_func = self._set_called |
2195 | + self.be.shutdown() |
2196 | + self.assertEqual(self._called, ((), {})) |
2197 | + |
2198 | + def test_shutdown_func_when_none(self): |
2199 | + """The shutdown_func can be None.""" |
2200 | + self.be.shutdown_func = None |
2201 | + self.be.shutdown() |
2202 | + # nothing explodes |
2203 | + |
2204 | |
2205 | class BackendAccountTestCase(BackendBasicTestCase): |
2206 | """Account tests for the backend.""" |
2207 | @@ -305,7 +327,20 @@ |
2208 | """The account_info method exercises its errback.""" |
2209 | # pylint: disable=E1101 |
2210 | self.be.wc.failure = 404 |
2211 | - yield self.assertFailure(self.be.account_info(), WebClientError) |
2212 | + yield self.assertFailure(self.be.account_info(), |
2213 | + backend.WebClientError) |
2214 | + |
2215 | + @inlineCallbacks |
2216 | + def test_account_info_fails_with_unauthorized(self): |
2217 | + """The account_info clears the credentials on unauthorized.""" |
2218 | + # pylint: disable=E1101 |
2219 | + self.be.wc.failure = 401 |
2220 | + d = defer.Deferred() |
2221 | + self.patch(backend.dbus_client, 'clear_credentials', |
2222 | + lambda: d.callback('called')) |
2223 | + yield self.assertFailure(self.be.account_info(), |
2224 | + backend.UnauthorizedError) |
2225 | + yield d |
2226 | |
2227 | |
2228 | class BackendDevicesTestCase(BackendBasicTestCase): |
2229 | @@ -322,14 +357,86 @@ |
2230 | @inlineCallbacks |
2231 | def test_devices_info_fails(self): |
2232 | """The devices_info method exercises its errback.""" |
2233 | + def fail(*args, **kwargs): |
2234 | + """Raise any error other than WebClientError.""" |
2235 | + raise ValueError(args) |
2236 | + |
2237 | + self.patch(self.be.wc, 'call_api', fail) |
2238 | + yield self.assertFailure(self.be.devices_info(), ValueError) |
2239 | + |
2240 | + @inlineCallbacks |
2241 | + def test_devices_info_with_webclient_error(self): |
2242 | + """The devices_info returns local info if webclient error.""" |
2243 | # pylint: disable=E1101 |
2244 | self.be.wc.failure = 404 |
2245 | - yield self.assertFailure(self.be.devices_info(), WebClientError) |
2246 | + result = yield self.be.devices_info() |
2247 | + |
2248 | + self.assertEqual(result, [LOCAL_DEVICE]) |
2249 | + self.assertTrue(self.memento.check_error('devices_info', |
2250 | + 'web client failure')) |
2251 | + |
2252 | + @inlineCallbacks |
2253 | + def test_devices_info_fails_with_unauthorized(self): |
2254 | + """The devices_info clears the credentials on unauthorized.""" |
2255 | + # pylint: disable=E1101 |
2256 | + self.be.wc.failure = 401 |
2257 | + d = defer.Deferred() |
2258 | + self.patch(backend.dbus_client, 'clear_credentials', |
2259 | + lambda: d.callback('called')) |
2260 | + yield self.assertFailure(self.be.devices_info(), |
2261 | + backend.UnauthorizedError) |
2262 | + yield d |
2263 | + |
2264 | + @inlineCallbacks |
2265 | + def test_devices_info_if_files_disable(self): |
2266 | + """The devices_info returns device only info if files is disabled.""" |
2267 | + yield self.be.disable_files() |
2268 | + status = yield self.be.file_sync_status() |
2269 | + assert status['status'] == backend.FILE_SYNC_DISABLED, status |
2270 | + |
2271 | + # pylint: disable=E1101 |
2272 | + self.be.wc.results[DEVICES_API] = SAMPLE_DEVICES_JSON |
2273 | + result = yield self.be.devices_info() |
2274 | + |
2275 | + expected = EXPECTED_DEVICES_INFO[:] |
2276 | + for device in expected: |
2277 | + device.pop('limit_bandwidth', None) |
2278 | + device.pop('max_download_speed', None) |
2279 | + device.pop('max_upload_speed', None) |
2280 | + device.pop('show_all_notifications', None) |
2281 | + device['configurable'] = '' |
2282 | + self.assertEqual(result, expected) |
2283 | + |
2284 | + @inlineCallbacks |
2285 | + def test_devices_info_when_token_name_is_empty(self): |
2286 | + """The devices_info can handle empty token names.""" |
2287 | + # pylint: disable=E1101 |
2288 | + self.be.wc.results[DEVICES_API] = EMPTY_DESCRIPTION_JSON |
2289 | + result = yield self.be.devices_info() |
2290 | + expected = {'configurable': '', |
2291 | + 'device_id': 'ComputerABCDEF01234token', |
2292 | + 'is_local': '', 'name': 'None', |
2293 | + 'type': DEVICE_TYPE_COMPUTER} |
2294 | + self.assertEqual(result, [expected]) |
2295 | + self.assertTrue(self.memento.check_warning('name', 'None')) |
2296 | + |
2297 | + @inlineCallbacks |
2298 | + def test_devices_info_does_not_log_device_id(self): |
2299 | + """The devices_info does not log the device_id.""" |
2300 | + # pylint: disable=E1101 |
2301 | + self.be.wc.results[DEVICES_API] = SAMPLE_DEVICES_JSON |
2302 | + yield self.be.devices_info() |
2303 | + |
2304 | + dids = (d['device_id'] for d in EXPECTED_DEVICES_INFO) |
2305 | + device_id_logged = all(all(did not in r.getMessage() |
2306 | + for r in self.memento.records) |
2307 | + for did in dids) |
2308 | + self.assertTrue(device_id_logged) |
2309 | |
2310 | @inlineCallbacks |
2311 | def test_remove_device(self): |
2312 | """The remove_device method calls the right api.""" |
2313 | - dtype, did = "Computer", "SAMPLE-TOKEN" |
2314 | + dtype, did = DEVICE_TYPE_COMPUTER, "SAMPLE-TOKEN" |
2315 | device_id = dtype + did |
2316 | apiurl = DEVICE_REMOVE_API % (dtype.lower(), did) |
2317 | # pylint: disable=E1101 |
2318 | @@ -354,7 +461,30 @@ |
2319 | """The remove_device method fails as expected.""" |
2320 | # pylint: disable=E1101 |
2321 | self.be.wc.failure = 404 |
2322 | - yield self.assertFailure(self.be.devices_info(), WebClientError) |
2323 | + yield self.assertFailure(self.be.remove_device(self.local_token), |
2324 | + backend.WebClientError) |
2325 | + |
2326 | + @inlineCallbacks |
2327 | + def test_remove_device_fails_with_unauthorized(self): |
2328 | + """The remove_device clears the credentials on unauthorized.""" |
2329 | + # pylint: disable=E1101 |
2330 | + self.be.wc.failure = 401 |
2331 | + d = defer.Deferred() |
2332 | + self.patch(backend.dbus_client, 'clear_credentials', |
2333 | + lambda: d.callback('called')) |
2334 | + yield self.assertFailure(self.be.remove_device(self.local_token), |
2335 | + backend.UnauthorizedError) |
2336 | + yield d |
2337 | + |
2338 | + @inlineCallbacks |
2339 | + def test_remove_device_does_not_log_device_id(self): |
2340 | + """The remove_device does not log the device_id.""" |
2341 | + device_id = DEVICE_TYPE_COMPUTER + TOKEN['token'] |
2342 | + yield self.be.remove_device(device_id) |
2343 | + |
2344 | + device_id_logged = all(device_id not in r.getMessage() |
2345 | + for r in self.memento.records) |
2346 | + self.assertTrue(device_id_logged) |
2347 | |
2348 | @inlineCallbacks |
2349 | def test_change_show_all_notifications(self): |
2350 | @@ -411,6 +541,16 @@ |
2351 | self.assertEqual(backend.dbus_client.limits["upload"], -1) |
2352 | self.assertEqual(backend.dbus_client.limits["download"], -1) |
2353 | |
2354 | + @inlineCallbacks |
2355 | + def test_changing_settings_does_not_log_device_id(self): |
2356 | + """The change_device_settings does not log the device_id.""" |
2357 | + device_id = 'yadda-yadda' |
2358 | + yield self.be.change_device_settings(device_id, {}) |
2359 | + |
2360 | + device_id_logged = all(device_id not in r.getMessage() |
2361 | + for r in self.memento.records) |
2362 | + self.assertTrue(device_id_logged) |
2363 | + |
2364 | |
2365 | class BackendVolumesTestCase(BackendBasicTestCase): |
2366 | """Volumes tests for the backend.""" |
2367 | @@ -581,6 +721,12 @@ |
2368 | class BackendSyncStatusTestCase(BackendBasicTestCase): |
2369 | """Syncdaemon state for the backend.""" |
2370 | |
2371 | + was_disabled = False |
2372 | + |
2373 | + def setUp(self): |
2374 | + super(BackendSyncStatusTestCase, self).setUp() |
2375 | + self.be.file_sync_disabled = self.was_disabled |
2376 | + |
2377 | def _build_msg(self): |
2378 | """Build expected message regarding file sync status.""" |
2379 | return '%s (%s)' % (MockDBusClient.status['description'], |
2380 | @@ -599,6 +745,7 @@ |
2381 | """The syncdaemon status is processed and emitted.""" |
2382 | self.patch(MockDBusClient, 'file_sync', False) |
2383 | yield self.assert_correct_status(FILE_SYNC_DISABLED, msg='') |
2384 | + self.assertTrue(self.be.file_sync_disabled) |
2385 | |
2386 | @inlineCallbacks |
2387 | def test_error(self): |
2388 | @@ -610,6 +757,8 @@ |
2389 | 'description': 'auth failed', |
2390 | } |
2391 | yield self.assert_correct_status(FILE_SYNC_ERROR) |
2392 | + # self.be.file_sync_disabled does not change |
2393 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2394 | |
2395 | @inlineCallbacks |
2396 | def test_starting_when_init_not_user(self): |
2397 | @@ -620,6 +769,7 @@ |
2398 | 'name': 'INIT', 'description': 'something new', |
2399 | } |
2400 | yield self.assert_correct_status(FILE_SYNC_STARTING) |
2401 | + self.assertFalse(self.be.file_sync_disabled) |
2402 | |
2403 | @inlineCallbacks |
2404 | def test_starting_when_init_with_user(self): |
2405 | @@ -630,6 +780,7 @@ |
2406 | 'name': 'INIT', 'description': 'something new', |
2407 | } |
2408 | yield self.assert_correct_status(FILE_SYNC_STARTING) |
2409 | + self.assertFalse(self.be.file_sync_disabled) |
2410 | |
2411 | @inlineCallbacks |
2412 | def test_starting_when_local_rescan_not_user(self): |
2413 | @@ -640,6 +791,7 @@ |
2414 | 'name': 'LOCAL_RESCAN', 'description': 'something new', |
2415 | } |
2416 | yield self.assert_correct_status(FILE_SYNC_STARTING) |
2417 | + self.assertFalse(self.be.file_sync_disabled) |
2418 | |
2419 | @inlineCallbacks |
2420 | def test_starting_when_local_rescan_with_user(self): |
2421 | @@ -650,6 +802,7 @@ |
2422 | 'name': 'LOCAL_RESCAN', 'description': 'something new', |
2423 | } |
2424 | yield self.assert_correct_status(FILE_SYNC_STARTING) |
2425 | + self.assertFalse(self.be.file_sync_disabled) |
2426 | |
2427 | @inlineCallbacks |
2428 | def test_starting_when_ready_with_user(self): |
2429 | @@ -660,6 +813,7 @@ |
2430 | 'name': 'READY', 'description': 'something nicer', |
2431 | } |
2432 | yield self.assert_correct_status(FILE_SYNC_STARTING) |
2433 | + self.assertFalse(self.be.file_sync_disabled) |
2434 | |
2435 | @inlineCallbacks |
2436 | def test_disconnected(self): |
2437 | @@ -672,6 +826,9 @@ |
2438 | } |
2439 | yield self.assert_correct_status(FILE_SYNC_DISCONNECTED) |
2440 | |
2441 | + # self.be.file_sync_disabled does not change |
2442 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2443 | + |
2444 | @inlineCallbacks |
2445 | def test_disconnected_when_waiting(self): |
2446 | """The syncdaemon status is processed and emitted.""" |
2447 | @@ -682,6 +839,9 @@ |
2448 | } |
2449 | yield self.assert_correct_status(FILE_SYNC_DISCONNECTED) |
2450 | |
2451 | + # self.be.file_sync_disabled does not change |
2452 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2453 | + |
2454 | @inlineCallbacks |
2455 | def test_syncing_if_online(self): |
2456 | """The syncdaemon status is processed and emitted.""" |
2457 | @@ -693,6 +853,9 @@ |
2458 | } |
2459 | yield self.assert_correct_status(FILE_SYNC_SYNCING) |
2460 | |
2461 | + # self.be.file_sync_disabled does not change |
2462 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2463 | + |
2464 | @inlineCallbacks |
2465 | def test_syncing_even_if_not_online(self): |
2466 | """The syncdaemon status is processed and emitted.""" |
2467 | @@ -704,6 +867,9 @@ |
2468 | } |
2469 | yield self.assert_correct_status(FILE_SYNC_SYNCING) |
2470 | |
2471 | + # self.be.file_sync_disabled does not change |
2472 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2473 | + |
2474 | @inlineCallbacks |
2475 | def test_idle(self): |
2476 | """The syncdaemon status is processed and emitted.""" |
2477 | @@ -715,6 +881,9 @@ |
2478 | } |
2479 | yield self.assert_correct_status(FILE_SYNC_IDLE) |
2480 | |
2481 | + # self.be.file_sync_disabled does not change |
2482 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2483 | + |
2484 | @inlineCallbacks |
2485 | def test_stopped(self): |
2486 | """The syncdaemon status is processed and emitted.""" |
2487 | @@ -726,6 +895,9 @@ |
2488 | } |
2489 | yield self.assert_correct_status(FILE_SYNC_STOPPED) |
2490 | |
2491 | + # self.be.file_sync_disabled does not change |
2492 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2493 | + |
2494 | @inlineCallbacks |
2495 | def test_unknown(self): |
2496 | """The syncdaemon status is processed and emitted.""" |
2497 | @@ -740,6 +912,9 @@ |
2498 | repr(MockDBusClient.status)) |
2499 | self.assertTrue(has_warning) |
2500 | |
2501 | + # self.be.file_sync_disabled does not change |
2502 | + self.assertEqual(self.was_disabled, self.be.file_sync_disabled) |
2503 | + |
2504 | def test_status_changed(self): |
2505 | """The file_sync_status is the status changed handler.""" |
2506 | self.be.status_changed_handler = self._set_called |
2507 | @@ -754,6 +929,21 @@ |
2508 | self.assertEqual(self._called, ((expected_status,), {})) |
2509 | |
2510 | |
2511 | +class BackendSyncStatusIfDisabledTestCase(BackendSyncStatusTestCase): |
2512 | + """Syncdaemon state for the backend when file sync is disabled.""" |
2513 | + |
2514 | + was_disabled = True |
2515 | + |
2516 | + @inlineCallbacks |
2517 | + def assert_correct_status(self, status, msg=None): |
2518 | + """Check that the resulting status is correct.""" |
2519 | + sup = super(BackendSyncStatusIfDisabledTestCase, self) |
2520 | + if status != FILE_SYNC_STARTING: |
2521 | + yield sup.assert_correct_status(FILE_SYNC_DISABLED, msg='') |
2522 | + else: |
2523 | + yield sup.assert_correct_status(status, msg=msg) |
2524 | + |
2525 | + |
2526 | class BackendFileSyncOpsTestCase(BackendBasicTestCase): |
2527 | """Syncdaemon operations for the backend.""" |
2528 | |
2529 | @@ -768,6 +958,7 @@ |
2530 | |
2531 | yield self.be.enable_files() |
2532 | self.assertTrue(MockDBusClient.file_sync) |
2533 | + self.assertFalse(self.be.file_sync_disabled) |
2534 | |
2535 | @inlineCallbacks |
2536 | def test_disable_files(self): |
2537 | @@ -776,6 +967,7 @@ |
2538 | |
2539 | yield self.be.disable_files() |
2540 | self.assertFalse(MockDBusClient.file_sync) |
2541 | + self.assertTrue(self.be.file_sync_disabled) |
2542 | |
2543 | @inlineCallbacks |
2544 | def test_connect_files(self): |
2545 | @@ -783,6 +975,7 @@ |
2546 | yield self.be.connect_files() |
2547 | |
2548 | self.assertEqual(MockDBusClient.actions, ['connect']) |
2549 | + self.assertFalse(self.be.file_sync_disabled) |
2550 | |
2551 | @inlineCallbacks |
2552 | def test_disconnect_files(self): |
2553 | @@ -790,6 +983,7 @@ |
2554 | yield self.be.disconnect_files() |
2555 | |
2556 | self.assertEqual(MockDBusClient.actions, ['disconnect']) |
2557 | + self.assertFalse(self.be.file_sync_disabled) |
2558 | |
2559 | @inlineCallbacks |
2560 | def test_restart_files(self): |
2561 | @@ -797,6 +991,7 @@ |
2562 | yield self.be.restart_files() |
2563 | |
2564 | self.assertEqual(MockDBusClient.actions, ['stop', 'start']) |
2565 | + self.assertFalse(self.be.file_sync_disabled) |
2566 | |
2567 | @inlineCallbacks |
2568 | def test_start_files(self): |
2569 | @@ -804,6 +999,7 @@ |
2570 | yield self.be.start_files() |
2571 | |
2572 | self.assertEqual(MockDBusClient.actions, ['start']) |
2573 | + self.assertFalse(self.be.file_sync_disabled) |
2574 | |
2575 | @inlineCallbacks |
2576 | def test_stop_files(self): |
2577 | @@ -811,6 +1007,7 @@ |
2578 | yield self.be.stop_files() |
2579 | |
2580 | self.assertEqual(MockDBusClient.actions, ['stop']) |
2581 | + self.assertFalse(self.be.file_sync_disabled) |
2582 | |
2583 | |
2584 | class BackendReplicationsTestCase(BackendBasicTestCase): |
2585 | |
2586 | === modified file 'ubuntuone/controlpanel/utils.py' |
2587 | --- ubuntuone/controlpanel/utils.py 2010-12-22 13:33:25 +0000 |
2588 | +++ ubuntuone/controlpanel/utils.py 2011-04-08 19:43:13 +0000 |
2589 | @@ -52,7 +52,7 @@ |
2590 | |
2591 | # otherwise, try to load PROJECT_DIR from installation path |
2592 | try: |
2593 | - # pylint: disable=F0401, E0611 |
2594 | + # pylint: disable=F0401, E0611, W0404 |
2595 | from ubuntuone.controlpanel.constants import PROJECT_DIR |
2596 | return PROJECT_DIR |
2597 | except ImportError: |
2598 | |
2599 | === modified file 'ubuntuone/controlpanel/webclient.py' |
2600 | --- ubuntuone/controlpanel/webclient.py 2010-12-22 13:33:25 +0000 |
2601 | +++ ubuntuone/controlpanel/webclient.py 2011-04-08 19:43:13 +0000 |
2602 | @@ -32,10 +32,18 @@ |
2603 | logger = setup_logging('webclient') |
2604 | |
2605 | |
2606 | +# full list of status codes |
2607 | +# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html |
2608 | + |
2609 | + |
2610 | class WebClientError(Exception): |
2611 | """An http error happened while calling the webservice.""" |
2612 | |
2613 | |
2614 | +class UnauthorizedError(WebClientError): |
2615 | + """The request ended with bad_request, unauthorized or forbidden.""" |
2616 | + |
2617 | + |
2618 | class WebClient(object): |
2619 | """A client for the u1 webservice.""" |
2620 | |
2621 | @@ -48,21 +56,24 @@ |
2622 | |
2623 | def _handler(self, session, msg, d): |
2624 | """Handle the result of an http message.""" |
2625 | - logger.debug("WebClient: got http response %d for uri %r", |
2626 | + logger.debug("got http response %d for uri %r", |
2627 | msg.status_code, msg.get_uri().to_string(False)) |
2628 | data = msg.response_body.data |
2629 | if msg.status_code == 200: |
2630 | result = simplejson.loads(data) |
2631 | d.callback(result) |
2632 | else: |
2633 | - e = WebClientError(msg.status_code, data) |
2634 | + if msg.status_code in (401,): |
2635 | + e = UnauthorizedError(msg.status_code, data) |
2636 | + else: |
2637 | + e = WebClientError(msg.status_code, data) |
2638 | d.errback(e) |
2639 | |
2640 | def _call_api_with_creds(self, credentials, api_name): |
2641 | """Get a given url from the webservice with credentials.""" |
2642 | url = (self.base_url + api_name).encode('utf-8') |
2643 | method = "GET" |
2644 | - logger.debug("WebClient: getting url: %s, %s", method, url) |
2645 | + logger.debug("getting url: %s, %s", method, url) |
2646 | msg = Soup.Message.new(method, url) |
2647 | add_oauth_headers(msg.request_headers.append, method, url, credentials) |
2648 | d = defer.Deferred() |
2649 | @@ -71,7 +82,8 @@ |
2650 | |
2651 | def call_api(self, api_name): |
2652 | """Get a given url from the webservice.""" |
2653 | - logger.debug("WebClient: calling api: %s", api_name) |
2654 | + # this may log device ID's, but only for removals, which is OK |
2655 | + logger.debug("calling api: %s", api_name) |
2656 | d = self.get_credentials() |
2657 | d.addCallback(self._call_api_with_creds, api_name) |
2658 | return d |