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
on 2010-12-09
| Status: | Merged | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Approved by: | Natalia Bidart on 2010-12-10 | ||||||||
| 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 on 2010-12-10 | ||
| Roberto Alsina (community) | 2010-12-09 | Approve on 2010-12-10 | |
|
Review via email:
|
|||
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.
lp:~nataliabidart/ubuntuone-control-panel/add-file-sync-status
updated
on 2010-12-09
- 37. By Natalia Bidart on 2010-12-09
-
Files sync availability is queried to SyncdaemonTool.
lp:~nataliabidart/ubuntuone-control-panel/add-file-sync-status
updated
on 2010-12-10
- 38. By Natalia Bidart on 2010-12-10
-
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 ;-)