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 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches