Merge lp:~mikemc/ubuntuone-client/fix-dummy-sync-menu into lp:ubuntuone-client
- fix-dummy-sync-menu
- Merge into trunk
| Status: | Superseded | ||||
|---|---|---|---|---|---|
| Proposed branch: | lp:~mikemc/ubuntuone-client/fix-dummy-sync-menu | ||||
| Merge into: | lp:ubuntuone-client | ||||
| Diff against target: |
894 lines (+741/-8) (has conflicts) 9 files modified
tests/platform/sync_menu/test_common.py (+49/-0) tests/platform/sync_menu/test_linux.py (+350/-0) tests/status/test_aggregator.py (+14/-0) tests/syncdaemon/test_status_listener.py (+2/-2) ubuntuone/platform/sync_menu/common.py (+41/-0) ubuntuone/platform/sync_menu/linux.py (+253/-0) ubuntuone/status/aggregator.py (+28/-3) ubuntuone/syncdaemon/main.py (+2/-1) ubuntuone/syncdaemon/status_listener.py (+2/-2) Text conflict in tests/platform/sync_menu/test_linux.py Text conflict in ubuntuone/platform/sync_menu/common.py Text conflict in ubuntuone/platform/sync_menu/linux.py |
||||
| To merge this branch: | bzr merge lp:~mikemc/ubuntuone-client/fix-dummy-sync-menu | ||||
| Related bugs: |
|
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Ubuntu One hackers | 2012-09-24 | Pending | |
|
Review via email:
|
|||
This proposal has been superseded by a proposal from 2012-09-27.
Commit Message
- Fix dummy sync menu implementation for windows and darwin. (LP: #1055840)
Description of the Change
- Fix dummy sync menu implementation for windows and darwin. (LP: #1055840)
Added a test to check that the dummy class has the same API, fixed missing start_timer function.
Removed code in dummy that won't be called.
Tests pass on darwin and linux (precise).
(On darwin, I only ran the specific tests that changed, since trunk tests have never worked.)
With this fix, an app built from trunk runs correctly on darwin again.
- 1322. By Mike McCracken on 2012-09-24
-
merge with default-
fs-monitor- fix - 1323. By Mike McCracken on 2012-09-25
-
merge with sync-menu-timer branch
- 1324. By Mike McCracken on 2012-09-25
-
change dummy to match new linux version
- 1325. By Mike McCracken on 2012-09-26
-
merge with trunk
- 1326. By Mike McCracken on 2012-09-26
-
change to reflect new API
- 1327. By Mike McCracken on 2012-09-27
-
merge with trunk
Unmerged revisions
Preview Diff
| 1 | === added file 'tests/platform/sync_menu/test_common.py' |
| 2 | --- tests/platform/sync_menu/test_common.py 1970-01-01 00:00:00 +0000 |
| 3 | +++ tests/platform/sync_menu/test_common.py 2012-09-26 18:35:23 +0000 |
| 4 | @@ -0,0 +1,49 @@ |
| 5 | +# -*- coding: utf-8 *-* |
| 6 | +# |
| 7 | +# Copyright 2012 Canonical Ltd. |
| 8 | +# |
| 9 | +# This program is free software: you can redistribute it and/or modify it |
| 10 | +# under the terms of the GNU General Public License version 3, as published |
| 11 | +# by the Free Software Foundation. |
| 12 | +# |
| 13 | +# This program is distributed in the hope that it will be useful, but |
| 14 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
| 15 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
| 16 | +# PURPOSE. See the GNU General Public License for more details. |
| 17 | +# |
| 18 | +# You should have received a copy of the GNU General Public License along |
| 19 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | +# |
| 21 | +# In addition, as a special exception, the copyright holders give |
| 22 | +# permission to link the code of portions of this program with the |
| 23 | +# OpenSSL library under certain conditions as described in each |
| 24 | +# individual source file, and distribute linked combinations |
| 25 | +# including the two. |
| 26 | +# You must obey the GNU General Public License in all respects |
| 27 | +# for all of the code used other than OpenSSL. If you modify |
| 28 | +# file(s) with this exception, you may extend this exception to your |
| 29 | +# version of the file(s), but you are not obligated to do so. If you |
| 30 | +# do not wish to do so, delete this exception statement from your |
| 31 | +# version. If you delete this exception statement from all source |
| 32 | +# files in the program, then also delete it here. |
| 33 | +"""Test the common dummy Sync Menu implementation for win32/darwin.""" |
| 34 | + |
| 35 | +from collections import Callable |
| 36 | + |
| 37 | +from twisted.trial.unittest import TestCase |
| 38 | + |
| 39 | +from ubuntuone.platform.sync_menu import common |
| 40 | + |
| 41 | + |
| 42 | +class SyncMenuDummyTestCase(TestCase): |
| 43 | + """Test the SyncMenu.""" |
| 44 | + |
| 45 | + def test_dummy_support(self): |
| 46 | + """Can we create a Dummy with the same #args as the real obj.""" |
| 47 | + dummy = common.UbuntuOneSyncMenu(1, 2) |
| 48 | + self.assertIsInstance(dummy, common.UbuntuOneSyncMenu) |
| 49 | + |
| 50 | + def test_dummy_has_update_transfers(self): |
| 51 | + """Check that the dummy has the proper methods required by the API.""" |
| 52 | + dummy = common.UbuntuOneSyncMenu(1, 2) |
| 53 | + self.assertIsInstance(dummy.update_transfers, Callable) |
| 54 | |
| 55 | === modified file 'tests/platform/sync_menu/test_linux.py' |
| 56 | --- tests/platform/sync_menu/test_linux.py 2012-09-24 13:09:52 +0000 |
| 57 | +++ tests/platform/sync_menu/test_linux.py 2012-09-26 18:35:23 +0000 |
| 58 | @@ -1,3 +1,4 @@ |
| 59 | +<<<<<<< TREE |
| 60 | # -*- coding: utf-8 *-* |
| 61 | # |
| 62 | # Copyright 2012 Canonical Ltd. |
| 63 | @@ -338,3 +339,352 @@ |
| 64 | self.assertEqual(item.property_get_int( |
| 65 | linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE), |
| 66 | percentage) |
| 67 | +======= |
| 68 | +# -*- coding: utf-8 *-* |
| 69 | +# |
| 70 | +# Copyright 2012 Canonical Ltd. |
| 71 | +# |
| 72 | +# This program is free software: you can redistribute it and/or modify it |
| 73 | +# under the terms of the GNU General Public License version 3, as published |
| 74 | +# by the Free Software Foundation. |
| 75 | +# |
| 76 | +# This program is distributed in the hope that it will be useful, but |
| 77 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
| 78 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
| 79 | +# PURPOSE. See the GNU General Public License for more details. |
| 80 | +# |
| 81 | +# You should have received a copy of the GNU General Public License along |
| 82 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
| 83 | +# |
| 84 | +# In addition, as a special exception, the copyright holders give |
| 85 | +# permission to link the code of portions of this program with the |
| 86 | +# OpenSSL library under certain conditions as described in each |
| 87 | +# individual source file, and distribute linked combinations |
| 88 | +# including the two. |
| 89 | +# You must obey the GNU General Public License in all respects |
| 90 | +# for all of the code used other than OpenSSL. If you modify |
| 91 | +# file(s) with this exception, you may extend this exception to your |
| 92 | +# version of the file(s), but you are not obligated to do so. If you |
| 93 | +# do not wish to do so, delete this exception statement from your |
| 94 | +# version. If you delete this exception statement from all source |
| 95 | +# files in the program, then also delete it here. |
| 96 | +"""Test the Sync Menu.""" |
| 97 | + |
| 98 | +import time |
| 99 | +from collections import Callable |
| 100 | + |
| 101 | +from twisted.internet import defer |
| 102 | +from twisted.trial.unittest import TestCase |
| 103 | + |
| 104 | +from ubuntuone.platform import sync_menu |
| 105 | +from ubuntuone.platform.sync_menu import linux |
| 106 | + |
| 107 | + |
| 108 | +def fake_call_later(*args): |
| 109 | + """Fake reactor.callLater.""" |
| 110 | + |
| 111 | + |
| 112 | +class FakeStatusFrontend(object): |
| 113 | + """Fake StatusFrontend.""" |
| 114 | + |
| 115 | + def __init__(self): |
| 116 | + self.recent_transfers_data = [] |
| 117 | + self.uploading_data = [] |
| 118 | + |
| 119 | + def recent_transfers(self): |
| 120 | + """Return the fake recent transfers files.""" |
| 121 | + return self.recent_transfers_data |
| 122 | + |
| 123 | + def files_uploading(self): |
| 124 | + """Return the fake files being upload.""" |
| 125 | + return self.uploading_data |
| 126 | + |
| 127 | + |
| 128 | +class FakeTimer(object): |
| 129 | + """Fake Timer.""" |
| 130 | + |
| 131 | + def __init__(self, delay): |
| 132 | + self.delay = delay |
| 133 | + self.callback = None |
| 134 | + |
| 135 | + def addCallback(self, callback): |
| 136 | + """Add callback.""" |
| 137 | + self.callback = callback |
| 138 | + |
| 139 | + |
| 140 | +class FakeSyncdaemonService(object): |
| 141 | + """Fake SyncdaemonService.""" |
| 142 | + |
| 143 | + |
| 144 | +class FakeSyncMenuApp(object): |
| 145 | + """Fake SyncMenu.""" |
| 146 | + |
| 147 | + data = {} |
| 148 | + |
| 149 | + @classmethod |
| 150 | + def new(cls, *args): |
| 151 | + return FakeSyncMenuApp() |
| 152 | + |
| 153 | + @classmethod |
| 154 | + def clean(cls): |
| 155 | + """Clear the values stored in data.""" |
| 156 | + |
| 157 | + def set_menu(self, server): |
| 158 | + """Set the menu for SyncMenu App.""" |
| 159 | + self.data['server'] = server |
| 160 | + |
| 161 | + def connect(self, signal, callback): |
| 162 | + """Fake connect.""" |
| 163 | + self.data['connect'] = (signal, callback) |
| 164 | + |
| 165 | + |
| 166 | +class SyncMenuDummyTestCase(TestCase): |
| 167 | + """Test the SyncMenu.""" |
| 168 | + |
| 169 | + def test_dummy_support(self): |
| 170 | + """Check that the Dummy object can be created properly.""" |
| 171 | + dummy = linux.DummySyncMenu('random', 'args') |
| 172 | + self.assertIsInstance(dummy, linux.DummySyncMenu) |
| 173 | + |
| 174 | + def test_dummy_has_start_timer(self): |
| 175 | + """Check that the dummy has the proper methods required by the API.""" |
| 176 | + dummy = linux.DummySyncMenu('random', 'args') |
| 177 | + self.assertIsInstance(dummy.start_timer, Callable) |
| 178 | + |
| 179 | + |
| 180 | +class SyncMenuTestCase(TestCase): |
| 181 | + """Test the SyncMenu.""" |
| 182 | + |
| 183 | + skip = None if linux.use_syncmenu else "SyncMenu not installed." |
| 184 | + |
| 185 | + @defer.inlineCallbacks |
| 186 | + def setUp(self): |
| 187 | + yield super(SyncMenuTestCase, self).setUp() |
| 188 | + self.patch(linux.SyncMenu, "App", FakeSyncMenuApp) |
| 189 | + FakeSyncMenuApp.clean() |
| 190 | + self.syncdaemon_service = FakeSyncdaemonService() |
| 191 | + self.status_frontend = FakeStatusFrontend() |
| 192 | + self._paused = False |
| 193 | + self.patch(sync_menu.UbuntuOneSyncMenu, "change_sync_status", |
| 194 | + self._change_sync_status) |
| 195 | + self.sync_menu = sync_menu.UbuntuOneSyncMenu(self.status_frontend, |
| 196 | + self.syncdaemon_service) |
| 197 | + |
| 198 | + def _change_sync_status(self, *args): |
| 199 | + """Fake change_sync_status.""" |
| 200 | + if self._paused: |
| 201 | + self._paused = False |
| 202 | + else: |
| 203 | + self._paused = True |
| 204 | + |
| 205 | + def test_init(self): |
| 206 | + """Check that the menu is properly initialized.""" |
| 207 | + self.assertIsInstance(FakeSyncMenuApp.data['server'], |
| 208 | + linux.Dbusmenu.Server) |
| 209 | + self.assertEqual(self.sync_menu.open_u1.get_parent(), |
| 210 | + self.sync_menu.root_menu) |
| 211 | + self.assertEqual(self.sync_menu.go_to_web.get_parent(), |
| 212 | + self.sync_menu.root_menu) |
| 213 | + self.assertEqual(self.sync_menu.more_storage.get_parent(), |
| 214 | + self.sync_menu.root_menu) |
| 215 | + self.assertEqual(self.sync_menu.get_help.get_parent(), |
| 216 | + self.sync_menu.root_menu) |
| 217 | + self.assertEqual(self.sync_menu.transfers.get_parent(), |
| 218 | + self.sync_menu.root_menu) |
| 219 | + |
| 220 | + self.assertEqual(self.sync_menu.open_u1.property_get( |
| 221 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.OPEN_U1) |
| 222 | + self.assertEqual(self.sync_menu.go_to_web.property_get( |
| 223 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.GO_TO_WEB) |
| 224 | + self.assertEqual(self.sync_menu.transfers.property_get( |
| 225 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.TRANSFERS) |
| 226 | + self.assertEqual(self.sync_menu.more_storage.property_get( |
| 227 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.MORE_STORAGE) |
| 228 | + self.assertEqual(self.sync_menu.get_help.property_get( |
| 229 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.GET_HELP) |
| 230 | + |
| 231 | + self.assertEqual(self.sync_menu.app.data['connect'], |
| 232 | + ("notify::paused", self.sync_menu.change_sync_status)) |
| 233 | + self.sync_menu.app.data['connect'][1]() |
| 234 | + self.assertTrue(self._paused) |
| 235 | + self.sync_menu.app.data['connect'][1]() |
| 236 | + self.assertFalse(self._paused) |
| 237 | + |
| 238 | + def test_open_u1(self): |
| 239 | + """Check that the proper action is executed.""" |
| 240 | + data = [] |
| 241 | + |
| 242 | + self.patch(linux.glib, "spawn_command_line_async", data.append) |
| 243 | + self.sync_menu.open_control_panel() |
| 244 | + self.assertEqual(data, ['ubuntuone-installer']) |
| 245 | + |
| 246 | + def test_go_to_web(self): |
| 247 | + """Check that the proper action is executed.""" |
| 248 | + data = [] |
| 249 | + |
| 250 | + self.patch(linux.webbrowser, "open", data.append) |
| 251 | + self.sync_menu.open_go_to_web() |
| 252 | + self.assertEqual(data, [linux.DASHBOARD]) |
| 253 | + |
| 254 | + def test_get_help(self): |
| 255 | + """Check that the proper action is executed.""" |
| 256 | + data = [] |
| 257 | + |
| 258 | + self.patch(linux.webbrowser, "open", data.append) |
| 259 | + self.sync_menu.open_web_help() |
| 260 | + self.assertEqual(data, [linux.HELP_LINK]) |
| 261 | + |
| 262 | + def test_more_storage(self): |
| 263 | + """Check that the proper action is executed.""" |
| 264 | + data = [] |
| 265 | + |
| 266 | + self.patch(linux.webbrowser, "open", data.append) |
| 267 | + self.sync_menu.open_get_more_storage() |
| 268 | + self.assertEqual(data, [linux.GET_STORAGE_LINK]) |
| 269 | + |
| 270 | + def test_empty_transfers(self): |
| 271 | + """Check that the Transfers menu is empty.""" |
| 272 | + self.assertEqual(self.sync_menu.transfers.get_children(), []) |
| 273 | + |
| 274 | + def test_only_recent(self): |
| 275 | + """Check that only recent transfers items are loaded.""" |
| 276 | + data = ['file1', 'file2', 'file3'] |
| 277 | + self.status_frontend.recent_transfers_data = data |
| 278 | + self.sync_menu.transfers.update_progress() |
| 279 | + children = self.sync_menu.transfers.get_children() |
| 280 | + self.assertEqual(len(children), 3) |
| 281 | + data.reverse() |
| 282 | + for itemM, itemD in zip(children, data): |
| 283 | + self.assertEqual(itemM.property_get( |
| 284 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD) |
| 285 | + |
| 286 | + def test_only_progress(self): |
| 287 | + """Check that only progress items are loaded.""" |
| 288 | + data = [ |
| 289 | + ('file1', 3000, 400), |
| 290 | + ('file2', 2000, 100), |
| 291 | + ('file3', 5000, 4600)] |
| 292 | + uploading_data = {} |
| 293 | + for filename, size, written in data: |
| 294 | + uploading_data[filename] = (size, written) |
| 295 | + self.status_frontend.uploading_data = data |
| 296 | + self.sync_menu.transfers.update_progress() |
| 297 | + children = self.sync_menu.transfers.get_children() |
| 298 | + self.assertEqual(len(children), 3) |
| 299 | + data.reverse() |
| 300 | + for item in children: |
| 301 | + text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL) |
| 302 | + self.assertIn(text, uploading_data) |
| 303 | + size, written = uploading_data[text] |
| 304 | + percentage = written * 100 / size |
| 305 | + self.assertEqual(item.property_get_int( |
| 306 | + linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE), |
| 307 | + percentage) |
| 308 | + |
| 309 | + def test_full_transfers(self): |
| 310 | + """Check that the transfers menu contains the maximum transfers.""" |
| 311 | + # The api of recent transfers always returns a maximum of 5 items |
| 312 | + data_recent = ['file1', 'file2', 'file3', 'file4', 'file5'] |
| 313 | + self.status_frontend.recent_transfers_data = \ |
| 314 | + data_recent |
| 315 | + self.sync_menu.transfers.update_progress() |
| 316 | + children = self.sync_menu.transfers.get_children() |
| 317 | + self.assertEqual(len(children), 5) |
| 318 | + data_recent.reverse() |
| 319 | + for itemM, itemD in zip(children, data_recent): |
| 320 | + self.assertEqual(itemM.property_get( |
| 321 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD) |
| 322 | + |
| 323 | + data_current = [ |
| 324 | + ('file0', 1200, 600), |
| 325 | + ('file1', 3000, 400), |
| 326 | + ('file2', 2000, 100), |
| 327 | + ('file3', 2500, 150), |
| 328 | + ('file4', 1000, 600), |
| 329 | + ('file5', 5000, 4600)] |
| 330 | + uploading_data = {} |
| 331 | + for filename, size, written in data_current: |
| 332 | + uploading_data[filename] = (size, written) |
| 333 | + self.status_frontend.uploading_data = data_current |
| 334 | + self.sync_menu.transfers.update_progress() |
| 335 | + children = self.sync_menu.transfers.get_children() |
| 336 | + # The menu should only show 5 current transfers. |
| 337 | + self.assertEqual(len(children), 10) |
| 338 | + data_current.reverse() |
| 339 | + for item in children[5:]: |
| 340 | + text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL) |
| 341 | + self.assertIn(text, uploading_data) |
| 342 | + size, written = uploading_data[text] |
| 343 | + percentage = written * 100 / size |
| 344 | + self.assertEqual(item.property_get_int( |
| 345 | + linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE), |
| 346 | + percentage) |
| 347 | + |
| 348 | + def test_update_transfers(self): |
| 349 | + """Check that everything is ok when updating the transfers value.""" |
| 350 | + data_current = [ |
| 351 | + ('file0', 1200, 600), |
| 352 | + ('file1', 3000, 400), |
| 353 | + ('file4', 1000, 600), |
| 354 | + ('file5', 5000, 4600)] |
| 355 | + uploading_data = {} |
| 356 | + for filename, size, written in data_current: |
| 357 | + uploading_data[filename] = (size, written) |
| 358 | + self.status_frontend.uploading_data = data_current |
| 359 | + self.sync_menu.transfers.update_progress() |
| 360 | + children = self.sync_menu.transfers.get_children() |
| 361 | + # The menu should only show 5 current transfers. |
| 362 | + self.assertEqual(len(children), 4) |
| 363 | + data_current.reverse() |
| 364 | + for item in children: |
| 365 | + text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL) |
| 366 | + self.assertIn(text, uploading_data) |
| 367 | + size, written = uploading_data[text] |
| 368 | + percentage = written * 100 / size |
| 369 | + self.assertEqual(item.property_get_int( |
| 370 | + linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE), |
| 371 | + percentage) |
| 372 | + |
| 373 | + data_recent = ['file5'] |
| 374 | + self.status_frontend.recent_transfers_data = data_recent |
| 375 | + self.sync_menu.transfers.update_progress() |
| 376 | + children = self.sync_menu.transfers.get_children() |
| 377 | + self.assertEqual(len(children), 5) |
| 378 | + data_recent.reverse() |
| 379 | + for itemM, itemD in zip(children, data_recent): |
| 380 | + self.assertEqual(itemM.property_get( |
| 381 | + linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD) |
| 382 | + |
| 383 | + data_current = [ |
| 384 | + ('file0', 1200, 700), |
| 385 | + ('file1', 3000, 600), |
| 386 | + ('file4', 1000, 800)] |
| 387 | + uploading_data = {} |
| 388 | + for filename, size, written in data_current: |
| 389 | + uploading_data[filename] = (size, written) |
| 390 | + self.status_frontend.uploading_data = data_current |
| 391 | + self.sync_menu.transfers.update_progress() |
| 392 | + children = self.sync_menu.transfers.get_children() |
| 393 | + # The menu should only show 5 current transfers. |
| 394 | + self.assertEqual(len(children), 4) |
| 395 | + data_current.reverse() |
| 396 | + for item in children[5:]: |
| 397 | + text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL) |
| 398 | + self.assertIn(text, uploading_data) |
| 399 | + size, written = uploading_data[text] |
| 400 | + percentage = written * 100 / size |
| 401 | + self.assertEqual(item.property_get_int( |
| 402 | + linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE), |
| 403 | + percentage) |
| 404 | + |
| 405 | + def test_update_transfers_delay(self): |
| 406 | + """Check that the timer is being handle properly.""" |
| 407 | + self.patch(linux.status.aggregator, "Timer", FakeTimer) |
| 408 | + self.sync_menu.next_update = time.time() + 3 |
| 409 | + self.sync_menu.update_transfers() |
| 410 | + self.assertLess(self.sync_menu.timer.delay, 3) |
| 411 | + self.sync_menu.timer = None |
| 412 | + self.sync_menu.next_update = time.time() / 2 |
| 413 | + self.sync_menu.update_transfers() |
| 414 | + self.assertEqual(self.sync_menu.timer.delay, 0) |
| 415 | +>>>>>>> MERGE-SOURCE |
| 416 | |
| 417 | === modified file 'tests/status/test_aggregator.py' |
| 418 | --- tests/status/test_aggregator.py 2012-08-20 13:18:43 +0000 |
| 419 | +++ tests/status/test_aggregator.py 2012-09-26 18:35:23 +0000 |
| 420 | @@ -1314,6 +1314,20 @@ |
| 421 | self.assertEqual({}, self.aggregator.to_do) |
| 422 | self.assertIdentical(None, self.aggregator.queue_done_timer) |
| 423 | |
| 424 | + def test_register_listener(self): |
| 425 | + """Check that register_listener handles properly additions.""" |
| 426 | + |
| 427 | + def fake_callback(): |
| 428 | + """Do nothing.""" |
| 429 | + |
| 430 | + self.aggregator.register_listener(fake_callback) |
| 431 | + self.assertEqual(len(self.aggregator.listeners_callbacks), 1) |
| 432 | + |
| 433 | + def test_register_listener_fail(self): |
| 434 | + """Check that register_listener handles properly additions.""" |
| 435 | + self.assertRaises(TypeError, self.aggregator.register_listener, []) |
| 436 | + self.assertEqual(len(self.aggregator.listeners_callbacks), 0) |
| 437 | + |
| 438 | def assertMiscCommandQueued(self, fc): |
| 439 | """Assert that some command was queued.""" |
| 440 | self.assertEqual(len(self.aggregator.to_do), 1) |
| 441 | |
| 442 | === modified file 'tests/syncdaemon/test_status_listener.py' |
| 443 | --- tests/syncdaemon/test_status_listener.py 2012-04-09 20:07:05 +0000 |
| 444 | +++ tests/syncdaemon/test_status_listener.py 2012-09-26 18:35:23 +0000 |
| 445 | @@ -84,7 +84,7 @@ |
| 446 | callback(args) |
| 447 | |
| 448 | listener = Listener() |
| 449 | - setattr(listener, 'handle_'+event, listener._handle_event) |
| 450 | + setattr(listener, 'handle_' + event, listener._handle_event) |
| 451 | event_q.subscribe(listener) |
| 452 | return listener |
| 453 | |
| 454 | @@ -92,7 +92,7 @@ |
| 455 | class FakeStatusFrontend(object): |
| 456 | """A fake status frontend.""" |
| 457 | |
| 458 | - def __init__(self): |
| 459 | + def __init__(self, *args, **kwargs): |
| 460 | """Initialize this instance.""" |
| 461 | self.call_log = [] |
| 462 | |
| 463 | |
| 464 | === modified file 'ubuntuone/platform/sync_menu/common.py' |
| 465 | --- ubuntuone/platform/sync_menu/common.py 2012-09-18 16:29:22 +0000 |
| 466 | +++ ubuntuone/platform/sync_menu/common.py 2012-09-26 18:35:23 +0000 |
| 467 | @@ -1,3 +1,4 @@ |
| 468 | +<<<<<<< TREE |
| 469 | # -*- coding: utf-8 *-* |
| 470 | # |
| 471 | # Copyright 2012 Canonical Ltd. |
| 472 | @@ -41,3 +42,43 @@ |
| 473 | |
| 474 | def start_timer(self): |
| 475 | """Empty start timer, this is not needed on windows.""" |
| 476 | +======= |
| 477 | +# -*- coding: utf-8 *-* |
| 478 | +# |
| 479 | +# Copyright 2012 Canonical Ltd. |
| 480 | +# |
| 481 | +# This program is free software: you can redistribute it and/or modify it |
| 482 | +# under the terms of the GNU General Public License version 3, as published |
| 483 | +# by the Free Software Foundation. |
| 484 | +# |
| 485 | +# This program is distributed in the hope that it will be useful, but |
| 486 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
| 487 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
| 488 | +# PURPOSE. See the GNU General Public License for more details. |
| 489 | +# |
| 490 | +# You should have received a copy of the GNU General Public License along |
| 491 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
| 492 | +# |
| 493 | +# In addition, as a special exception, the copyright holders give |
| 494 | +# permission to link the code of portions of this program with the |
| 495 | +# OpenSSL library under certain conditions as described in each |
| 496 | +# individual source file, and distribute linked combinations |
| 497 | +# including the two. |
| 498 | +# You must obey the GNU General Public License in all respects |
| 499 | +# for all of the code used other than OpenSSL. If you modify |
| 500 | +# file(s) with this exception, you may extend this exception to your |
| 501 | +# version of the file(s), but you are not obligated to do so. If you |
| 502 | +# do not wish to do so, delete this exception statement from your |
| 503 | +# version. If you delete this exception statement from all source |
| 504 | +# files in the program, then also delete it here. |
| 505 | +"""Dummy implementation of sync_menu lib for win32 and darwin.""" |
| 506 | + |
| 507 | + |
| 508 | +class UbuntuOneSyncMenu(object): |
| 509 | + """Integrate U1 with the Ubuntu Sync Menu.""" |
| 510 | + def __init__(self, status, syncdaemon_service): |
| 511 | + """Match #args of linux syncmenu and do nothing.""" |
| 512 | + |
| 513 | + def update_transfers(self): |
| 514 | + """Do nothing.""" |
| 515 | +>>>>>>> MERGE-SOURCE |
| 516 | |
| 517 | === modified file 'ubuntuone/platform/sync_menu/linux.py' |
| 518 | --- ubuntuone/platform/sync_menu/linux.py 2012-09-24 12:47:05 +0000 |
| 519 | +++ ubuntuone/platform/sync_menu/linux.py 2012-09-26 18:35:23 +0000 |
| 520 | @@ -1,3 +1,4 @@ |
| 521 | +<<<<<<< TREE |
| 522 | # -*- coding: utf-8 *-* |
| 523 | # |
| 524 | # Copyright 2012 Canonical Ltd. |
| 525 | @@ -239,3 +240,255 @@ |
| 526 | |
| 527 | |
| 528 | UbuntuOneSyncMenu = UbuntuOneSyncMenuLinux if use_syncmenu else DummySyncMenu |
| 529 | +======= |
| 530 | +# -*- coding: utf-8 *-* |
| 531 | +# |
| 532 | +# Copyright 2012 Canonical Ltd. |
| 533 | +# |
| 534 | +# This program is free software: you can redistribute it and/or modify it |
| 535 | +# under the terms of the GNU General Public License version 3, as published |
| 536 | +# by the Free Software Foundation. |
| 537 | +# |
| 538 | +# This program is distributed in the hope that it will be useful, but |
| 539 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
| 540 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
| 541 | +# PURPOSE. See the GNU General Public License for more details. |
| 542 | +# |
| 543 | +# You should have received a copy of the GNU General Public License along |
| 544 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
| 545 | +# |
| 546 | +# In addition, as a special exception, the copyright holders give |
| 547 | +# permission to link the code of portions of this program with the |
| 548 | +# OpenSSL library under certain conditions as described in each |
| 549 | +# individual source file, and distribute linked combinations |
| 550 | +# including the two. |
| 551 | +# You must obey the GNU General Public License in all respects |
| 552 | +# for all of the code used other than OpenSSL. If you modify |
| 553 | +# file(s) with this exception, you may extend this exception to your |
| 554 | +# version of the file(s), but you are not obligated to do so. If you |
| 555 | +# do not wish to do so, delete this exception statement from your |
| 556 | +# version. If you delete this exception statement from all source |
| 557 | +# files in the program, then also delete it here. |
| 558 | +"""Use SyncMenu lib to integrate U1 with the Systray Sync Icon.""" |
| 559 | + |
| 560 | +import gettext |
| 561 | +import logging |
| 562 | +import time |
| 563 | +import sys |
| 564 | +import webbrowser |
| 565 | + |
| 566 | +glib = None |
| 567 | +try: |
| 568 | + if 'gobject' in sys.modules and sys.modules['gobject'] is not None: |
| 569 | + import glib as GLib |
| 570 | + glib = GLib |
| 571 | + else: |
| 572 | + from gi.repository import GLib |
| 573 | + glib = GLib |
| 574 | +except ImportError: |
| 575 | + pass |
| 576 | +try: |
| 577 | + from gi.repository import ( |
| 578 | + Dbusmenu, |
| 579 | + SyncMenu, |
| 580 | + ) |
| 581 | + use_syncmenu = True |
| 582 | +except: |
| 583 | + use_syncmenu = False |
| 584 | + |
| 585 | +from ubuntuone.clientdefs import GETTEXT_PACKAGE |
| 586 | +from ubuntuone import status |
| 587 | + |
| 588 | + |
| 589 | +logger = logging.getLogger("ubuntuone.platform.SyncMenu") |
| 590 | + |
| 591 | +Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string) |
| 592 | + |
| 593 | +OPEN_U1 = Q_("Open Ubuntu One") |
| 594 | +GO_TO_WEB = Q_("Go to the Ubuntu One Website") |
| 595 | +TRANSFERS = Q_("Current and Recent Transfers") |
| 596 | +MORE_STORAGE = Q_("Get More Space") |
| 597 | +GET_HELP = Q_("Get Help on the Web") |
| 598 | + |
| 599 | +DELAY_BETWEEN_UPDATES = 3 |
| 600 | +UBUNTUONE_LINK = u'https://one.ubuntu.com/' |
| 601 | +DASHBOARD = UBUNTUONE_LINK + u'dashboard/' |
| 602 | +HELP_LINK = UBUNTUONE_LINK + u'support/' |
| 603 | +GET_STORAGE_LINK = UBUNTUONE_LINK + u'services/#storage_panel' |
| 604 | + |
| 605 | + |
| 606 | +class UbuntuOneSyncMenuLinux(object): |
| 607 | + """Integrate U1 with the Ubuntu Sync Menu.""" |
| 608 | + |
| 609 | + def __init__(self, status, syncdaemon_service): |
| 610 | + """Initialize menu.""" |
| 611 | + self._syncdaemon_service = syncdaemon_service |
| 612 | + self._paused = False |
| 613 | + self.timer = None |
| 614 | + self.next_update = time.time() |
| 615 | + self.root_menu = Dbusmenu.Menuitem() |
| 616 | + |
| 617 | + self.open_u1 = Dbusmenu.Menuitem() |
| 618 | + self.open_u1.property_set(Dbusmenu.MENUITEM_PROP_LABEL, OPEN_U1) |
| 619 | + |
| 620 | + self.go_to_web = Dbusmenu.Menuitem() |
| 621 | + self.go_to_web.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 622 | + GO_TO_WEB) |
| 623 | + |
| 624 | + self.transfers = TransfersMenu(status) |
| 625 | + self.transfers.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 626 | + TRANSFERS) |
| 627 | + |
| 628 | + self.more_storage = Dbusmenu.Menuitem() |
| 629 | + self.more_storage.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 630 | + MORE_STORAGE) |
| 631 | + |
| 632 | + self.get_help = Dbusmenu.Menuitem() |
| 633 | + self.get_help.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 634 | + GET_HELP) |
| 635 | + |
| 636 | + # Connect signals |
| 637 | + self.open_u1.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, |
| 638 | + self.open_control_panel) |
| 639 | + self.go_to_web.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, |
| 640 | + self.open_go_to_web) |
| 641 | + self.get_help.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, |
| 642 | + self.open_web_help) |
| 643 | + self.more_storage.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, |
| 644 | + self.open_get_more_storage) |
| 645 | + |
| 646 | + # Add items |
| 647 | + self.root_menu.child_append(self.open_u1) |
| 648 | + self.root_menu.child_append(self.go_to_web) |
| 649 | + self.root_menu.child_append(self.transfers) |
| 650 | + self.root_menu.child_append(self.more_storage) |
| 651 | + self.root_menu.child_append(self.get_help) |
| 652 | + |
| 653 | + self.server = Dbusmenu.Server() |
| 654 | + self.server.set_root(self.root_menu) |
| 655 | + self.app = SyncMenu.App.new("ubuntuone-installer.desktop") |
| 656 | + self.app.set_menu(self.server) |
| 657 | + self.app.connect("notify::paused", self.change_sync_status) |
| 658 | + |
| 659 | + def change_sync_status(self, *args): |
| 660 | + """Triggered when the sync status is changed fromm the menu.""" |
| 661 | + if self._paused: |
| 662 | + self._syncdaemon_service.connect() |
| 663 | + self._paused = False |
| 664 | + else: |
| 665 | + self._syncdaemon_service.disconnect() |
| 666 | + self._paused = True |
| 667 | + |
| 668 | + def open_control_panel(self, *args): |
| 669 | + """Open the Ubuntu One Control Panel.""" |
| 670 | + glib.spawn_command_line_async('ubuntuone-installer') |
| 671 | + |
| 672 | + def open_go_to_web(self, *args): |
| 673 | + """Open the Ubunto One Help Page""" |
| 674 | + webbrowser.open(DASHBOARD) |
| 675 | + |
| 676 | + def open_web_help(self, *args): |
| 677 | + """Open the Ubunto One Help Page""" |
| 678 | + webbrowser.open(HELP_LINK) |
| 679 | + |
| 680 | + def open_get_more_storage(self, *args): |
| 681 | + """Open the Ubunto One Help Page""" |
| 682 | + webbrowser.open(GET_STORAGE_LINK) |
| 683 | + |
| 684 | + def _timeout(self, result): |
| 685 | + """The aggregating timer has expired, so update the UI.""" |
| 686 | + self.next_update = int(time.time()) + DELAY_BETWEEN_UPDATES |
| 687 | + self.transfers.update_progress() |
| 688 | + self.timer = None |
| 689 | + |
| 690 | + def update_transfers(self): |
| 691 | + """Set up a timer if there isn't one ticking and update the ui.""" |
| 692 | + if not self.timer: |
| 693 | + logger.debug("Updating Transfers.") |
| 694 | + delay = int(max(0, min(DELAY_BETWEEN_UPDATES, |
| 695 | + self.next_update - time.time()))) |
| 696 | + self.timer = status.aggregator.Timer(delay) |
| 697 | + self.timer.addCallback(self._timeout) |
| 698 | + |
| 699 | + |
| 700 | +class TransfersMenu(Dbusmenu.Menuitem): |
| 701 | + """Menu that handles the recent and current transfers.""" |
| 702 | + |
| 703 | + def __init__(self, status_frontend): |
| 704 | + super(TransfersMenu, self).__init__() |
| 705 | + self.status_frontend = status_frontend |
| 706 | + self.uploading = {} |
| 707 | + self.previous_transfers = [] |
| 708 | + self._transfers_items = {} |
| 709 | + self._uploading_items = {} |
| 710 | + |
| 711 | + def update_progress(self): |
| 712 | + """Update the list of recent transfers and current transfers.""" |
| 713 | + current_transfers = self.status_frontend.recent_transfers() |
| 714 | + uploading_data = {} |
| 715 | + for filename, size, written in \ |
| 716 | + self.status_frontend.files_uploading(): |
| 717 | + uploading_data[filename] = (size, written) |
| 718 | + |
| 719 | + temp_transfers = {} |
| 720 | + if current_transfers != self.previous_transfers: |
| 721 | + logger.debug("Update recent transfers with: %r", current_transfers) |
| 722 | + for item_transfer in self._transfers_items: |
| 723 | + self.child_delete(self._transfers_items[item_transfer]) |
| 724 | + for item in current_transfers: |
| 725 | + recent_file = Dbusmenu.Menuitem() |
| 726 | + recent_file.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 727 | + item) |
| 728 | + self.child_add_position(recent_file, 0) |
| 729 | + temp_transfers[item] = recent_file |
| 730 | + self._transfers_items = temp_transfers |
| 731 | + |
| 732 | + items_added = 0 |
| 733 | + remove = [] |
| 734 | + for item in self._uploading_items: |
| 735 | + if item in uploading_data: |
| 736 | + size, written = uploading_data[item] |
| 737 | + percentage = written * 100 / size |
| 738 | + upload_item = self._uploading_items[item] |
| 739 | + upload_item.property_set_int( |
| 740 | + SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE, |
| 741 | + percentage) |
| 742 | + logger.debug("Current transfer %s progress update: %r", |
| 743 | + item, percentage) |
| 744 | + items_added += 1 |
| 745 | + else: |
| 746 | + self.child_delete(self._uploading_items[item]) |
| 747 | + remove.append(item) |
| 748 | + for item in remove: |
| 749 | + self._uploading_items.pop(item) |
| 750 | + if items_added < 5: |
| 751 | + for item in uploading_data: |
| 752 | + if item not in self._uploading_items and items_added < 5: |
| 753 | + size, written = uploading_data[item] |
| 754 | + percentage = written * 100 / size |
| 755 | + uploading_file = Dbusmenu.Menuitem() |
| 756 | + uploading_file.property_set(Dbusmenu.MENUITEM_PROP_LABEL, |
| 757 | + item) |
| 758 | + uploading_file.property_set(Dbusmenu.MENUITEM_PROP_TYPE, |
| 759 | + SyncMenu.PROGRESS_MENUITEM_TYPE) |
| 760 | + uploading_file.property_set_int( |
| 761 | + SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE, |
| 762 | + percentage) |
| 763 | + logger.debug("Current transfer %s created", item) |
| 764 | + self.child_append(uploading_file) |
| 765 | + self._uploading_items[item] = uploading_file |
| 766 | + items_added += 1 |
| 767 | + |
| 768 | + |
| 769 | +class DummySyncMenu(object): |
| 770 | + """Dummy SyncMenu""" |
| 771 | + |
| 772 | + def __init__(self, *args, **kwargs): |
| 773 | + """Initialize menu.""" |
| 774 | + |
| 775 | + def start_timer(self): |
| 776 | + """Do nothing.""" |
| 777 | + |
| 778 | + |
| 779 | +UbuntuOneSyncMenu = UbuntuOneSyncMenuLinux if use_syncmenu else DummySyncMenu |
| 780 | +>>>>>>> MERGE-SOURCE |
| 781 | |
| 782 | === modified file 'ubuntuone/status/aggregator.py' |
| 783 | --- ubuntuone/status/aggregator.py 2012-08-16 12:00:12 +0000 |
| 784 | +++ ubuntuone/status/aggregator.py 2012-09-26 18:35:23 +0000 |
| 785 | @@ -33,7 +33,7 @@ |
| 786 | import itertools |
| 787 | import operator |
| 788 | import os |
| 789 | -from collections import deque |
| 790 | +from collections import deque, Callable |
| 791 | |
| 792 | import gettext |
| 793 | |
| 794 | @@ -41,7 +41,11 @@ |
| 795 | |
| 796 | from ubuntuone.clientdefs import GETTEXT_PACKAGE |
| 797 | from ubuntuone.status.logger import logger |
| 798 | -from ubuntuone.platform import session, notification |
| 799 | +from ubuntuone.platform import ( |
| 800 | + notification, |
| 801 | + session, |
| 802 | + sync_menu |
| 803 | +) |
| 804 | from ubuntuone.platform.messaging import Messaging |
| 805 | from ubuntuone.platform.launcher import UbuntuOneLauncher, DummyLauncher |
| 806 | |
| 807 | @@ -625,6 +629,7 @@ |
| 808 | self.progress = {} |
| 809 | self.to_do = {} |
| 810 | self.recent_transfers = deque(maxlen=5) |
| 811 | + self.listeners_callbacks = [] |
| 812 | |
| 813 | def get_notification(self): |
| 814 | """Create a new toggleable notification object.""" |
| 815 | @@ -655,6 +660,13 @@ |
| 816 | self.to_do = {} |
| 817 | # pylint: enable=W0201 |
| 818 | |
| 819 | + def register_listener(self, listener): |
| 820 | + """Register a callable object to be notified.""" |
| 821 | + if isinstance(listener, Callable): |
| 822 | + self.listeners_callbacks.append(listener) |
| 823 | + else: |
| 824 | + raise TypeError("Callable object expected.") |
| 825 | + |
| 826 | def get_discovery_message(self): |
| 827 | """Get the text for the discovery bubble.""" |
| 828 | lines = [] |
| 829 | @@ -716,6 +728,8 @@ |
| 830 | progress = float( |
| 831 | sum(self.progress.values())) / sum(self.to_do.values()) |
| 832 | self.progress_bar.set_progress(progress) |
| 833 | + for listener in self.listeners_callbacks: |
| 834 | + listener() |
| 835 | |
| 836 | def download_started(self, command): |
| 837 | """A download just started.""" |
| 838 | @@ -801,13 +815,24 @@ |
| 839 | class StatusFrontend(object): |
| 840 | """Frontend for the status aggregator, used by the StatusListener.""" |
| 841 | |
| 842 | - def __init__(self, clock=reactor): |
| 843 | + def __init__(self, clock=reactor, service=None): |
| 844 | """Initialize this instance.""" |
| 845 | self.aggregator = StatusAggregator(clock=clock) |
| 846 | self.notification = self.aggregator.get_notification() |
| 847 | self.messaging = Messaging() |
| 848 | self.quota_timer = None |
| 849 | |
| 850 | + self.syncdaemon_service = service |
| 851 | + self.sync_menu = None |
| 852 | + self.start_sync_menu() |
| 853 | + |
| 854 | + def start_sync_menu(self): |
| 855 | + """Create the sync menu and run the loop.""" |
| 856 | + if self.syncdaemon_service is not None: |
| 857 | + self.sync_menu = sync_menu.UbuntuOneSyncMenu(self, |
| 858 | + self.syncdaemon_service) |
| 859 | + self.aggregator.register_listener(self.sync_menu.update_transfers) |
| 860 | + |
| 861 | def recent_transfers(self): |
| 862 | """Return a tuple with the recent transfers paths.""" |
| 863 | return list(self.aggregator.recent_transfers) |
| 864 | |
| 865 | === modified file 'ubuntuone/syncdaemon/main.py' |
| 866 | --- ubuntuone/syncdaemon/main.py 2012-09-19 17:23:02 +0000 |
| 867 | +++ ubuntuone/syncdaemon/main.py 2012-09-26 18:35:23 +0000 |
| 868 | @@ -176,7 +176,8 @@ |
| 869 | |
| 870 | def start_status_listener(self): |
| 871 | """Start the status listener if it is configured to start.""" |
| 872 | - self.status_listener = status_listener.get_listener(self.fs, self.vm) |
| 873 | + self.status_listener = status_listener.get_listener(self.fs, self.vm, |
| 874 | + self.external) |
| 875 | # subscribe to EQ, to be unsubscribed in shutdown |
| 876 | if self.status_listener: |
| 877 | self.event_q.subscribe(self.status_listener) |
| 878 | |
| 879 | === modified file 'ubuntuone/syncdaemon/status_listener.py' |
| 880 | --- ubuntuone/syncdaemon/status_listener.py 2012-08-10 12:49:46 +0000 |
| 881 | +++ ubuntuone/syncdaemon/status_listener.py 2012-09-26 18:35:23 +0000 |
| 882 | @@ -48,10 +48,10 @@ |
| 883 | return True |
| 884 | |
| 885 | |
| 886 | -def get_listener(fsm, vm): |
| 887 | +def get_listener(fsm, vm, syncdaemon_service=None): |
| 888 | """Return an instance of the status listener, or None if turned off.""" |
| 889 | if should_start_listener(): |
| 890 | - status_frontend = StatusFrontend() |
| 891 | + status_frontend = StatusFrontend(service=syncdaemon_service) |
| 892 | return StatusListener(fsm, vm, status_frontend) |
| 893 | else: |
| 894 | return None |

