Merge lp:~nataliabidart/ubuntuone-control-panel/add-file-sync-status into lp:ubuntuone-control-panel
- add-file-sync-status
- Merge into trunk
Proposed by
Natalia Bidart
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Natalia Bidart | ||||||||
Approved revision: | 38 | ||||||||
Merged at revision: | 31 | ||||||||
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/add-file-sync-status | ||||||||
Merge into: | lp:ubuntuone-control-panel | ||||||||
Diff against target: |
1208 lines (+763/-65) 11 files modified
ubuntuone/controlpanel/backend.py (+85/-2) ubuntuone/controlpanel/dbus_client.py (+35/-1) ubuntuone/controlpanel/dbus_service.py (+68/-14) ubuntuone/controlpanel/gtk/gui.py (+67/-3) ubuntuone/controlpanel/gtk/tests/test_gui.py (+73/-1) ubuntuone/controlpanel/integrationtests/__init__.py (+5/-0) ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py (+124/-2) ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+87/-19) ubuntuone/controlpanel/logger.py (+24/-1) ubuntuone/controlpanel/tests/test_backend.py (+195/-2) ubuntuone/controlpanel/utils.py (+0/-20) |
||||||||
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/add-file-sync-status | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alejandro J. Cura (community) | Approve | ||
Roberto Alsina (community) | Approve | ||
Review via email: mp+43264@code.launchpad.net |
Description of the change
To test, do the following (you will need to point PYTHONPATH to current's u1client trunk):
in terminal 1:
PYTHONPATH=
in terminal 2:
PYTHONPATH=
You can check in the top right corner how the file sync is updated.
To post a comment you must log in.
- 37. By Natalia Bidart
-
Files sync availability is queried to SyncdaemonTool.
- 38. By Natalia Bidart
-
Fixing typo.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'ubuntuone/controlpanel/backend.py' |
2 | --- ubuntuone/controlpanel/backend.py 2010-12-02 16:23:03 +0000 |
3 | +++ ubuntuone/controlpanel/backend.py 2010-12-10 15:05:12 +0000 |
4 | @@ -22,8 +22,12 @@ |
5 | from twisted.internet.defer import inlineCallbacks, returnValue |
6 | |
7 | from ubuntuone.controlpanel import dbus_client |
8 | +from ubuntuone.controlpanel.logger import setup_logging, log_call |
9 | from ubuntuone.controlpanel.webclient import WebClient |
10 | |
11 | + |
12 | +logger = setup_logging('backend') |
13 | + |
14 | ACCOUNT_API = "account/" |
15 | QUOTA_API = "quota/" |
16 | DEVICES_API = "1.0/devices/" |
17 | @@ -33,6 +37,17 @@ |
18 | UPLOAD_KEY = "max_upload_speed" |
19 | DOWNLOAD_KEY = "max_download_speed" |
20 | |
21 | +FILE_SYNC_DISABLED = 'file-sync-disabled' |
22 | +FILE_SYNC_DISCONNECTED = 'file-sync-disconnected' |
23 | +FILE_SYNC_ERROR = 'file-sync-error' |
24 | +FILE_SYNC_IDLE = 'file-sync-idle' |
25 | +FILE_SYNC_STARTING = 'file-sync-starting' |
26 | +FILE_SYNC_SYNCING = 'file-sync-syncing' |
27 | +FILE_SYNC_UNKNOWN = 'file-sync-unknown' |
28 | + |
29 | +MSG_KEY = 'message' |
30 | +STATUS_KEY = 'status' |
31 | + |
32 | |
33 | class ControlBackend(object): |
34 | """The control panel backend.""" |
35 | @@ -40,6 +55,68 @@ |
36 | def __init__(self): |
37 | """Initialize the webclient.""" |
38 | self.wc = WebClient(dbus_client.get_credentials) |
39 | + self._status_changed_handler = None |
40 | + self.status_changed_handler = lambda *a: None |
41 | + |
42 | + def _process_file_sync_status(self, status): |
43 | + """Process raw file sync status into custom format. |
44 | + |
45 | + Return a dictionary with two members: |
46 | + * STATUS_KEY: the current status of syncdaemon, can be one of: |
47 | + FILE_SYNC_DISABLED, FILE_SYNC_STARTING, FILE_SYNC_DISCONNECTED, |
48 | + FILE_SYNC_SYNCING, FILE_SYNC_IDLE, FILE_SYNC_ERROR, |
49 | + FILE_SYNC_UNKNOWN |
50 | + * MSG_KEY: a non translatable but human readable string of the status. |
51 | + |
52 | + """ |
53 | + if not status: |
54 | + return {MSG_KEY: '', STATUS_KEY: FILE_SYNC_DISABLED} |
55 | + |
56 | + msg = '%s (%s)' % (status['description'], status['name']) |
57 | + result = {MSG_KEY: msg} |
58 | + |
59 | + # file synch is enabled |
60 | + is_error = bool(status['is_error']) |
61 | + is_synching = bool(status['is_connected']) |
62 | + is_idle = bool(status['is_online']) and status['queues'] == 'IDLE' |
63 | + is_disconnected = status['name'] == 'READY' and \ |
64 | + 'Not User' in status['connection'] |
65 | + is_starting = status['name'] in ('INIT', 'LOCAL_RESCAN', 'READY') |
66 | + |
67 | + if is_error: |
68 | + result[STATUS_KEY] = FILE_SYNC_ERROR |
69 | + elif is_idle: |
70 | + result[STATUS_KEY] = FILE_SYNC_IDLE |
71 | + elif is_synching: |
72 | + result[STATUS_KEY] = FILE_SYNC_SYNCING |
73 | + elif is_disconnected: |
74 | + result[STATUS_KEY] = FILE_SYNC_DISCONNECTED |
75 | + elif is_starting: |
76 | + result[STATUS_KEY] = FILE_SYNC_STARTING |
77 | + else: |
78 | + logger.warning('file_sync_status: unknown (got %r)', status) |
79 | + result[STATUS_KEY] = FILE_SYNC_UNKNOWN |
80 | + |
81 | + return result |
82 | + |
83 | + def _set_status_changed_handler(self, handler): |
84 | + """Set 'handler' to be called when file sync status changes.""" |
85 | + logger.debug('setting status_changed_handler to %r', handler) |
86 | + |
87 | + def process_and_callback(status): |
88 | + """Process syncdaemon's status and callback 'handler'.""" |
89 | + result = self._process_file_sync_status(status) |
90 | + handler(result) |
91 | + |
92 | + self._status_changed_handler = process_and_callback |
93 | + dbus_client.set_status_changed_handler(process_and_callback) |
94 | + |
95 | + def _get_status_changed_handler(self, handler): |
96 | + """Return the handler to be called when file sync status changes.""" |
97 | + return self._status_changed_handler |
98 | + |
99 | + status_changed_handler = property(_get_status_changed_handler, |
100 | + _set_status_changed_handler) |
101 | |
102 | @inlineCallbacks |
103 | def get_token(self): |
104 | @@ -145,10 +222,16 @@ |
105 | yield self.wc.call_api(api) |
106 | returnValue(device_id) |
107 | |
108 | + @log_call(logger.info) |
109 | + @inlineCallbacks |
110 | def file_sync_status(self): |
111 | """Return the status of the file sync service.""" |
112 | - # still pending (LP: #673670) |
113 | - returnValue((True, "Synchronizing")) |
114 | + enabled = yield dbus_client.files_sync_enabled() |
115 | + if enabled: |
116 | + status = yield dbus_client.get_current_status() |
117 | + else: |
118 | + status = {} |
119 | + returnValue(self._process_file_sync_status(status)) |
120 | |
121 | @inlineCallbacks |
122 | def volumes_info(self): |
123 | |
124 | === modified file 'ubuntuone/controlpanel/dbus_client.py' |
125 | --- ubuntuone/controlpanel/dbus_client.py 2010-12-02 16:35:31 +0000 |
126 | +++ ubuntuone/controlpanel/dbus_client.py 2010-12-10 15:05:12 +0000 |
127 | @@ -25,7 +25,8 @@ |
128 | from twisted.internet import defer |
129 | |
130 | from ubuntuone.clientdefs import APP_NAME |
131 | -from ubuntuone.syncdaemon import dbus_interface as sd_dbus_iface |
132 | +from ubuntuone.platform.linux import dbus_interface as sd_dbus_iface |
133 | +from ubuntuone.platform.linux.tools import SyncDaemonTool |
134 | |
135 | from ubuntuone.controlpanel.logger import setup_logging |
136 | |
137 | @@ -106,6 +107,12 @@ |
138 | sd_dbus_iface.DBUS_IFACE_FOLDERS_NAME) |
139 | |
140 | |
141 | +def get_status_syncdaemon_proxy(): |
142 | + """Get a DBus proxy for syncdaemon status calls.""" |
143 | + return get_syncdaemon_proxy("/status", |
144 | + sd_dbus_iface.DBUS_IFACE_STATUS_NAME) |
145 | + |
146 | + |
147 | def get_throttling_limits(): |
148 | """Get the speed limits from the syncdaemon.""" |
149 | d = defer.Deferred() |
150 | @@ -183,3 +190,30 @@ |
151 | |
152 | d.addBoth(cleanup_signals) |
153 | return d |
154 | + |
155 | + |
156 | +def get_current_status(): |
157 | + """Retrieve the current status from syncdaemon.""" |
158 | + d = defer.Deferred() |
159 | + proxy = get_status_syncdaemon_proxy() |
160 | + proxy.current_status(reply_handler=d.callback, error_handler=d.errback) |
161 | + return d |
162 | + |
163 | + |
164 | +def set_status_changed_handler(handler): |
165 | + """Connect 'handler' with syncdaemon's StatusChanged signal.""" |
166 | + proxy = get_status_syncdaemon_proxy() |
167 | + sig = proxy.connect_to_signal('StatusChanged', handler) |
168 | + return proxy, sig |
169 | + |
170 | + |
171 | +def files_sync_enabled(): |
172 | + """Get if file sync service is enabled.""" |
173 | + enabled = SyncDaemonTool(bus=None).is_files_sync_enabled() |
174 | + return defer.succeed(enabled) |
175 | + |
176 | + |
177 | +@defer.inlineCallbacks |
178 | +def set_files_sync_enabled(enabled): |
179 | + """Set the file sync service to be 'enabled'.""" |
180 | + yield SyncDaemonTool(bus=dbus.SessionBus()).enable_files_sync(enabled) |
181 | |
182 | === modified file 'ubuntuone/controlpanel/dbus_service.py' |
183 | --- ubuntuone/controlpanel/dbus_service.py 2010-12-02 16:35:31 +0000 |
184 | +++ ubuntuone/controlpanel/dbus_service.py 2010-12-10 15:05:12 +0000 |
185 | @@ -28,9 +28,15 @@ |
186 | from twisted.python.failure import Failure |
187 | |
188 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
189 | - DBUS_PREFERENCES_IFACE, utils) |
190 | -from ubuntuone.controlpanel.backend import ControlBackend |
191 | -from ubuntuone.controlpanel.logger import setup_logging |
192 | + DBUS_PREFERENCES_IFACE) |
193 | +from ubuntuone.controlpanel.backend import ( |
194 | + ControlBackend, FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED, |
195 | + FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_SYNCING, |
196 | + MSG_KEY, STATUS_KEY, |
197 | +) |
198 | +from ubuntuone.controlpanel.logger import setup_logging, log_call |
199 | +from ubuntuone.controlpanel.utils import (ERROR_TYPE, ERROR_MESSAGE, |
200 | + failure_to_error_dict, exception_to_error_dict) |
201 | |
202 | |
203 | logger = setup_logging('dbus_service') |
204 | @@ -55,15 +61,15 @@ |
205 | """ |
206 | result = {} |
207 | if isinstance(error, Failure): |
208 | - result = utils.failure_to_error_dict(error) |
209 | + result = failure_to_error_dict(error) |
210 | elif isinstance(error, Exception): |
211 | - result = utils.exception_to_error_dict(error) |
212 | + result = exception_to_error_dict(error) |
213 | elif isinstance(error, dict): |
214 | # ensure that both keys and values are unicodes |
215 | result = dict(map(make_unicode, i) for i in error.iteritems()) |
216 | else: |
217 | msg = 'Got unexpected error argument %r' % error |
218 | - result = {utils.ERROR_TYPE: 'UnknownError', utils.ERROR_MESSAGE: msg} |
219 | + result = {ERROR_TYPE: 'UnknownError', ERROR_MESSAGE: msg} |
220 | |
221 | return result |
222 | |
223 | @@ -89,7 +95,10 @@ |
224 | """Create this instance of the backend.""" |
225 | super(ControlPanelBackend, self).__init__(*args, **kwargs) |
226 | self.backend = backend |
227 | - logger.debug('ControlPanelBackend created with %r, %r', args, kwargs) |
228 | + self.backend.status_changed_handler = self.process_status |
229 | + logger.debug('ControlPanelBackend: created with %r, %r.\n' |
230 | + 'status_changed_handler is %r.', |
231 | + args, kwargs, self.process_status) |
232 | |
233 | # pylint: disable=C0103 |
234 | |
235 | @@ -161,17 +170,62 @@ |
236 | |
237 | #--- |
238 | |
239 | + def process_status(self, status_dict): |
240 | + """Match status with signals.""" |
241 | + logger.info('process_status: new status received %r', status_dict) |
242 | + status = status_dict[STATUS_KEY] |
243 | + msg = status_dict[MSG_KEY] |
244 | + if status == FILE_SYNC_DISABLED: |
245 | + self.FileSyncStatusDisabled(msg) |
246 | + elif status == FILE_SYNC_STARTING: |
247 | + self.FileSyncStatusStarting(msg) |
248 | + elif status == FILE_SYNC_DISCONNECTED: |
249 | + self.FileSyncStatusDisconnected(msg) |
250 | + elif status == FILE_SYNC_SYNCING: |
251 | + self.FileSyncStatusSyncing(msg) |
252 | + elif status == FILE_SYNC_IDLE: |
253 | + self.FileSyncStatusIdle(msg) |
254 | + elif status == FILE_SYNC_ERROR: |
255 | + error_dict = {ERROR_TYPE: 'FileSyncStatusError', |
256 | + ERROR_MESSAGE: msg} |
257 | + self.FileSyncStatusError(error_dict) |
258 | + else: |
259 | + self.FileSyncStatusError(error_handler(status_dict)) |
260 | + |
261 | + @log_call(logger.info) |
262 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
263 | def file_sync_status(self): |
264 | """Get the status of the file sync service.""" |
265 | d = self.backend.file_sync_status() |
266 | - d.addCallback(lambda args: self.FileSyncStatusReady(*args)) |
267 | + d.addCallback(self.process_status) |
268 | d.addErrback(transform_failure(self.FileSyncStatusError)) |
269 | |
270 | - @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="bs") |
271 | - def FileSyncStatusReady(self, enabled, status): |
272 | - """The new file sync status is available.""" |
273 | - |
274 | + @log_call(logger.debug) |
275 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
276 | + def FileSyncStatusDisabled(self, msg): |
277 | + """The file sync status is disabled.""" |
278 | + |
279 | + @log_call(logger.debug) |
280 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
281 | + def FileSyncStatusStarting(self, msg): |
282 | + """The file sync service is starting.""" |
283 | + |
284 | + @log_call(logger.debug) |
285 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
286 | + def FileSyncStatusDisconnected(self, msg): |
287 | + """The file sync service is waiting for user to request connection.""" |
288 | + |
289 | + @log_call(logger.debug) |
290 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
291 | + def FileSyncStatusSyncing(self, msg): |
292 | + """The file sync service is currently syncing.""" |
293 | + |
294 | + @log_call(logger.debug) |
295 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
296 | + def FileSyncStatusIdle(self, msg): |
297 | + """The file sync service is idle.""" |
298 | + |
299 | + @log_call(logger.error) |
300 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
301 | def FileSyncStatusError(self, error): |
302 | """Problem getting the file sync status.""" |
303 | @@ -185,12 +239,12 @@ |
304 | d.addCallback(self.VolumesInfoReady) |
305 | d.addErrback(transform_failure(self.VolumesInfoError)) |
306 | |
307 | - @utils.log_call(logger.info) |
308 | + @log_call(logger.debug) |
309 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
310 | def VolumesInfoReady(self, info): |
311 | """The info for the volumes is available right now.""" |
312 | |
313 | - @utils.log_call(logger.error) |
314 | + @log_call(logger.error) |
315 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
316 | def VolumesInfoError(self, error): |
317 | """The info for the volumes is currently unavailable.""" |
318 | |
319 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' |
320 | --- ubuntuone/controlpanel/gtk/gui.py 2010-12-01 12:51:55 +0000 |
321 | +++ ubuntuone/controlpanel/gtk/gui.py 2010-12-10 15:05:12 +0000 |
322 | @@ -46,8 +46,8 @@ |
323 | |
324 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
325 | DBUS_PREFERENCES_IFACE) |
326 | -from ubuntuone.controlpanel.logger import setup_logging |
327 | -from ubuntuone.controlpanel.utils import get_data_file, log_call |
328 | +from ubuntuone.controlpanel.logger import setup_logging, log_call |
329 | +from ubuntuone.controlpanel.utils import get_data_file |
330 | |
331 | |
332 | logger = setup_logging('gtk.gui') |
333 | @@ -188,7 +188,9 @@ |
334 | |
335 | def on_show_management_panel(self, *args, **kwargs): |
336 | """Show the netbook (main panel).""" |
337 | - self.add(ManagementPanel()) |
338 | + if self.overview in self.get_children(): |
339 | + self.remove(self.overview) |
340 | + self.add(ManagementPanel()) |
341 | |
342 | |
343 | class UbuntuOneBin(gtk.VBox): |
344 | @@ -486,6 +488,16 @@ |
345 | """ |
346 | |
347 | QUOTA_LABEL = _('%(used)s used of %(total)s (%(percentage).1f%%)') |
348 | + FILE_SYNC_DISABLED = _('File synchronization service is not enabled.') |
349 | + FILE_SYNC_STARTING = _('File synchronization service is starting,\n' |
350 | + 'please wait...') |
351 | + FILE_SYNC_DISCONNECTED = _('File synchronization service is ready,\nplease' |
352 | + ' connect it to access your personal cloud. ') |
353 | + FILE_SYNC_SYNCING = _('File synchronization service is fully functional,\n' |
354 | + 'performing synchronization now...') |
355 | + FILE_SYNC_IDLE = _('File synchronization service is idle,\n' |
356 | + 'all the files are synchronized.') |
357 | + FILE_SYNC_ERROR = _('File synchronization status can not be retrieved.') |
358 | |
359 | def __init__(self): |
360 | gtk.VBox.__init__(self) |
361 | @@ -498,6 +510,19 @@ |
362 | self.backend.connect_to_signal('AccountInfoError', |
363 | self.on_account_info_error) |
364 | |
365 | + self.backend.connect_to_signal('FileSyncStatusDisabled', |
366 | + self.on_file_sync_status_disabled) |
367 | + self.backend.connect_to_signal('FileSyncStatusStarting', |
368 | + self.on_file_sync_status_starting) |
369 | + self.backend.connect_to_signal('FileSyncStatusDisconnected', |
370 | + self.on_file_sync_status_disconnected) |
371 | + self.backend.connect_to_signal('FileSyncStatusSyncing', |
372 | + self.on_file_sync_status_syncing) |
373 | + self.backend.connect_to_signal('FileSyncStatusIdle', |
374 | + self.on_file_sync_status_idle) |
375 | + self.backend.connect_to_signal('FileSyncStatusError', |
376 | + self.on_file_sync_status_error) |
377 | + |
378 | self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG)) |
379 | self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
380 | self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
381 | @@ -521,6 +546,7 @@ |
382 | self.notebook.insert_page(getattr(self, tab), position=page_num) |
383 | |
384 | self.backend.account_info() |
385 | + self.backend.file_sync_status() |
386 | |
387 | def _update_quota(self, msg, data=None): |
388 | """Update the quota info.""" |
389 | @@ -532,6 +558,11 @@ |
390 | fraction = data.get('percentage', 0.0) / 100 |
391 | self.quota_progressbar.set_fraction(fraction) |
392 | |
393 | + def _update_status(self, msg): |
394 | + """Update the status info.""" |
395 | + self.status_label.set_markup(msg) |
396 | + self.status_label.stop() |
397 | + |
398 | @log_call(logger.debug) |
399 | def on_account_info_ready(self, info): |
400 | """Backend notifies of account info.""" |
401 | @@ -546,6 +577,39 @@ |
402 | """Backend notifies of an error when fetching account info.""" |
403 | self._update_quota(WARNING_MARKUP % self.VALUE_ERROR) |
404 | |
405 | + @log_call(logger.info) |
406 | + def on_file_sync_status_disabled(self, msg): |
407 | + """Backend notifies of file sync status being disabled.""" |
408 | + self._update_status(self.FILE_SYNC_DISABLED) |
409 | + |
410 | + @log_call(logger.info) |
411 | + def on_file_sync_status_starting(self, msg): |
412 | + """Backend notifies of file sync status being starting.""" |
413 | + self._update_status(self.FILE_SYNC_STARTING) |
414 | + |
415 | + @log_call(logger.info) |
416 | + def on_file_sync_status_disconnected(self, msg): |
417 | + """Backend notifies of file sync status being ready.""" |
418 | + self._update_status(self.FILE_SYNC_DISCONNECTED) |
419 | + |
420 | + @log_call(logger.info) |
421 | + def on_file_sync_status_syncing(self, msg): |
422 | + """Backend notifies of file sync status being syncing.""" |
423 | + self._update_status(self.FILE_SYNC_SYNCING) |
424 | + |
425 | + @log_call(logger.info) |
426 | + def on_file_sync_status_idle(self, msg): |
427 | + """Backend notifies of file sync status being idle.""" |
428 | + self._update_status(self.FILE_SYNC_IDLE) |
429 | + |
430 | + @log_call(logger.error) |
431 | + def on_file_sync_status_error(self, error_dict=None): |
432 | + """Backend notifies of an error when fetching file sync status.""" |
433 | + msg = error_dict.get('error_msg', None) |
434 | + if msg is None: |
435 | + msg = error_dict.get('message', self.VALUE_ERROR) |
436 | + self._update_status(WARNING_MARKUP % msg) |
437 | + |
438 | |
439 | gobject.signal_new('credentials-found', OverviewPanel, |
440 | gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, |
441 | |
442 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' |
443 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-02 16:23:03 +0000 |
444 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-10 15:05:12 +0000 |
445 | @@ -302,6 +302,13 @@ |
446 | children = self.ui.get_children() |
447 | self.assertIsInstance(children[-1], gui.ManagementPanel) |
448 | |
449 | + def test_on_show_management_panel_is_idempotent(self): |
450 | + """Only one ManagementPanel is shown.""" |
451 | + self.ui.on_show_management_panel() |
452 | + self.ui.on_show_management_panel() |
453 | + |
454 | + self.assertEqual(1, len(self.ui.get_children())) |
455 | + |
456 | |
457 | class UbuntuOneBinTestCase(BaseTestCase): |
458 | """The test suite for a Ubuntu One panel.""" |
459 | @@ -842,7 +849,7 @@ |
460 | class ManagementPanelAccountTestCase(ManagementPanelTestCase): |
461 | """The test suite for the management panel (account tab).""" |
462 | |
463 | - def test_backend_signals(self): |
464 | + def test_backend_account_signals(self): |
465 | """The proper signals are connected to the backend.""" |
466 | self.assertEqual(self.ui.backend._signals['AccountInfoReady'], |
467 | [self.ui.on_account_info_ready]) |
468 | @@ -898,3 +905,68 @@ |
469 | for widget in (self.ui.quota_label,): |
470 | self.assert_warning_correct(widget, self.ui.VALUE_ERROR) |
471 | self.assertFalse(widget.active) |
472 | + |
473 | + def test_backend_file_sync_signals(self): |
474 | + """The proper signals are connected to the backend.""" |
475 | + matches = ( |
476 | + ('FileSyncStatusDisabled', [self.ui.on_file_sync_status_disabled]), |
477 | + ('FileSyncStatusStarting', [self.ui.on_file_sync_status_starting]), |
478 | + ('FileSyncStatusDisconnected', |
479 | + [self.ui.on_file_sync_status_disconnected]), |
480 | + ('FileSyncStatusSyncing', [self.ui.on_file_sync_status_syncing]), |
481 | + ('FileSyncStatusIdle', [self.ui.on_file_sync_status_idle]), |
482 | + ('FileSyncStatusError', [self.ui.on_file_sync_status_error]), |
483 | + ) |
484 | + for sig, handlers in matches: |
485 | + self.assertEqual(self.ui.backend._signals[sig], handlers) |
486 | + |
487 | + def test_file_sync_status_is_requested(self): |
488 | + """The file sync status is requested to the backend.""" |
489 | + self.assert_backend_called('file_sync_status', ()) |
490 | + |
491 | + def test_on_file_sync_status_disabled(self): |
492 | + """The file sync is disabled.""" |
493 | + self.ui.on_file_sync_status_disabled('msg') |
494 | + |
495 | + self.assertFalse(self.ui.status_label.active) |
496 | + self.assertEqual(self.ui.status_label.get_text(), |
497 | + self.ui.FILE_SYNC_DISABLED) |
498 | + |
499 | + def test_on_file_sync_status_starting(self): |
500 | + """The file sync status is starting.""" |
501 | + self.ui.on_file_sync_status_starting('msg') |
502 | + |
503 | + self.assertFalse(self.ui.status_label.active) |
504 | + self.assertEqual(self.ui.status_label.get_text(), |
505 | + self.ui.FILE_SYNC_STARTING) |
506 | + |
507 | + def test_on_file_sync_status_disconnected(self): |
508 | + """The file sync status is disconnected.""" |
509 | + self.ui.on_file_sync_status_disconnected('msg') |
510 | + |
511 | + self.assertFalse(self.ui.status_label.active) |
512 | + self.assertEqual(self.ui.status_label.get_text(), |
513 | + self.ui.FILE_SYNC_DISCONNECTED) |
514 | + |
515 | + def test_on_file_sync_status_syncing(self): |
516 | + """The file sync status is syncing.""" |
517 | + self.ui.on_file_sync_status_syncing('msg') |
518 | + |
519 | + self.assertFalse(self.ui.status_label.active) |
520 | + self.assertEqual(self.ui.status_label.get_text(), |
521 | + self.ui.FILE_SYNC_SYNCING) |
522 | + |
523 | + def test_on_file_sync_status_idle(self): |
524 | + """The file sync status is idle.""" |
525 | + self.ui.on_file_sync_status_idle('msg') |
526 | + |
527 | + self.assertFalse(self.ui.status_label.active) |
528 | + self.assertEqual(self.ui.status_label.get_text(), |
529 | + self.ui.FILE_SYNC_IDLE) |
530 | + |
531 | + def test_on_file_sync_status_error(self): |
532 | + """The file sync status couldn't be retrieved.""" |
533 | + self.ui.on_file_sync_status_error({'error_msg': 'error msg'}) |
534 | + |
535 | + self.assert_warning_correct(self.ui.status_label, 'error msg') |
536 | + self.assertFalse(self.ui.status_label.active) |
537 | |
538 | === modified file 'ubuntuone/controlpanel/integrationtests/__init__.py' |
539 | --- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-02 16:23:03 +0000 |
540 | +++ ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-10 15:05:12 +0000 |
541 | @@ -43,8 +43,13 @@ |
542 | def setUp(self): |
543 | super(DBusClientTestCase, self).setUp() |
544 | self.mock = None |
545 | + self._called = False |
546 | dbus_service.init_mainloop() |
547 | |
548 | + def _set_called(self, *args, **kwargs): |
549 | + """Keep track of function calls, useful for monkeypatching.""" |
550 | + self._called = (args, kwargs) |
551 | + |
552 | def register_mockserver(self, bus_name, object_path, object_class, |
553 | **kwargs): |
554 | """The mock service is registered on the DBus.""" |
555 | |
556 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py' |
557 | --- ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-06 15:03:58 +0000 |
558 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_client_sd.py 2010-12-10 15:05:12 +0000 |
559 | @@ -204,13 +204,13 @@ |
560 | |
561 | |
562 | class FoldersMockDBusSyncDaemon(sd_dbus_iface.Folders): |
563 | - """A mock object that mimicks syncdaemon regarding the folders iface.""" |
564 | + """A mock object that mimicks syncdaemon regarding the Folders iface.""" |
565 | |
566 | # __init__ method from a non direct base class 'Object' is called |
567 | # __init__ method from base class 'Folders' is not called |
568 | # pylint: disable=W0231, W0233 |
569 | |
570 | - def __init__(self, object_path, conn, vm=None): |
571 | + def __init__(self, object_path, conn): |
572 | self.udfs = {} |
573 | self.udf_id = 1 |
574 | dbus.service.Object.__init__(self, |
575 | @@ -327,6 +327,25 @@ |
576 | self.assertEqual(result, [sd_dbus_iface._get_udf_dict(expected)]) |
577 | |
578 | @inlineCallbacks |
579 | + def test_get_folders_error(self): |
580 | + """Handle error when retrieving current syncdaemon status.""" |
581 | + path = '~/bar/baz' |
582 | + yield dbus_client.create_volume(path) |
583 | + |
584 | + def fail(value): |
585 | + """Fake an error.""" |
586 | + raise TestDBusException(value) |
587 | + |
588 | + self.patch(sd_dbus_iface, '_get_udf_dict', fail) |
589 | + |
590 | + try: |
591 | + yield dbus_client.get_volumes() |
592 | + except dbus.DBusException: |
593 | + pass # test passes! |
594 | + else: |
595 | + self.fail('dbus_client.get_folders should be errbacking') |
596 | + |
597 | + @inlineCallbacks |
598 | def test_create_volume(self): |
599 | """Create a new volume.""" |
600 | path = '~/bar/baz' |
601 | @@ -342,3 +361,106 @@ |
602 | yield dbus_client.create_volume(path='') |
603 | except dbus_client.VolumesError, e: |
604 | self.assertEqual(e[0], {'path': ''}) |
605 | + |
606 | + |
607 | +class StatusMockDBusSyncDaemon(dbus.service.Object): |
608 | + """A mock object that mimicks syncdaemon regarding the Status iface.""" |
609 | + |
610 | + state_dict = { |
611 | + 'name': 'TEST', |
612 | + 'description': 'Some test state, nothing else.', |
613 | + 'is_error': '', |
614 | + 'is_connected': 'True', |
615 | + 'is_online': '', |
616 | + 'queues': 'GORGEOUS', |
617 | + 'connection': '', |
618 | + } |
619 | + |
620 | + def _get_current_state(self): |
621 | + """Get the current status of the system.""" |
622 | + return self.state_dict |
623 | + |
624 | + @dbus.service.method(sd_dbus_iface.DBUS_IFACE_STATUS_NAME, |
625 | + in_signature='', out_signature='a{ss}') |
626 | + def current_status(self): |
627 | + """Return the current faked status of the system.""" |
628 | + return self._get_current_state() |
629 | + |
630 | + # pylint: disable=C0103 |
631 | + # Invalid name "StatusChanged" |
632 | + |
633 | + @dbus.service.signal(sd_dbus_iface.DBUS_IFACE_STATUS_NAME) |
634 | + def StatusChanged(self, status): |
635 | + """Fire a signal to notify that the status of the system changed.""" |
636 | + |
637 | + def emit_status_changed(self, state=None): |
638 | + """Emit StatusChanged.""" |
639 | + self.StatusChanged(self._get_current_state()) |
640 | + |
641 | + |
642 | +class StatusTestCase(DBusClientTestCase): |
643 | + """Test for the volumes dbus client methods.""" |
644 | + |
645 | + def setUp(self): |
646 | + super(StatusTestCase, self).setUp() |
647 | + self.register_mockserver(sd_dbus_iface.DBUS_IFACE_NAME, |
648 | + "/status", StatusMockDBusSyncDaemon) |
649 | + |
650 | + @inlineCallbacks |
651 | + def test_get_current_status(self): |
652 | + """Retrieve current syncdaemon status.""" |
653 | + status = yield dbus_client.get_current_status() |
654 | + |
655 | + self.assertEqual(StatusMockDBusSyncDaemon.state_dict, status) |
656 | + |
657 | + @inlineCallbacks |
658 | + def test_get_current_status_error(self): |
659 | + """Handle error when retrieving current syncdaemon status.""" |
660 | + |
661 | + def fail(value): |
662 | + """Fake an error.""" |
663 | + raise TestDBusException(value) |
664 | + |
665 | + self.patch(StatusMockDBusSyncDaemon, '_get_current_state', fail) |
666 | + |
667 | + try: |
668 | + yield dbus_client.get_current_status() |
669 | + except dbus.DBusException: |
670 | + pass # test passes! |
671 | + else: |
672 | + self.fail('dbus_client.get_current_status should be errbacking') |
673 | + |
674 | + def test_status_changed(self): |
675 | + """A proper callback can be connected to StatusChanged signal.""" |
676 | + _, sig = dbus_client.set_status_changed_handler(self._set_called) |
677 | + |
678 | + self.assertEqual(sig._handler, self._set_called) |
679 | + self.assertEqual(sig._member, 'StatusChanged') |
680 | + self.assertEqual(sig._path, '/status') |
681 | + self.assertEqual(sig._interface, sd_dbus_iface.DBUS_IFACE_STATUS_NAME) |
682 | + |
683 | + |
684 | +class FileSyncTestCase(DBusClientTestCase): |
685 | + """Test for the files sync enabled dbus client methods.""" |
686 | + |
687 | + @inlineCallbacks |
688 | + def test_files_sync_enabled(self): |
689 | + """Retrieve whether file sync is enabled.""" |
690 | + expected = object() |
691 | + self.patch(dbus_client.SyncDaemonTool, 'is_files_sync_enabled', |
692 | + lambda _: expected) |
693 | + |
694 | + enabled = yield dbus_client.files_sync_enabled() |
695 | + |
696 | + self.assertEqual(expected, enabled) |
697 | + |
698 | + @inlineCallbacks |
699 | + def test_set_files_sync_enabled(self): |
700 | + """Set if file sync is enabled or not.""" |
701 | + self.patch(dbus_client.SyncDaemonTool, 'enable_files_sync', |
702 | + self._set_called) |
703 | + expected = object() |
704 | + # set the opposite value |
705 | + yield dbus_client.set_files_sync_enabled(expected) |
706 | + |
707 | + self.assertEqual(self._called, ((expected,), {})) |
708 | |
709 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py' |
710 | --- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-02 16:35:31 +0000 |
711 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-10 15:05:12 +0000 |
712 | @@ -115,7 +115,13 @@ |
713 | |
714 | class MockBackend(object): |
715 | """A mock backend.""" |
716 | + |
717 | exception = None |
718 | + sample_status = { |
719 | + dbus_service.MSG_KEY: 'test me please', |
720 | + dbus_service.STATUS_KEY: dbus_service.FILE_SYNC_IDLE, |
721 | + } |
722 | + status_changed_handler = None |
723 | |
724 | def _process(self, result): |
725 | """Process the request with the given result.""" |
726 | @@ -142,7 +148,7 @@ |
727 | |
728 | def file_sync_status(self): |
729 | """Return the status of the file sync service.""" |
730 | - return self._process((True, "Synchronizing")) |
731 | + return self._process(self.sample_status) |
732 | |
733 | def volumes_info(self): |
734 | """Get the user volumes info.""" |
735 | @@ -170,6 +176,11 @@ |
736 | """Initialize each test run.""" |
737 | super(DBusServiceTestCase, self).setUp() |
738 | dbus_service.init_mainloop() |
739 | + self._called = False |
740 | + |
741 | + def _set_called(self, *args, **kwargs): |
742 | + """Keep track of function calls, useful for monkeypatching.""" |
743 | + self._called = (args, kwargs) |
744 | |
745 | def test_register_service(self): |
746 | """The DBus service is successfully registered.""" |
747 | @@ -193,7 +204,7 @@ |
748 | def test_error_handler_with_failure(self): |
749 | """Ensure to build a string-string dict to pass to error signals.""" |
750 | error = dbus_service.Failure(TypeError('oh no!')) |
751 | - expected = dbus_service.utils.failure_to_error_dict(error) |
752 | + expected = dbus_service.failure_to_error_dict(error) |
753 | |
754 | result = dbus_service.error_handler(error) |
755 | |
756 | @@ -202,7 +213,7 @@ |
757 | def test_error_handler_with_exception(self): |
758 | """Ensure to build a string-string dict to pass to error signals.""" |
759 | error = TypeError('oh no, no again!') |
760 | - expected = dbus_service.utils.exception_to_error_dict(error) |
761 | + expected = dbus_service.exception_to_error_dict(error) |
762 | |
763 | result = dbus_service.error_handler(error) |
764 | |
765 | @@ -231,8 +242,8 @@ |
766 | def test_error_handler_default(self): |
767 | """Ensure to build a string-string dict to pass to error signals.""" |
768 | msg = 'Got unexpected error argument %r' % None |
769 | - expected = {dbus_service.utils.ERROR_TYPE: 'UnknownError', |
770 | - dbus_service.utils.ERROR_MESSAGE: msg} |
771 | + expected = {dbus_service.ERROR_TYPE: 'UnknownError', |
772 | + dbus_service.ERROR_MESSAGE: msg} |
773 | |
774 | result = dbus_service.error_handler(None) |
775 | |
776 | @@ -372,19 +383,6 @@ |
777 | self.backend.remove_device, sample_token) |
778 | return self.assert_correct_method_call(*args) |
779 | |
780 | - def test_file_sync_status(self): |
781 | - """The file sync status is reported.""" |
782 | - |
783 | - def got_signal(enabled, status): |
784 | - """The correct status was received.""" |
785 | - self.assertEqual(enabled, True) |
786 | - self.assertEqual(status, "Synchronizing") |
787 | - self.deferred.callback("success") |
788 | - |
789 | - args = ("FileSyncStatusReady", "FileSyncStatusError", got_signal, |
790 | - self.backend.file_sync_status) |
791 | - return self.assert_correct_method_call(*args) |
792 | - |
793 | def test_volumes_info(self): |
794 | """The volumes info is reported.""" |
795 | |
796 | @@ -453,9 +451,79 @@ |
797 | |
798 | def got_error_signal(error_dict): |
799 | """The error signal was received.""" |
800 | - self.assertEqual(error_dict[dbus_service.utils.ERROR_TYPE], |
801 | + self.assertEqual(error_dict[dbus_service.ERROR_TYPE], |
802 | 'AssertionError') |
803 | self.deferred.callback("success") |
804 | |
805 | return super(OperationsErrorTestCase, self).assert_correct_method_call( |
806 | error_sig, success_sig, got_error_signal, method, *args) |
807 | + |
808 | + |
809 | +class FileSyncTestCase(OperationsTestCase): |
810 | + """Test for the DBus service when requesting file sync status.""" |
811 | + |
812 | + def assert_correct_status_signal(self, status, sync_signal, |
813 | + error_signal="FileSyncStatusError", |
814 | + expected_msg=None): |
815 | + """The file sync status is reported properly.""" |
816 | + MockBackend.sample_status[dbus_service.STATUS_KEY] = status |
817 | + if expected_msg is None: |
818 | + expected_msg = MockBackend.sample_status[dbus_service.MSG_KEY] |
819 | + |
820 | + def got_signal(msg): |
821 | + """The correct status was received.""" |
822 | + self.assertEqual(msg, expected_msg) |
823 | + self.deferred.callback("success") |
824 | + |
825 | + args = (sync_signal, error_signal, got_signal, |
826 | + self.backend.file_sync_status) |
827 | + return self.assert_correct_method_call(*args) |
828 | + |
829 | + def test_file_sync_status_unknown(self): |
830 | + """The file sync status is reported properly.""" |
831 | + msg = MockBackend.sample_status |
832 | + args = ('invalid-file-sync-status', "FileSyncStatusError", |
833 | + "FileSyncStatusIdle", msg) |
834 | + return self.assert_correct_status_signal(*args) |
835 | + |
836 | + def test_file_sync_status_error(self): |
837 | + """The file sync status is reported properly.""" |
838 | + msg = MockBackend.sample_status[dbus_service.MSG_KEY] |
839 | + err_dict = {dbus_service.ERROR_TYPE: 'FileSyncStatusError', |
840 | + dbus_service.ERROR_MESSAGE: msg} |
841 | + args = (dbus_service.FILE_SYNC_ERROR, "FileSyncStatusError", |
842 | + "FileSyncStatusIdle", err_dict) |
843 | + return self.assert_correct_status_signal(*args) |
844 | + |
845 | + def test_file_sync_status_disabled(self): |
846 | + """The file sync status is reported properly.""" |
847 | + args = (dbus_service.FILE_SYNC_DISABLED, "FileSyncStatusDisabled") |
848 | + return self.assert_correct_status_signal(*args) |
849 | + |
850 | + def test_file_sync_status_starting(self): |
851 | + """The file sync status is reported properly.""" |
852 | + args = (dbus_service.FILE_SYNC_STARTING, "FileSyncStatusStarting") |
853 | + return self.assert_correct_status_signal(*args) |
854 | + |
855 | + def test_file_sync_status_disconnected(self): |
856 | + """The file sync status is reported properly.""" |
857 | + args = (dbus_service.FILE_SYNC_DISCONNECTED, |
858 | + "FileSyncStatusDisconnected") |
859 | + return self.assert_correct_status_signal(*args) |
860 | + |
861 | + def test_file_sync_status_syncing(self): |
862 | + """The file sync status is reported properly.""" |
863 | + args = (dbus_service.FILE_SYNC_SYNCING, "FileSyncStatusSyncing") |
864 | + return self.assert_correct_status_signal(*args) |
865 | + |
866 | + def test_file_sync_status_idle(self): |
867 | + """The file sync status is reported properly.""" |
868 | + args = (dbus_service.FILE_SYNC_IDLE, "FileSyncStatusIdle") |
869 | + return self.assert_correct_status_signal(*args) |
870 | + |
871 | + def test_status_changed_handler(self): |
872 | + """The status changed handler is properly set.""" |
873 | + be = MockBackend() |
874 | + cpbe = dbus_service.ControlPanelBackend(backend=be) |
875 | + |
876 | + self.assertEqual(be.status_changed_handler, cpbe.process_status) |
877 | |
878 | === modified file 'ubuntuone/controlpanel/logger.py' |
879 | --- ubuntuone/controlpanel/logger.py 2010-12-02 16:35:31 +0000 |
880 | +++ ubuntuone/controlpanel/logger.py 2010-12-10 15:05:12 +0000 |
881 | @@ -22,6 +22,7 @@ |
882 | import os |
883 | import sys |
884 | |
885 | +from functools import wraps |
886 | from logging.handlers import RotatingFileHandler |
887 | |
888 | # pylint: disable=F0401,E0611 |
889 | @@ -41,7 +42,7 @@ |
890 | MAIN_HANDLER.setLevel(LOG_LEVEL) |
891 | |
892 | |
893 | -def setup_logging(log_domain): |
894 | +def setup_logging(log_domain, prefix=None): |
895 | """Create a logger for 'log_domain'. |
896 | |
897 | Final domain will be 'ubuntuone.controlpanel.<log_domain>. |
898 | @@ -52,6 +53,28 @@ |
899 | logger.addHandler(MAIN_HANDLER) |
900 | if os.environ.get('DEBUG'): |
901 | debug_handler = logging.StreamHandler(sys.stderr) |
902 | + if prefix is not None: |
903 | + fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n" |
904 | + formatter = logging.Formatter(fmt) |
905 | + debug_handler.setFormatter(formatter) |
906 | logger.addHandler(debug_handler) |
907 | |
908 | return logger |
909 | + |
910 | + |
911 | +def log_call(log_func): |
912 | + """Decorator to add log info using 'log_func'.""" |
913 | + |
914 | + def middle(f): |
915 | + """Add logging when calling 'f'.""" |
916 | + |
917 | + @wraps(f) |
918 | + def inner(*args, **kwargs): |
919 | + """Call f(*args, **kwargs).""" |
920 | + log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs) |
921 | + res = f(*args, **kwargs) |
922 | + return res |
923 | + |
924 | + return inner |
925 | + |
926 | + return middle |
927 | |
928 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' |
929 | --- ubuntuone/controlpanel/tests/test_backend.py 2010-12-02 16:23:03 +0000 |
930 | +++ ubuntuone/controlpanel/tests/test_backend.py 2010-12-10 15:05:12 +0000 |
931 | @@ -23,10 +23,21 @@ |
932 | |
933 | from twisted.internet import defer |
934 | from twisted.internet.defer import inlineCallbacks |
935 | +from ubuntuone.devtools.handlers import MementoHandler |
936 | |
937 | from ubuntuone.controlpanel import backend |
938 | -from ubuntuone.controlpanel.backend import (ACCOUNT_API, QUOTA_API, |
939 | - DEVICES_API, DEVICE_REMOVE_API) |
940 | +from ubuntuone.controlpanel.backend import (ACCOUNT_API, |
941 | + DEVICES_API, DEVICE_REMOVE_API, QUOTA_API, |
942 | + FILE_SYNC_DISABLED, |
943 | + FILE_SYNC_DISCONNECTED, |
944 | + FILE_SYNC_ERROR, |
945 | + FILE_SYNC_IDLE, |
946 | + FILE_SYNC_STARTING, |
947 | + FILE_SYNC_SYNCING, |
948 | + FILE_SYNC_UNKNOWN, |
949 | + MSG_KEY, STATUS_KEY, |
950 | +) |
951 | + |
952 | from ubuntuone.controlpanel.tests import TestCase |
953 | from ubuntuone.controlpanel.webclient import WebClientError |
954 | |
955 | @@ -152,6 +163,13 @@ |
956 | creds = SAMPLE_CREDENTIALS |
957 | throttling = False |
958 | limits = {"download": -1, "upload": -1} |
959 | + file_sync = True |
960 | + status = { |
961 | + 'name': 'TEST', 'queues': 'GORGEOUS', 'connection': '', |
962 | + 'description': 'Some test state, nothing else.', |
963 | + 'is_error': '', 'is_connected': 'True', 'is_online': '', |
964 | + } |
965 | + status_changed_handler = None |
966 | |
967 | def get_credentials(self): |
968 | """Return the mock credentials.""" |
969 | @@ -178,10 +196,26 @@ |
970 | """Disable bw throttling.""" |
971 | self.throttling = False |
972 | |
973 | + def files_sync_enabled(self): |
974 | + """Get if file sync service is enabled.""" |
975 | + return self.file_sync |
976 | + |
977 | + def set_files_sync_enabled(self, enabled): |
978 | + """Set the file sync service to be 'enabled'.""" |
979 | + self.file_sync = enabled |
980 | + |
981 | def get_volumes(self): |
982 | """Grab list of folders and shares.""" |
983 | return SAMPLE_VOLUMES |
984 | |
985 | + def get_current_status(self): |
986 | + """Grab syncdaemon status.""" |
987 | + return MockDBusClient.status |
988 | + |
989 | + def set_status_changed_handler(self, handler): |
990 | + """Connect a handler for tracking syncdaemon status changes.""" |
991 | + self.status_changed_handler = handler |
992 | + |
993 | |
994 | class BackendBasicTestCase(TestCase): |
995 | """Simple tests for the backend.""" |
996 | @@ -189,11 +223,15 @@ |
997 | timeout = 3 |
998 | |
999 | def setUp(self): |
1000 | + super(BackendBasicTestCase, self).setUp() |
1001 | self.patch(backend, "WebClient", MockWebClient) |
1002 | self.patch(backend, "dbus_client", MockDBusClient()) |
1003 | self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"] |
1004 | self.be = backend.ControlBackend() |
1005 | |
1006 | + self.memento = MementoHandler() |
1007 | + backend.logger.addHandler(self.memento) |
1008 | + |
1009 | def test_backend_creation(self): |
1010 | """The backend instance is successfully created.""" |
1011 | self.assertEqual(self.be.wc.__class__, MockWebClient) |
1012 | @@ -314,3 +352,158 @@ |
1013 | """The volumes_info method exercises its callback.""" |
1014 | result = yield self.be.volumes_info() |
1015 | self.assertEqual(result, SAMPLE_VOLUMES) |
1016 | + |
1017 | + |
1018 | +class BackendSyncStatusTestCase(BackendBasicTestCase): |
1019 | + """Syncdaemon state for the backend.""" |
1020 | + |
1021 | + def _build_msg(self): |
1022 | + """Build expected message regarding file sync status.""" |
1023 | + return '%s (%s)' % (MockDBusClient.status['description'], |
1024 | + MockDBusClient.status['name']) |
1025 | + |
1026 | + @inlineCallbacks |
1027 | + def assert_correct_status(self, status, msg=None): |
1028 | + """Check that the resulting status is correct.""" |
1029 | + expected = {MSG_KEY: self._build_msg() if msg is None else msg, |
1030 | + STATUS_KEY: status} |
1031 | + result = yield self.be.file_sync_status() |
1032 | + self.assertEqual(expected, result) |
1033 | + |
1034 | + @inlineCallbacks |
1035 | + def test_disabled(self): |
1036 | + """The syncdaemon status is processed and emitted.""" |
1037 | + self.patch(MockDBusClient, 'file_sync', False) |
1038 | + yield self.assert_correct_status(FILE_SYNC_DISABLED, msg='') |
1039 | + |
1040 | + @inlineCallbacks |
1041 | + def test_error(self): |
1042 | + """The syncdaemon status is processed and emitted.""" |
1043 | + MockDBusClient.status = { |
1044 | + 'is_error': 'True', # nothing else matters |
1045 | + 'is_online': '', 'is_connected': '', |
1046 | + 'name': 'AUTH_FAILED', 'connection': '', 'queues': '', |
1047 | + 'description': 'auth failed', |
1048 | + } |
1049 | + yield self.assert_correct_status(FILE_SYNC_ERROR) |
1050 | + |
1051 | + @inlineCallbacks |
1052 | + def test_starting_when_init_not_user(self): |
1053 | + """The syncdaemon status is processed and emitted.""" |
1054 | + MockDBusClient.status = { |
1055 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1056 | + 'connection': 'Not User With Network', 'queues': '', |
1057 | + 'name': 'INIT', 'description': 'something new', |
1058 | + } |
1059 | + yield self.assert_correct_status(FILE_SYNC_STARTING) |
1060 | + |
1061 | + @inlineCallbacks |
1062 | + def test_starting_when_init_with_user(self): |
1063 | + """The syncdaemon status is processed and emitted.""" |
1064 | + MockDBusClient.status = { |
1065 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1066 | + 'connection': 'With User With Network', 'queues': '', |
1067 | + 'name': 'INIT', 'description': 'something new', |
1068 | + } |
1069 | + yield self.assert_correct_status(FILE_SYNC_STARTING) |
1070 | + |
1071 | + @inlineCallbacks |
1072 | + def test_starting_when_local_rescan_not_user(self): |
1073 | + """The syncdaemon status is processed and emitted.""" |
1074 | + MockDBusClient.status = { |
1075 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1076 | + 'connection': 'Not User With Network', 'queues': '', |
1077 | + 'name': 'LOCAL_RESCAN', 'description': 'something new', |
1078 | + } |
1079 | + yield self.assert_correct_status(FILE_SYNC_STARTING) |
1080 | + |
1081 | + @inlineCallbacks |
1082 | + def test_starting_when_local_rescan_with_user(self): |
1083 | + """The syncdaemon status is processed and emitted.""" |
1084 | + MockDBusClient.status = { |
1085 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1086 | + 'connection': 'With User With Network', 'queues': '', |
1087 | + 'name': 'LOCAL_RESCAN', 'description': 'something new', |
1088 | + } |
1089 | + yield self.assert_correct_status(FILE_SYNC_STARTING) |
1090 | + |
1091 | + @inlineCallbacks |
1092 | + def test_starting_when_ready_with_user(self): |
1093 | + """The syncdaemon status is processed and emitted.""" |
1094 | + MockDBusClient.status = { |
1095 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1096 | + 'connection': 'With User With Network', 'queues': '', |
1097 | + 'name': 'READY', 'description': 'something nicer', |
1098 | + } |
1099 | + yield self.assert_correct_status(FILE_SYNC_STARTING) |
1100 | + |
1101 | + @inlineCallbacks |
1102 | + def test_disconnected(self): |
1103 | + """The syncdaemon status is processed and emitted.""" |
1104 | + MockDBusClient.status = { |
1105 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1106 | + 'queues': '', 'description': 'something new', |
1107 | + 'connection': 'Not User With Network', # user didn't connect |
1108 | + 'name': 'READY', # must be READY, otherwise is STARTING |
1109 | + } |
1110 | + yield self.assert_correct_status(FILE_SYNC_DISCONNECTED) |
1111 | + |
1112 | + @inlineCallbacks |
1113 | + def test_syncing_if_online(self): |
1114 | + """The syncdaemon status is processed and emitted.""" |
1115 | + MockDBusClient.status = { |
1116 | + 'is_error': '', 'is_online': 'True', 'is_connected': 'True', |
1117 | + 'name': 'QUEUE_MANAGER', 'connection': '', |
1118 | + 'queues': 'WORKING_ON_CONTENT', # anything but IDLE |
1119 | + 'description': 'something nicer', |
1120 | + } |
1121 | + yield self.assert_correct_status(FILE_SYNC_SYNCING) |
1122 | + |
1123 | + @inlineCallbacks |
1124 | + def test_syncing_even_if_not_online(self): |
1125 | + """The syncdaemon status is processed and emitted.""" |
1126 | + MockDBusClient.status = { |
1127 | + 'is_error': '', 'is_online': '', 'is_connected': 'True', |
1128 | + 'name': 'CHECK_VERSION', 'connection': '', |
1129 | + 'queues': 'WORKING_ON_CONTENT', |
1130 | + 'description': 'something nicer', |
1131 | + } |
1132 | + yield self.assert_correct_status(FILE_SYNC_SYNCING) |
1133 | + |
1134 | + @inlineCallbacks |
1135 | + def test_idle(self): |
1136 | + """The syncdaemon status is processed and emitted.""" |
1137 | + MockDBusClient.status = { |
1138 | + 'is_error': '', 'is_online': 'True', 'is_connected': 'True', |
1139 | + 'name': 'QUEUE_MANAGER', 'connection': '', |
1140 | + 'queues': 'IDLE', |
1141 | + 'description': 'something nice', |
1142 | + } |
1143 | + yield self.assert_correct_status(FILE_SYNC_IDLE) |
1144 | + |
1145 | + @inlineCallbacks |
1146 | + def test_unknown(self): |
1147 | + """The syncdaemon status is processed and emitted.""" |
1148 | + MockDBusClient.status = { |
1149 | + 'is_error': '', 'is_online': '', 'is_connected': '', |
1150 | + 'name': '', 'connection': '', 'queues': '', |
1151 | + 'description': '', |
1152 | + } |
1153 | + yield self.assert_correct_status(FILE_SYNC_UNKNOWN) |
1154 | + |
1155 | + has_warning = self.memento.check_warning('file_sync_status: unknown', |
1156 | + repr(MockDBusClient.status)) |
1157 | + self.assertTrue(has_warning) |
1158 | + |
1159 | + def test_status_changed(self): |
1160 | + """The file_sync_status is the status changed handler.""" |
1161 | + self.be.status_changed_handler = self._set_called |
1162 | + status = {'name': 'foo', 'description': 'bar', 'is_error': '', |
1163 | + 'is_connected': '', 'is_online': '', 'queues': ''} |
1164 | + # pylint: disable=E1101 |
1165 | + backend.dbus_client.status_changed_handler(status) |
1166 | + |
1167 | + # pylint: disable=W0212 |
1168 | + # Access to a protected member _process_file_sync_status |
1169 | + expected_status = self.be._process_file_sync_status(status) |
1170 | + self.assertEqual(self._called, ((expected_status,), {})) |
1171 | |
1172 | === modified file 'ubuntuone/controlpanel/utils.py' |
1173 | --- ubuntuone/controlpanel/utils.py 2010-12-02 16:35:31 +0000 |
1174 | +++ ubuntuone/controlpanel/utils.py 2010-12-10 15:05:12 +0000 |
1175 | @@ -20,8 +20,6 @@ |
1176 | |
1177 | import os |
1178 | |
1179 | -from functools import wraps |
1180 | - |
1181 | import dbus |
1182 | |
1183 | from ubuntuone.controlpanel.logger import setup_logging |
1184 | @@ -68,24 +66,6 @@ |
1185 | return os.path.join(get_project_dir(), filename) |
1186 | |
1187 | |
1188 | -def log_call(log_func): |
1189 | - """Decorator to add log info using 'log_func'.""" |
1190 | - |
1191 | - def middle(f): |
1192 | - """Add logging when calling 'f'.""" |
1193 | - |
1194 | - @wraps(f) |
1195 | - def inner(*args, **kwargs): |
1196 | - """Call f(*args, **kwargs).""" |
1197 | - log_func('%s: args %r, kwargs %r.', f.__name__, args, kwargs) |
1198 | - res = f(*args, **kwargs) |
1199 | - return res |
1200 | - |
1201 | - return inner |
1202 | - |
1203 | - return middle |
1204 | - |
1205 | - |
1206 | def is_dbus_no_reply(failure): |
1207 | """Decide if 'failure' is a DBus NoReply Error.""" |
1208 | exc = failure.value |
I have checked the code and I see nothing wrong with it. Keep in mind that I am not very familiar with the codebase yet, so the second reviewer should be extra careful ;-)