Merge lp:~mikemc/ubuntuone-client/fix-dummy-sync-menu into lp:ubuntuone-client

Proposed by Mike McCracken
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
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+126125@code.launchpad.net

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.

To post a comment you must log in.
1322. By Mike McCracken

merge with default-fs-monitor-fix

1323. By Mike McCracken

merge with sync-menu-timer branch

1324. By Mike McCracken

change dummy to match new linux version

1325. By Mike McCracken

merge with trunk

1326. By Mike McCracken

change to reflect new API

1327. By Mike McCracken

merge with trunk

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'tests/platform/sync_menu/test_common.py'
--- tests/platform/sync_menu/test_common.py 1970-01-01 00:00:00 +0000
+++ tests/platform/sync_menu/test_common.py 2012-09-26 18:35:23 +0000
@@ -0,0 +1,49 @@
1# -*- coding: utf-8 *-*
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Test the common dummy Sync Menu implementation for win32/darwin."""
30
31from collections import Callable
32
33from twisted.trial.unittest import TestCase
34
35from ubuntuone.platform.sync_menu import common
36
37
38class SyncMenuDummyTestCase(TestCase):
39 """Test the SyncMenu."""
40
41 def test_dummy_support(self):
42 """Can we create a Dummy with the same #args as the real obj."""
43 dummy = common.UbuntuOneSyncMenu(1, 2)
44 self.assertIsInstance(dummy, common.UbuntuOneSyncMenu)
45
46 def test_dummy_has_update_transfers(self):
47 """Check that the dummy has the proper methods required by the API."""
48 dummy = common.UbuntuOneSyncMenu(1, 2)
49 self.assertIsInstance(dummy.update_transfers, Callable)
050
=== modified file 'tests/platform/sync_menu/test_linux.py'
--- tests/platform/sync_menu/test_linux.py 2012-09-24 13:09:52 +0000
+++ tests/platform/sync_menu/test_linux.py 2012-09-26 18:35:23 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1# -*- coding: utf-8 *-*2# -*- coding: utf-8 *-*
2#3#
3# Copyright 2012 Canonical Ltd.4# Copyright 2012 Canonical Ltd.
@@ -338,3 +339,352 @@
338 self.assertEqual(item.property_get_int(339 self.assertEqual(item.property_get_int(
339 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),340 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),
340 percentage)341 percentage)
342=======
343# -*- coding: utf-8 *-*
344#
345# Copyright 2012 Canonical Ltd.
346#
347# This program is free software: you can redistribute it and/or modify it
348# under the terms of the GNU General Public License version 3, as published
349# by the Free Software Foundation.
350#
351# This program is distributed in the hope that it will be useful, but
352# WITHOUT ANY WARRANTY; without even the implied warranties of
353# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
354# PURPOSE. See the GNU General Public License for more details.
355#
356# You should have received a copy of the GNU General Public License along
357# with this program. If not, see <http://www.gnu.org/licenses/>.
358#
359# In addition, as a special exception, the copyright holders give
360# permission to link the code of portions of this program with the
361# OpenSSL library under certain conditions as described in each
362# individual source file, and distribute linked combinations
363# including the two.
364# You must obey the GNU General Public License in all respects
365# for all of the code used other than OpenSSL. If you modify
366# file(s) with this exception, you may extend this exception to your
367# version of the file(s), but you are not obligated to do so. If you
368# do not wish to do so, delete this exception statement from your
369# version. If you delete this exception statement from all source
370# files in the program, then also delete it here.
371"""Test the Sync Menu."""
372
373import time
374from collections import Callable
375
376from twisted.internet import defer
377from twisted.trial.unittest import TestCase
378
379from ubuntuone.platform import sync_menu
380from ubuntuone.platform.sync_menu import linux
381
382
383def fake_call_later(*args):
384 """Fake reactor.callLater."""
385
386
387class FakeStatusFrontend(object):
388 """Fake StatusFrontend."""
389
390 def __init__(self):
391 self.recent_transfers_data = []
392 self.uploading_data = []
393
394 def recent_transfers(self):
395 """Return the fake recent transfers files."""
396 return self.recent_transfers_data
397
398 def files_uploading(self):
399 """Return the fake files being upload."""
400 return self.uploading_data
401
402
403class FakeTimer(object):
404 """Fake Timer."""
405
406 def __init__(self, delay):
407 self.delay = delay
408 self.callback = None
409
410 def addCallback(self, callback):
411 """Add callback."""
412 self.callback = callback
413
414
415class FakeSyncdaemonService(object):
416 """Fake SyncdaemonService."""
417
418
419class FakeSyncMenuApp(object):
420 """Fake SyncMenu."""
421
422 data = {}
423
424 @classmethod
425 def new(cls, *args):
426 return FakeSyncMenuApp()
427
428 @classmethod
429 def clean(cls):
430 """Clear the values stored in data."""
431
432 def set_menu(self, server):
433 """Set the menu for SyncMenu App."""
434 self.data['server'] = server
435
436 def connect(self, signal, callback):
437 """Fake connect."""
438 self.data['connect'] = (signal, callback)
439
440
441class SyncMenuDummyTestCase(TestCase):
442 """Test the SyncMenu."""
443
444 def test_dummy_support(self):
445 """Check that the Dummy object can be created properly."""
446 dummy = linux.DummySyncMenu('random', 'args')
447 self.assertIsInstance(dummy, linux.DummySyncMenu)
448
449 def test_dummy_has_start_timer(self):
450 """Check that the dummy has the proper methods required by the API."""
451 dummy = linux.DummySyncMenu('random', 'args')
452 self.assertIsInstance(dummy.start_timer, Callable)
453
454
455class SyncMenuTestCase(TestCase):
456 """Test the SyncMenu."""
457
458 skip = None if linux.use_syncmenu else "SyncMenu not installed."
459
460 @defer.inlineCallbacks
461 def setUp(self):
462 yield super(SyncMenuTestCase, self).setUp()
463 self.patch(linux.SyncMenu, "App", FakeSyncMenuApp)
464 FakeSyncMenuApp.clean()
465 self.syncdaemon_service = FakeSyncdaemonService()
466 self.status_frontend = FakeStatusFrontend()
467 self._paused = False
468 self.patch(sync_menu.UbuntuOneSyncMenu, "change_sync_status",
469 self._change_sync_status)
470 self.sync_menu = sync_menu.UbuntuOneSyncMenu(self.status_frontend,
471 self.syncdaemon_service)
472
473 def _change_sync_status(self, *args):
474 """Fake change_sync_status."""
475 if self._paused:
476 self._paused = False
477 else:
478 self._paused = True
479
480 def test_init(self):
481 """Check that the menu is properly initialized."""
482 self.assertIsInstance(FakeSyncMenuApp.data['server'],
483 linux.Dbusmenu.Server)
484 self.assertEqual(self.sync_menu.open_u1.get_parent(),
485 self.sync_menu.root_menu)
486 self.assertEqual(self.sync_menu.go_to_web.get_parent(),
487 self.sync_menu.root_menu)
488 self.assertEqual(self.sync_menu.more_storage.get_parent(),
489 self.sync_menu.root_menu)
490 self.assertEqual(self.sync_menu.get_help.get_parent(),
491 self.sync_menu.root_menu)
492 self.assertEqual(self.sync_menu.transfers.get_parent(),
493 self.sync_menu.root_menu)
494
495 self.assertEqual(self.sync_menu.open_u1.property_get(
496 linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.OPEN_U1)
497 self.assertEqual(self.sync_menu.go_to_web.property_get(
498 linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.GO_TO_WEB)
499 self.assertEqual(self.sync_menu.transfers.property_get(
500 linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.TRANSFERS)
501 self.assertEqual(self.sync_menu.more_storage.property_get(
502 linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.MORE_STORAGE)
503 self.assertEqual(self.sync_menu.get_help.property_get(
504 linux.Dbusmenu.MENUITEM_PROP_LABEL), linux.GET_HELP)
505
506 self.assertEqual(self.sync_menu.app.data['connect'],
507 ("notify::paused", self.sync_menu.change_sync_status))
508 self.sync_menu.app.data['connect'][1]()
509 self.assertTrue(self._paused)
510 self.sync_menu.app.data['connect'][1]()
511 self.assertFalse(self._paused)
512
513 def test_open_u1(self):
514 """Check that the proper action is executed."""
515 data = []
516
517 self.patch(linux.glib, "spawn_command_line_async", data.append)
518 self.sync_menu.open_control_panel()
519 self.assertEqual(data, ['ubuntuone-installer'])
520
521 def test_go_to_web(self):
522 """Check that the proper action is executed."""
523 data = []
524
525 self.patch(linux.webbrowser, "open", data.append)
526 self.sync_menu.open_go_to_web()
527 self.assertEqual(data, [linux.DASHBOARD])
528
529 def test_get_help(self):
530 """Check that the proper action is executed."""
531 data = []
532
533 self.patch(linux.webbrowser, "open", data.append)
534 self.sync_menu.open_web_help()
535 self.assertEqual(data, [linux.HELP_LINK])
536
537 def test_more_storage(self):
538 """Check that the proper action is executed."""
539 data = []
540
541 self.patch(linux.webbrowser, "open", data.append)
542 self.sync_menu.open_get_more_storage()
543 self.assertEqual(data, [linux.GET_STORAGE_LINK])
544
545 def test_empty_transfers(self):
546 """Check that the Transfers menu is empty."""
547 self.assertEqual(self.sync_menu.transfers.get_children(), [])
548
549 def test_only_recent(self):
550 """Check that only recent transfers items are loaded."""
551 data = ['file1', 'file2', 'file3']
552 self.status_frontend.recent_transfers_data = data
553 self.sync_menu.transfers.update_progress()
554 children = self.sync_menu.transfers.get_children()
555 self.assertEqual(len(children), 3)
556 data.reverse()
557 for itemM, itemD in zip(children, data):
558 self.assertEqual(itemM.property_get(
559 linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD)
560
561 def test_only_progress(self):
562 """Check that only progress items are loaded."""
563 data = [
564 ('file1', 3000, 400),
565 ('file2', 2000, 100),
566 ('file3', 5000, 4600)]
567 uploading_data = {}
568 for filename, size, written in data:
569 uploading_data[filename] = (size, written)
570 self.status_frontend.uploading_data = data
571 self.sync_menu.transfers.update_progress()
572 children = self.sync_menu.transfers.get_children()
573 self.assertEqual(len(children), 3)
574 data.reverse()
575 for item in children:
576 text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL)
577 self.assertIn(text, uploading_data)
578 size, written = uploading_data[text]
579 percentage = written * 100 / size
580 self.assertEqual(item.property_get_int(
581 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),
582 percentage)
583
584 def test_full_transfers(self):
585 """Check that the transfers menu contains the maximum transfers."""
586 # The api of recent transfers always returns a maximum of 5 items
587 data_recent = ['file1', 'file2', 'file3', 'file4', 'file5']
588 self.status_frontend.recent_transfers_data = \
589 data_recent
590 self.sync_menu.transfers.update_progress()
591 children = self.sync_menu.transfers.get_children()
592 self.assertEqual(len(children), 5)
593 data_recent.reverse()
594 for itemM, itemD in zip(children, data_recent):
595 self.assertEqual(itemM.property_get(
596 linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD)
597
598 data_current = [
599 ('file0', 1200, 600),
600 ('file1', 3000, 400),
601 ('file2', 2000, 100),
602 ('file3', 2500, 150),
603 ('file4', 1000, 600),
604 ('file5', 5000, 4600)]
605 uploading_data = {}
606 for filename, size, written in data_current:
607 uploading_data[filename] = (size, written)
608 self.status_frontend.uploading_data = data_current
609 self.sync_menu.transfers.update_progress()
610 children = self.sync_menu.transfers.get_children()
611 # The menu should only show 5 current transfers.
612 self.assertEqual(len(children), 10)
613 data_current.reverse()
614 for item in children[5:]:
615 text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL)
616 self.assertIn(text, uploading_data)
617 size, written = uploading_data[text]
618 percentage = written * 100 / size
619 self.assertEqual(item.property_get_int(
620 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),
621 percentage)
622
623 def test_update_transfers(self):
624 """Check that everything is ok when updating the transfers value."""
625 data_current = [
626 ('file0', 1200, 600),
627 ('file1', 3000, 400),
628 ('file4', 1000, 600),
629 ('file5', 5000, 4600)]
630 uploading_data = {}
631 for filename, size, written in data_current:
632 uploading_data[filename] = (size, written)
633 self.status_frontend.uploading_data = data_current
634 self.sync_menu.transfers.update_progress()
635 children = self.sync_menu.transfers.get_children()
636 # The menu should only show 5 current transfers.
637 self.assertEqual(len(children), 4)
638 data_current.reverse()
639 for item in children:
640 text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL)
641 self.assertIn(text, uploading_data)
642 size, written = uploading_data[text]
643 percentage = written * 100 / size
644 self.assertEqual(item.property_get_int(
645 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),
646 percentage)
647
648 data_recent = ['file5']
649 self.status_frontend.recent_transfers_data = data_recent
650 self.sync_menu.transfers.update_progress()
651 children = self.sync_menu.transfers.get_children()
652 self.assertEqual(len(children), 5)
653 data_recent.reverse()
654 for itemM, itemD in zip(children, data_recent):
655 self.assertEqual(itemM.property_get(
656 linux.Dbusmenu.MENUITEM_PROP_LABEL), itemD)
657
658 data_current = [
659 ('file0', 1200, 700),
660 ('file1', 3000, 600),
661 ('file4', 1000, 800)]
662 uploading_data = {}
663 for filename, size, written in data_current:
664 uploading_data[filename] = (size, written)
665 self.status_frontend.uploading_data = data_current
666 self.sync_menu.transfers.update_progress()
667 children = self.sync_menu.transfers.get_children()
668 # The menu should only show 5 current transfers.
669 self.assertEqual(len(children), 4)
670 data_current.reverse()
671 for item in children[5:]:
672 text = item.property_get(linux.Dbusmenu.MENUITEM_PROP_LABEL)
673 self.assertIn(text, uploading_data)
674 size, written = uploading_data[text]
675 percentage = written * 100 / size
676 self.assertEqual(item.property_get_int(
677 linux.SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE),
678 percentage)
679
680 def test_update_transfers_delay(self):
681 """Check that the timer is being handle properly."""
682 self.patch(linux.status.aggregator, "Timer", FakeTimer)
683 self.sync_menu.next_update = time.time() + 3
684 self.sync_menu.update_transfers()
685 self.assertLess(self.sync_menu.timer.delay, 3)
686 self.sync_menu.timer = None
687 self.sync_menu.next_update = time.time() / 2
688 self.sync_menu.update_transfers()
689 self.assertEqual(self.sync_menu.timer.delay, 0)
690>>>>>>> MERGE-SOURCE
341691
=== modified file 'tests/status/test_aggregator.py'
--- tests/status/test_aggregator.py 2012-08-20 13:18:43 +0000
+++ tests/status/test_aggregator.py 2012-09-26 18:35:23 +0000
@@ -1314,6 +1314,20 @@
1314 self.assertEqual({}, self.aggregator.to_do)1314 self.assertEqual({}, self.aggregator.to_do)
1315 self.assertIdentical(None, self.aggregator.queue_done_timer)1315 self.assertIdentical(None, self.aggregator.queue_done_timer)
13161316
1317 def test_register_listener(self):
1318 """Check that register_listener handles properly additions."""
1319
1320 def fake_callback():
1321 """Do nothing."""
1322
1323 self.aggregator.register_listener(fake_callback)
1324 self.assertEqual(len(self.aggregator.listeners_callbacks), 1)
1325
1326 def test_register_listener_fail(self):
1327 """Check that register_listener handles properly additions."""
1328 self.assertRaises(TypeError, self.aggregator.register_listener, [])
1329 self.assertEqual(len(self.aggregator.listeners_callbacks), 0)
1330
1317 def assertMiscCommandQueued(self, fc):1331 def assertMiscCommandQueued(self, fc):
1318 """Assert that some command was queued."""1332 """Assert that some command was queued."""
1319 self.assertEqual(len(self.aggregator.to_do), 1)1333 self.assertEqual(len(self.aggregator.to_do), 1)
13201334
=== modified file 'tests/syncdaemon/test_status_listener.py'
--- tests/syncdaemon/test_status_listener.py 2012-04-09 20:07:05 +0000
+++ tests/syncdaemon/test_status_listener.py 2012-09-26 18:35:23 +0000
@@ -84,7 +84,7 @@
84 callback(args)84 callback(args)
8585
86 listener = Listener()86 listener = Listener()
87 setattr(listener, 'handle_'+event, listener._handle_event)87 setattr(listener, 'handle_' + event, listener._handle_event)
88 event_q.subscribe(listener)88 event_q.subscribe(listener)
89 return listener89 return listener
9090
@@ -92,7 +92,7 @@
92class FakeStatusFrontend(object):92class FakeStatusFrontend(object):
93 """A fake status frontend."""93 """A fake status frontend."""
9494
95 def __init__(self):95 def __init__(self, *args, **kwargs):
96 """Initialize this instance."""96 """Initialize this instance."""
97 self.call_log = []97 self.call_log = []
9898
9999
=== modified file 'ubuntuone/platform/sync_menu/common.py'
--- ubuntuone/platform/sync_menu/common.py 2012-09-18 16:29:22 +0000
+++ ubuntuone/platform/sync_menu/common.py 2012-09-26 18:35:23 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1# -*- coding: utf-8 *-*2# -*- coding: utf-8 *-*
2#3#
3# Copyright 2012 Canonical Ltd.4# Copyright 2012 Canonical Ltd.
@@ -41,3 +42,43 @@
4142
42 def start_timer(self):43 def start_timer(self):
43 """Empty start timer, this is not needed on windows."""44 """Empty start timer, this is not needed on windows."""
45=======
46# -*- coding: utf-8 *-*
47#
48# Copyright 2012 Canonical Ltd.
49#
50# This program is free software: you can redistribute it and/or modify it
51# under the terms of the GNU General Public License version 3, as published
52# by the Free Software Foundation.
53#
54# This program is distributed in the hope that it will be useful, but
55# WITHOUT ANY WARRANTY; without even the implied warranties of
56# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
57# PURPOSE. See the GNU General Public License for more details.
58#
59# You should have received a copy of the GNU General Public License along
60# with this program. If not, see <http://www.gnu.org/licenses/>.
61#
62# In addition, as a special exception, the copyright holders give
63# permission to link the code of portions of this program with the
64# OpenSSL library under certain conditions as described in each
65# individual source file, and distribute linked combinations
66# including the two.
67# You must obey the GNU General Public License in all respects
68# for all of the code used other than OpenSSL. If you modify
69# file(s) with this exception, you may extend this exception to your
70# version of the file(s), but you are not obligated to do so. If you
71# do not wish to do so, delete this exception statement from your
72# version. If you delete this exception statement from all source
73# files in the program, then also delete it here.
74"""Dummy implementation of sync_menu lib for win32 and darwin."""
75
76
77class UbuntuOneSyncMenu(object):
78 """Integrate U1 with the Ubuntu Sync Menu."""
79 def __init__(self, status, syncdaemon_service):
80 """Match #args of linux syncmenu and do nothing."""
81
82 def update_transfers(self):
83 """Do nothing."""
84>>>>>>> MERGE-SOURCE
4485
=== modified file 'ubuntuone/platform/sync_menu/linux.py'
--- ubuntuone/platform/sync_menu/linux.py 2012-09-24 12:47:05 +0000
+++ ubuntuone/platform/sync_menu/linux.py 2012-09-26 18:35:23 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1# -*- coding: utf-8 *-*2# -*- coding: utf-8 *-*
2#3#
3# Copyright 2012 Canonical Ltd.4# Copyright 2012 Canonical Ltd.
@@ -239,3 +240,255 @@
239240
240241
241UbuntuOneSyncMenu = UbuntuOneSyncMenuLinux if use_syncmenu else DummySyncMenu242UbuntuOneSyncMenu = UbuntuOneSyncMenuLinux if use_syncmenu else DummySyncMenu
243=======
244# -*- coding: utf-8 *-*
245#
246# Copyright 2012 Canonical Ltd.
247#
248# This program is free software: you can redistribute it and/or modify it
249# under the terms of the GNU General Public License version 3, as published
250# by the Free Software Foundation.
251#
252# This program is distributed in the hope that it will be useful, but
253# WITHOUT ANY WARRANTY; without even the implied warranties of
254# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
255# PURPOSE. See the GNU General Public License for more details.
256#
257# You should have received a copy of the GNU General Public License along
258# with this program. If not, see <http://www.gnu.org/licenses/>.
259#
260# In addition, as a special exception, the copyright holders give
261# permission to link the code of portions of this program with the
262# OpenSSL library under certain conditions as described in each
263# individual source file, and distribute linked combinations
264# including the two.
265# You must obey the GNU General Public License in all respects
266# for all of the code used other than OpenSSL. If you modify
267# file(s) with this exception, you may extend this exception to your
268# version of the file(s), but you are not obligated to do so. If you
269# do not wish to do so, delete this exception statement from your
270# version. If you delete this exception statement from all source
271# files in the program, then also delete it here.
272"""Use SyncMenu lib to integrate U1 with the Systray Sync Icon."""
273
274import gettext
275import logging
276import time
277import sys
278import webbrowser
279
280glib = None
281try:
282 if 'gobject' in sys.modules and sys.modules['gobject'] is not None:
283 import glib as GLib
284 glib = GLib
285 else:
286 from gi.repository import GLib
287 glib = GLib
288except ImportError:
289 pass
290try:
291 from gi.repository import (
292 Dbusmenu,
293 SyncMenu,
294 )
295 use_syncmenu = True
296except:
297 use_syncmenu = False
298
299from ubuntuone.clientdefs import GETTEXT_PACKAGE
300from ubuntuone import status
301
302
303logger = logging.getLogger("ubuntuone.platform.SyncMenu")
304
305Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string)
306
307OPEN_U1 = Q_("Open Ubuntu One")
308GO_TO_WEB = Q_("Go to the Ubuntu One Website")
309TRANSFERS = Q_("Current and Recent Transfers")
310MORE_STORAGE = Q_("Get More Space")
311GET_HELP = Q_("Get Help on the Web")
312
313DELAY_BETWEEN_UPDATES = 3
314UBUNTUONE_LINK = u'https://one.ubuntu.com/'
315DASHBOARD = UBUNTUONE_LINK + u'dashboard/'
316HELP_LINK = UBUNTUONE_LINK + u'support/'
317GET_STORAGE_LINK = UBUNTUONE_LINK + u'services/#storage_panel'
318
319
320class UbuntuOneSyncMenuLinux(object):
321 """Integrate U1 with the Ubuntu Sync Menu."""
322
323 def __init__(self, status, syncdaemon_service):
324 """Initialize menu."""
325 self._syncdaemon_service = syncdaemon_service
326 self._paused = False
327 self.timer = None
328 self.next_update = time.time()
329 self.root_menu = Dbusmenu.Menuitem()
330
331 self.open_u1 = Dbusmenu.Menuitem()
332 self.open_u1.property_set(Dbusmenu.MENUITEM_PROP_LABEL, OPEN_U1)
333
334 self.go_to_web = Dbusmenu.Menuitem()
335 self.go_to_web.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
336 GO_TO_WEB)
337
338 self.transfers = TransfersMenu(status)
339 self.transfers.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
340 TRANSFERS)
341
342 self.more_storage = Dbusmenu.Menuitem()
343 self.more_storage.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
344 MORE_STORAGE)
345
346 self.get_help = Dbusmenu.Menuitem()
347 self.get_help.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
348 GET_HELP)
349
350 # Connect signals
351 self.open_u1.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
352 self.open_control_panel)
353 self.go_to_web.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
354 self.open_go_to_web)
355 self.get_help.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
356 self.open_web_help)
357 self.more_storage.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
358 self.open_get_more_storage)
359
360 # Add items
361 self.root_menu.child_append(self.open_u1)
362 self.root_menu.child_append(self.go_to_web)
363 self.root_menu.child_append(self.transfers)
364 self.root_menu.child_append(self.more_storage)
365 self.root_menu.child_append(self.get_help)
366
367 self.server = Dbusmenu.Server()
368 self.server.set_root(self.root_menu)
369 self.app = SyncMenu.App.new("ubuntuone-installer.desktop")
370 self.app.set_menu(self.server)
371 self.app.connect("notify::paused", self.change_sync_status)
372
373 def change_sync_status(self, *args):
374 """Triggered when the sync status is changed fromm the menu."""
375 if self._paused:
376 self._syncdaemon_service.connect()
377 self._paused = False
378 else:
379 self._syncdaemon_service.disconnect()
380 self._paused = True
381
382 def open_control_panel(self, *args):
383 """Open the Ubuntu One Control Panel."""
384 glib.spawn_command_line_async('ubuntuone-installer')
385
386 def open_go_to_web(self, *args):
387 """Open the Ubunto One Help Page"""
388 webbrowser.open(DASHBOARD)
389
390 def open_web_help(self, *args):
391 """Open the Ubunto One Help Page"""
392 webbrowser.open(HELP_LINK)
393
394 def open_get_more_storage(self, *args):
395 """Open the Ubunto One Help Page"""
396 webbrowser.open(GET_STORAGE_LINK)
397
398 def _timeout(self, result):
399 """The aggregating timer has expired, so update the UI."""
400 self.next_update = int(time.time()) + DELAY_BETWEEN_UPDATES
401 self.transfers.update_progress()
402 self.timer = None
403
404 def update_transfers(self):
405 """Set up a timer if there isn't one ticking and update the ui."""
406 if not self.timer:
407 logger.debug("Updating Transfers.")
408 delay = int(max(0, min(DELAY_BETWEEN_UPDATES,
409 self.next_update - time.time())))
410 self.timer = status.aggregator.Timer(delay)
411 self.timer.addCallback(self._timeout)
412
413
414class TransfersMenu(Dbusmenu.Menuitem):
415 """Menu that handles the recent and current transfers."""
416
417 def __init__(self, status_frontend):
418 super(TransfersMenu, self).__init__()
419 self.status_frontend = status_frontend
420 self.uploading = {}
421 self.previous_transfers = []
422 self._transfers_items = {}
423 self._uploading_items = {}
424
425 def update_progress(self):
426 """Update the list of recent transfers and current transfers."""
427 current_transfers = self.status_frontend.recent_transfers()
428 uploading_data = {}
429 for filename, size, written in \
430 self.status_frontend.files_uploading():
431 uploading_data[filename] = (size, written)
432
433 temp_transfers = {}
434 if current_transfers != self.previous_transfers:
435 logger.debug("Update recent transfers with: %r", current_transfers)
436 for item_transfer in self._transfers_items:
437 self.child_delete(self._transfers_items[item_transfer])
438 for item in current_transfers:
439 recent_file = Dbusmenu.Menuitem()
440 recent_file.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
441 item)
442 self.child_add_position(recent_file, 0)
443 temp_transfers[item] = recent_file
444 self._transfers_items = temp_transfers
445
446 items_added = 0
447 remove = []
448 for item in self._uploading_items:
449 if item in uploading_data:
450 size, written = uploading_data[item]
451 percentage = written * 100 / size
452 upload_item = self._uploading_items[item]
453 upload_item.property_set_int(
454 SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE,
455 percentage)
456 logger.debug("Current transfer %s progress update: %r",
457 item, percentage)
458 items_added += 1
459 else:
460 self.child_delete(self._uploading_items[item])
461 remove.append(item)
462 for item in remove:
463 self._uploading_items.pop(item)
464 if items_added < 5:
465 for item in uploading_data:
466 if item not in self._uploading_items and items_added < 5:
467 size, written = uploading_data[item]
468 percentage = written * 100 / size
469 uploading_file = Dbusmenu.Menuitem()
470 uploading_file.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
471 item)
472 uploading_file.property_set(Dbusmenu.MENUITEM_PROP_TYPE,
473 SyncMenu.PROGRESS_MENUITEM_TYPE)
474 uploading_file.property_set_int(
475 SyncMenu.PROGRESS_MENUITEM_PROP_PERCENT_DONE,
476 percentage)
477 logger.debug("Current transfer %s created", item)
478 self.child_append(uploading_file)
479 self._uploading_items[item] = uploading_file
480 items_added += 1
481
482
483class DummySyncMenu(object):
484 """Dummy SyncMenu"""
485
486 def __init__(self, *args, **kwargs):
487 """Initialize menu."""
488
489 def start_timer(self):
490 """Do nothing."""
491
492
493UbuntuOneSyncMenu = UbuntuOneSyncMenuLinux if use_syncmenu else DummySyncMenu
494>>>>>>> MERGE-SOURCE
242495
=== modified file 'ubuntuone/status/aggregator.py'
--- ubuntuone/status/aggregator.py 2012-08-16 12:00:12 +0000
+++ ubuntuone/status/aggregator.py 2012-09-26 18:35:23 +0000
@@ -33,7 +33,7 @@
33import itertools33import itertools
34import operator34import operator
35import os35import os
36from collections import deque36from collections import deque, Callable
3737
38import gettext38import gettext
3939
@@ -41,7 +41,11 @@
4141
42from ubuntuone.clientdefs import GETTEXT_PACKAGE42from ubuntuone.clientdefs import GETTEXT_PACKAGE
43from ubuntuone.status.logger import logger43from ubuntuone.status.logger import logger
44from ubuntuone.platform import session, notification44from ubuntuone.platform import (
45 notification,
46 session,
47 sync_menu
48)
45from ubuntuone.platform.messaging import Messaging49from ubuntuone.platform.messaging import Messaging
46from ubuntuone.platform.launcher import UbuntuOneLauncher, DummyLauncher50from ubuntuone.platform.launcher import UbuntuOneLauncher, DummyLauncher
4751
@@ -625,6 +629,7 @@
625 self.progress = {}629 self.progress = {}
626 self.to_do = {}630 self.to_do = {}
627 self.recent_transfers = deque(maxlen=5)631 self.recent_transfers = deque(maxlen=5)
632 self.listeners_callbacks = []
628633
629 def get_notification(self):634 def get_notification(self):
630 """Create a new toggleable notification object."""635 """Create a new toggleable notification object."""
@@ -655,6 +660,13 @@
655 self.to_do = {}660 self.to_do = {}
656 # pylint: enable=W0201661 # pylint: enable=W0201
657662
663 def register_listener(self, listener):
664 """Register a callable object to be notified."""
665 if isinstance(listener, Callable):
666 self.listeners_callbacks.append(listener)
667 else:
668 raise TypeError("Callable object expected.")
669
658 def get_discovery_message(self):670 def get_discovery_message(self):
659 """Get the text for the discovery bubble."""671 """Get the text for the discovery bubble."""
660 lines = []672 lines = []
@@ -716,6 +728,8 @@
716 progress = float(728 progress = float(
717 sum(self.progress.values())) / sum(self.to_do.values())729 sum(self.progress.values())) / sum(self.to_do.values())
718 self.progress_bar.set_progress(progress)730 self.progress_bar.set_progress(progress)
731 for listener in self.listeners_callbacks:
732 listener()
719733
720 def download_started(self, command):734 def download_started(self, command):
721 """A download just started."""735 """A download just started."""
@@ -801,13 +815,24 @@
801class StatusFrontend(object):815class StatusFrontend(object):
802 """Frontend for the status aggregator, used by the StatusListener."""816 """Frontend for the status aggregator, used by the StatusListener."""
803817
804 def __init__(self, clock=reactor):818 def __init__(self, clock=reactor, service=None):
805 """Initialize this instance."""819 """Initialize this instance."""
806 self.aggregator = StatusAggregator(clock=clock)820 self.aggregator = StatusAggregator(clock=clock)
807 self.notification = self.aggregator.get_notification()821 self.notification = self.aggregator.get_notification()
808 self.messaging = Messaging()822 self.messaging = Messaging()
809 self.quota_timer = None823 self.quota_timer = None
810824
825 self.syncdaemon_service = service
826 self.sync_menu = None
827 self.start_sync_menu()
828
829 def start_sync_menu(self):
830 """Create the sync menu and run the loop."""
831 if self.syncdaemon_service is not None:
832 self.sync_menu = sync_menu.UbuntuOneSyncMenu(self,
833 self.syncdaemon_service)
834 self.aggregator.register_listener(self.sync_menu.update_transfers)
835
811 def recent_transfers(self):836 def recent_transfers(self):
812 """Return a tuple with the recent transfers paths."""837 """Return a tuple with the recent transfers paths."""
813 return list(self.aggregator.recent_transfers)838 return list(self.aggregator.recent_transfers)
814839
=== modified file 'ubuntuone/syncdaemon/main.py'
--- ubuntuone/syncdaemon/main.py 2012-09-19 17:23:02 +0000
+++ ubuntuone/syncdaemon/main.py 2012-09-26 18:35:23 +0000
@@ -176,7 +176,8 @@
176176
177 def start_status_listener(self):177 def start_status_listener(self):
178 """Start the status listener if it is configured to start."""178 """Start the status listener if it is configured to start."""
179 self.status_listener = status_listener.get_listener(self.fs, self.vm)179 self.status_listener = status_listener.get_listener(self.fs, self.vm,
180 self.external)
180 # subscribe to EQ, to be unsubscribed in shutdown181 # subscribe to EQ, to be unsubscribed in shutdown
181 if self.status_listener:182 if self.status_listener:
182 self.event_q.subscribe(self.status_listener)183 self.event_q.subscribe(self.status_listener)
183184
=== modified file 'ubuntuone/syncdaemon/status_listener.py'
--- ubuntuone/syncdaemon/status_listener.py 2012-08-10 12:49:46 +0000
+++ ubuntuone/syncdaemon/status_listener.py 2012-09-26 18:35:23 +0000
@@ -48,10 +48,10 @@
48 return True48 return True
4949
5050
51def get_listener(fsm, vm):51def get_listener(fsm, vm, syncdaemon_service=None):
52 """Return an instance of the status listener, or None if turned off."""52 """Return an instance of the status listener, or None if turned off."""
53 if should_start_listener():53 if should_start_listener():
54 status_frontend = StatusFrontend()54 status_frontend = StatusFrontend(service=syncdaemon_service)
55 return StatusListener(fsm, vm, status_frontend)55 return StatusListener(fsm, vm, status_frontend)
56 else:56 else:
57 return None57 return None

Subscribers

People subscribed via source and target branches