Merge lp:~ubuntu-branches/ubuntu/precise/ubuntuone-client/precise-201112142106 into lp:ubuntu/precise/ubuntuone-client

Proposed by Ubuntu Package Importer
Status: Rejected
Rejected by: James Westby
Proposed branch: lp:~ubuntu-branches/ubuntu/precise/ubuntuone-client/precise-201112142106
Merge into: lp:ubuntu/precise/ubuntuone-client
Diff against target: 2539 lines (+2516/-0) (has conflicts)
3 files modified
.pc/03_reset_notify_name.patch/tests/status/test_aggregator.py (+1560/-0)
.pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py (+882/-0)
debian/patches/03_reset_notify_name.patch (+74/-0)
Conflict adding file .pc/03_reset_notify_name.patch.  Moved existing file to .pc/03_reset_notify_name.patch.moved.
Conflict adding file debian/patches/03_reset_notify_name.patch.  Moved existing file to debian/patches/03_reset_notify_name.patch.moved.
To merge this branch: bzr merge lp:~ubuntu-branches/ubuntu/precise/ubuntuone-client/precise-201112142106
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+85748@code.launchpad.net

Description of the change

The package importer has detected a possible inconsistency between the package history in the archive and the history in bzr. As the archive is authoritative the importer has made lp:ubuntu/precise/ubuntuone-client reflect what is in the archive and the old bzr branch has been pushed to lp:~ubuntu-branches/ubuntu/precise/ubuntuone-client/precise-201112142106. This merge proposal was created so that an Ubuntu developer can review the situations and perform a merge/upload if necessary. There are three typical cases where this can happen.
  1. Where someone pushes a change to bzr and someone else uploads the package without that change. This is the reason that this check is done by the importer. If this appears to be the case then a merge/upload should be done if the changes that were in bzr are still desirable.
  2. The importer incorrectly detected the above situation when someone made a change in bzr and then uploaded it.
  3. The importer incorrectly detected the above situation when someone just uploaded a package and didn't touch bzr.

If this case doesn't appear to be the first situation then set the status of the merge proposal to "Rejected" and help avoid the problem in future by filing a bug at https://bugs.launchpad.net/udd linking to this merge proposal.

(this is an automatically generated message)

To post a comment you must log in.

Unmerged revisions

92. By Ken VanDine

releasing version 2.0.0-0ubuntu4

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc/03_reset_notify_name.patch'
2=== renamed directory '.pc/03_reset_notify_name.patch' => '.pc/03_reset_notify_name.patch.moved'
3=== added file '.pc/03_reset_notify_name.patch/.timestamp'
4=== added directory '.pc/03_reset_notify_name.patch/tests'
5=== added directory '.pc/03_reset_notify_name.patch/tests/status'
6=== added file '.pc/03_reset_notify_name.patch/tests/status/test_aggregator.py'
7--- .pc/03_reset_notify_name.patch/tests/status/test_aggregator.py 1970-01-01 00:00:00 +0000
8+++ .pc/03_reset_notify_name.patch/tests/status/test_aggregator.py 2011-12-14 21:11:28 +0000
9@@ -0,0 +1,1560 @@
10+# tests.status.test_aggregator
11+#
12+# Author: Alejandro J. Cura <alecu@canonical.com>
13+#
14+# Copyright 2011 Canonical Ltd.
15+#
16+# This program is free software: you can redistribute it and/or modify it
17+# under the terms of the GNU General Public License version 3, as published
18+# by the Free Software Foundation.
19+#
20+# This program is distributed in the hope that it will be useful, but
21+# WITHOUT ANY WARRANTY; without even the implied warranties of
22+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
23+# PURPOSE. See the GNU General Public License for more details.
24+#
25+# You should have received a copy of the GNU General Public License along
26+# with this program. If not, see <http://www.gnu.org/licenses/>.
27+"""Tests for the status events aggregator."""
28+
29+import logging
30+
31+from twisted.internet import defer
32+from twisted.internet.task import Clock
33+from twisted.trial.unittest import TestCase
34+from mocker import Mocker
35+
36+from contrib.testing.testcase import BaseTwistedTestCase
37+from ubuntuone.devtools.handlers import MementoHandler
38+from ubuntuone.status import aggregator
39+from ubuntuone.status.notification import AbstractNotification
40+from ubuntuone.status.messaging import AbstractMessaging
41+from ubuntuone.syncdaemon import status_listener
42+from ubuntuone.syncdaemon.volume_manager import Share, UDF, Root
43+
44+FILENAME = 'example.txt'
45+FILENAME2 = 'another_example.mp3'
46+
47+
48+class PatchedClock(Clock):
49+ """Patch the clock to fix twisted bug #4823."""
50+
51+ def advance(self, amount):
52+ """Sort the calls before advancing the clock."""
53+ self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
54+ Clock.advance(self, amount)
55+
56+
57+class TimerTestCase(TestCase):
58+ """Test the Timer class."""
59+
60+ TIMEOUT = 3.0
61+
62+ @defer.inlineCallbacks
63+ def setUp(self):
64+ """Initialize this test instance."""
65+ yield super(TimerTestCase, self).setUp()
66+ self.clock = PatchedClock()
67+ self.timer = aggregator.Timer(delay=3.0, clock=self.clock)
68+
69+ def test_not_fired_initially(self):
70+ """The timer is not fired initially"""
71+ self.assertFalse(self.timer.called)
72+
73+ def test_fired_after_delay(self):
74+ """The timer is fired after the initial delay."""
75+ self.clock.advance(self.timer.delay)
76+ self.assertTrue(self.timer.called)
77+
78+ def test_cleanup_cancels_delay_call(self):
79+ """Calling cleanup cancels the delay call."""
80+ self.timer.cleanup()
81+ self.assertTrue(self.timer.delay_call.cancelled)
82+
83+ def test_not_fired_immediately(self):
84+ """The timer is not fired immediately."""
85+ self.timer.reset()
86+ self.assertFalse(self.timer.called)
87+
88+ def test_fired_after_initial_wait(self):
89+ """The timer is fired after an initial wait."""
90+ self.timer.reset()
91+ self.clock.advance(self.timer.delay)
92+ self.assertTrue(self.timer.called)
93+
94+ def test_not_fired_if_reset_within_delay(self):
95+ """The timer is not fired if it is reset within the delay."""
96+ self.timer.reset()
97+ self.clock.advance(self.timer.delay / 0.8)
98+ self.timer.reset()
99+ self.clock.advance(self.timer.delay / 0.8)
100+ self.assertTrue(self.timer.called)
101+
102+ def test_active(self):
103+ """The timer is active until the delay is reached."""
104+ self.timer.reset()
105+ self.assertTrue(self.timer.active)
106+ self.clock.advance(self.timer.delay + 1)
107+ self.assertFalse(self.timer.active)
108+
109+
110+class DeadlineTimerTestCase(TimerTestCase):
111+ """Test the DeadlineTimer class."""
112+
113+ DELAY = 0.5
114+
115+ @defer.inlineCallbacks
116+ def setUp(self):
117+ """Initialize this test instance."""
118+ yield super(DeadlineTimerTestCase, self).setUp()
119+ self.clock = PatchedClock()
120+ self.timer = aggregator.DeadlineTimer(delay=0.5, timeout=3.0,
121+ clock=self.clock)
122+
123+ def test_fired_if_initial_timeout_exceeded(self):
124+ """Timer is fired if the initial timeout is exceeded."""
125+ small_delay = self.timer.delay * 0.8
126+ for n in range(int(self.timer.timeout / small_delay) + 1):
127+ self.timer.reset()
128+ self.clock.advance(small_delay)
129+ self.assertTrue(self.timer.called)
130+
131+ def test_not_fired_twice_if_delay_exceeded(self):
132+ """Timer is not fired twice if the delay is exceeded."""
133+ large_delay = self.timer.delay * 1.2
134+ for n in range(int(self.timer.timeout / large_delay) + 1):
135+ self.timer.reset()
136+ self.clock.advance(large_delay)
137+ self.clock.advance(self.timer.delay)
138+ self.assertTrue(self.timer.called)
139+
140+ def test_not_fired_twice_if_timeout_exceeded(self):
141+ """Timer is not fired twice if the timeout is exceeded."""
142+ small_delay = self.timer.delay * 0.8
143+ for n in range(int(self.timer.timeout / small_delay) + 1):
144+ self.timer.reset()
145+ self.clock.advance(small_delay)
146+ self.clock.advance(self.timer.delay)
147+ self.assertTrue(self.timer.called)
148+
149+ def test_cleanup_cancels_timeout_call(self):
150+ """Calling cleanup cancels the delay call."""
151+ self.timer.cleanup()
152+ self.assertTrue(self.timer.timeout_call.cancelled)
153+
154+
155+class FakeNotification(AbstractNotification):
156+ """A fake notification class."""
157+
158+ def __init__(self, application_name="fake app"):
159+ """Initialize this instance."""
160+ self.notifications_shown = []
161+ self.notification_switch = None
162+ self.application_name = application_name
163+ self.notification = None
164+
165+ def send_notification(self, title, message, icon=None, append=False):
166+ """Show a notification to the user."""
167+ if (self.notification_switch is not None
168+ and not self.notification_switch.enabled):
169+ return
170+ self.notification = (title, message, icon, append)
171+ self.notifications_shown.append((title, message, icon, append))
172+ return len(self.notifications_shown) - 1
173+
174+
175+def FakeNotificationSingleton():
176+ """Builds a notification singleton, that logs all notifications shown."""
177+ instance = FakeNotification()
178+
179+ def get_instance(notification_switch):
180+ """Returns the single instance."""
181+ instance.notification_switch = notification_switch
182+ return instance
183+
184+ return get_instance
185+
186+
187+class FakeMessaging(AbstractMessaging):
188+ """A fake messaging class."""
189+
190+ def __init__(self): # pylint: disable=W0231
191+ self.messages_shown = {}
192+ self.messages_updated = {}
193+ self.callbacks = []
194+
195+ # pylint: disable=R0913
196+ def show_message(self, sender, callback=None, message_time=None,
197+ message_count=None, icon=None):
198+ """Show a message to the user."""
199+ if message_count and sender in self.messages_shown:
200+ self.update_count(sender, message_count)
201+ self.messages_shown[sender] = (
202+ callback, message_time, message_count, icon)
203+ # pylint: enable=R0913
204+
205+ def update_count(self, sender, add_count):
206+ """Update the count for an existing indicator."""
207+ self.messages_updated[sender] = (sender, add_count)
208+
209+ def _callback(self, indicator, message_time=None):
210+ """Fake callback."""
211+ self.callbacks.append((indicator, message_time))
212+
213+ def create_callback(self):
214+ """Create the callback."""
215+ return self._callback
216+
217+
218+class FakeStatusAggregator(object):
219+ """A fake status aggregator."""
220+
221+ def __init__(self, clock): # pylint: disable=W0613
222+ """Initialize this instance."""
223+ self.discovered = 0
224+ self.completed = 0
225+ self.notification_switch = aggregator.NotificationSwitch()
226+
227+ def get_discovery_message(self):
228+ """Return the file discovery message."""
229+ self.discovered += 1
230+ return self.build_discovery_message()
231+
232+ def build_discovery_message(self):
233+ """Build the file discovery message."""
234+ return "a lot of files found (%d).""" % self.discovered
235+
236+ def get_progress_message(self):
237+ """Return the progress message."""
238+ self.completed += 1
239+ return self.build_progress_message()
240+
241+ def build_progress_message(self):
242+ """Build the progress message."""
243+ params = (self.discovered, self.completed)
244+ return "a lot of files transferring (%d/%d).""" % params
245+
246+ def get_final_status_message(self):
247+ """Return the final status message."""
248+ return "a lot of files completed."""
249+
250+ def get_notification(self):
251+ """Create a new toggleable notification object."""
252+ return self.notification_switch.get_notification()
253+
254+
255+class ToggleableNotificationTestCase(TestCase):
256+ """Test the ToggleableNotification class."""
257+
258+ @defer.inlineCallbacks
259+ def setUp(self):
260+ """Initialize this test instance."""
261+ yield super(ToggleableNotificationTestCase, self).setUp()
262+ self.patch(aggregator, "Notification", FakeNotification)
263+ self.notification_switch = aggregator.NotificationSwitch()
264+ self.toggleable = self.notification_switch.get_notification()
265+
266+ def assertShown(self, notification):
267+ """Assert that the notification was shown."""
268+ self.assertIn(notification,
269+ self.toggleable.notification.notifications_shown)
270+
271+ def assertNotShown(self, notification):
272+ """Assert that the notification was shown."""
273+ self.assertNotIn(notification,
274+ self.toggleable.notification.notifications_shown)
275+
276+ def test_send_notification_passes_thru(self):
277+ """The send_notification method passes thru."""
278+ args = (1, 2, 3, 4)
279+ self.toggleable.send_notification(*args)
280+ self.assertShown(args)
281+
282+ def test_send_notification_honored_when_enabled(self):
283+ """The send_notification method is honored when enabled."""
284+ self.notification_switch.enable_notifications()
285+ args = (aggregator.UBUNTUONE_TITLE, "hello", None, False)
286+ self.toggleable.send_notification(*args)
287+ self.assertShown(args)
288+
289+ def test_send_notification_ignored_when_disabled(self):
290+ """The send_notification method is ignored when disabled."""
291+ self.notification_switch.disable_notifications()
292+ args = (aggregator.UBUNTUONE_TITLE, "hello", None, False)
293+ self.toggleable.send_notification(*args)
294+ self.assertNotShown(args)
295+
296+
297+class NotificationSwitchTestCase(TestCase):
298+ """Test the NotificationSwitch class."""
299+
300+ @defer.inlineCallbacks
301+ def setUp(self):
302+ """Initialize this test instance."""
303+ yield super(NotificationSwitchTestCase, self).setUp()
304+ self.notification_switch = aggregator.NotificationSwitch()
305+
306+ def test_get_notification(self):
307+ """A new notification instance is returned."""
308+ notification = self.notification_switch.get_notification()
309+ self.assertEqual(notification.notification_switch,
310+ self.notification_switch)
311+
312+ def test_enable_notifications(self):
313+ """The switch is turned on."""
314+ self.notification_switch.enable_notifications()
315+ self.assertTrue(self.notification_switch.enabled)
316+
317+ def test_disable_notifications(self):
318+ """The switch is turned off."""
319+ self.notification_switch.disable_notifications()
320+ self.assertFalse(self.notification_switch.enabled)
321+
322+
323+class FileDiscoveryBubbleTestCase(TestCase):
324+ """Test the FileDiscoveryBubble class."""
325+
326+ @defer.inlineCallbacks
327+ def setUp(self):
328+ """Initialize this test instance."""
329+ yield super(FileDiscoveryBubbleTestCase, self).setUp()
330+ self.patch(aggregator, "ToggleableNotification",
331+ FakeNotificationSingleton())
332+ self.clock = PatchedClock()
333+ self.aggregator = FakeStatusAggregator(clock=self.clock)
334+ self.bubble = aggregator.FileDiscoveryBubble(self.aggregator,
335+ clock=self.clock)
336+ self.addCleanup(self.bubble.cleanup)
337+ fdis = aggregator.FileDiscoveryGatheringState
338+ self.initial_delay = fdis.initial_delay
339+ self.smaller_delay = self.initial_delay * 0.8
340+ self.initial_timeout = fdis.initial_timeout
341+ fdus = aggregator.FileDiscoveryUpdateState
342+ self.updates_delay = fdus.updates_delay
343+ self.updates_timeout = fdus.updates_timeout
344+ fdss = aggregator.FileDiscoverySleepState
345+ self.sleep_delay = fdss.sleep_delay
346+
347+ self.handler = MementoHandler()
348+ self.handler.setLevel(logging.DEBUG)
349+ aggregator.logger.addHandler(self.handler)
350+ aggregator.logger.setLevel(logging.DEBUG)
351+ self.addCleanup(aggregator.logger.removeHandler, self.handler)
352+
353+ def get_notifications_shown(self):
354+ """The list of notifications shown."""
355+ return self.bubble.notification.notifications_shown
356+
357+ def test_popup_shows_notification_when_connected(self):
358+ """The popup callback shows notifications."""
359+ self.bubble.connection_made()
360+ self.bubble.new_file_found()
361+ self.bubble._popup()
362+ message = self.aggregator.build_discovery_message()
363+ notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
364+ self.assertIn(notification, self.get_notifications_shown())
365+
366+ def test_popup_shows_notification_after_connected(self):
367+ """The popup callback shows notifications."""
368+ self.bubble.new_file_found()
369+ self.bubble.connection_made()
370+ message = self.aggregator.build_discovery_message()
371+ notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
372+ self.assertIn(notification, self.get_notifications_shown())
373+
374+ def test_popup_shows_no_notification_before_connection_made(self):
375+ """The popup callback shows notifications."""
376+ self.bubble.new_file_found()
377+ self.bubble._popup()
378+ message = self.aggregator.build_discovery_message()
379+ notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
380+ self.assertNotIn(notification, self.get_notifications_shown())
381+
382+ def test_popup_shows_no_notification_after_connection_lost(self):
383+ """The popup callback shows notifications."""
384+ self.bubble.connection_made()
385+ self.bubble.connection_lost()
386+ self.bubble.new_file_found()
387+ self.bubble._popup()
388+ message = self.aggregator.build_discovery_message()
389+ notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
390+ self.assertNotIn(notification, self.get_notifications_shown())
391+
392+ def test_notification_is_logged_in_debug(self):
393+ """The notification is printed in the debug log."""
394+ self.bubble.connection_made()
395+ self.bubble.new_file_found()
396+ self.bubble._popup()
397+ msg = "notification shown: %s" % self.get_notifications_shown()[0][1]
398+ self.assertTrue(self.handler.check_debug(msg))
399+
400+ def test_bubble_is_not_shown_initially(self):
401+ """The bubble is not shown initially."""
402+ self.bubble.new_file_found()
403+ self.assertEqual(0, len(self.get_notifications_shown()))
404+
405+ def test_bubble_is_shown_after_delay(self):
406+ """The bubble is shown after a delay."""
407+ self.bubble.connection_made()
408+ self.bubble.new_file_found()
409+ self.clock.advance(self.initial_delay)
410+ self.assertEqual(1, len(self.get_notifications_shown()))
411+
412+ def test_bubble_not_shown_if_more_files_found(self):
413+ """The bubble is not shown if more files found within delay."""
414+ self.clock.advance(self.smaller_delay)
415+ self.bubble.new_file_found()
416+ self.clock.advance(self.smaller_delay)
417+ self.assertEqual(0, len(self.get_notifications_shown()))
418+
419+ def test_bubble_shown_if_timeout_exceeded(self):
420+ """The bubble is shown if the timeout is exceeded."""
421+ self.bubble.connection_made()
422+ self.bubble.new_file_found()
423+ count = int(self.initial_timeout / self.smaller_delay) + 1
424+ for n in range(count):
425+ self.clock.advance(self.smaller_delay)
426+ self.bubble.new_file_found()
427+ self.assertEqual(1, len(self.get_notifications_shown()))
428+
429+ def test_idle_state(self):
430+ """The idle state is verified."""
431+ self.assertEqual(type(self.bubble.state),
432+ aggregator.FileDiscoveryIdleState)
433+
434+ def test_gathering_state(self):
435+ """The gathering state is set after the first file is found."""
436+ self.bubble.new_file_found()
437+ self.assertEqual(type(self.bubble.state),
438+ aggregator.FileDiscoveryGatheringState)
439+
440+ def test_update_state(self):
441+ """When the gathering state finishes, the update state is started."""
442+ self.bubble.connection_made()
443+ self.bubble.new_file_found()
444+ self.clock.advance(self.initial_delay)
445+ self.assertEqual(type(self.bubble.state),
446+ aggregator.FileDiscoveryUpdateState)
447+
448+ def test_sleeping_state(self):
449+ """When the update state finishes, the sleeping state is started."""
450+ self.bubble.connection_made()
451+ self.bubble.new_file_found()
452+ self.clock.advance(self.initial_delay)
453+ self.clock.advance(self.updates_timeout)
454+ self.assertEqual(type(self.bubble.state),
455+ aggregator.FileDiscoverySleepState)
456+
457+ def test_back_to_initial_state(self):
458+ """When the last state finishes, we return to the idle state."""
459+ self.bubble.connection_made()
460+ self.bubble.new_file_found()
461+ self.clock.advance(self.initial_delay)
462+ self.clock.advance(self.updates_timeout)
463+ self.clock.advance(self.sleep_delay)
464+ self.assertEqual(type(self.bubble.state),
465+ aggregator.FileDiscoveryIdleState)
466+
467+ def test_new_files_found_while_updating_not_shown_immediately(self):
468+ """New files found in the updating state are not shown immediately."""
469+ self.bubble.connection_made()
470+ self.bubble.new_file_found()
471+ self.clock.advance(self.initial_delay)
472+ self.bubble.new_file_found()
473+ self.assertEqual(1, len(self.get_notifications_shown()))
474+
475+ def test_new_files_found_while_updating_are_shown_after_a_delay(self):
476+ """New files found in the updating state are shown after a delay."""
477+ self.bubble.connection_made()
478+ self.bubble.new_file_found()
479+ self.clock.advance(self.initial_delay)
480+ self.bubble.new_file_found()
481+ self.clock.advance(self.updates_delay)
482+ self.assertEqual(2, len(self.get_notifications_shown()))
483+
484+ def test_update_modifies_notification(self):
485+ """The update callback updates notifications."""
486+ self.bubble.connection_made()
487+ self.bubble.new_file_found()
488+ self.bubble._popup()
489+ self.bubble.new_file_found()
490+ self.bubble._update()
491+ message = self.aggregator.build_discovery_message()
492+ notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
493+ self.assertIn(notification, self.get_notifications_shown())
494+
495+ def test_update_is_logged_in_debug(self):
496+ """The notification is logged when _update is called."""
497+ self.bubble.connection_made()
498+ self.bubble.new_file_found()
499+ self.bubble._popup()
500+ self.bubble.new_file_found()
501+ self.bubble._update()
502+ msg = "notification updated: %s" % self.get_notifications_shown()[1][1]
503+ self.assertTrue(self.handler.check_debug(msg))
504+
505+
506+class FinalBubbleTestCase(TestCase):
507+ """Test for the final status notification bubble."""
508+
509+ @defer.inlineCallbacks
510+ def setUp(self):
511+ """Initialize this test instance."""
512+ yield super(FinalBubbleTestCase, self).setUp()
513+ self.patch(aggregator, "ToggleableNotification",
514+ FakeNotificationSingleton())
515+ self.clock = PatchedClock()
516+ self.aggregator = FakeStatusAggregator(clock=self.clock)
517+ self.bubble = aggregator.FinalStatusBubble(self.aggregator)
518+ self.addCleanup(self.bubble.cleanup)
519+
520+ def test_notification_not_shown_initially(self):
521+ """The notification is not shown initially."""
522+ self.assertEqual(None, self.bubble.notification)
523+
524+ def test_show_pops_bubble(self):
525+ """The show method pops the bubble immediately."""
526+ self.bubble.show()
527+ self.assertEqual(1, len(self.bubble.notification.notifications_shown))
528+
529+
530+class FakeLauncher(object):
531+ """A fake UbuntuOneLauncher."""
532+
533+ progress_visible = False
534+ progress = 0.0
535+
536+ def show_progressbar(self):
537+ """The progressbar is shown."""
538+ self.progress_visible = True
539+
540+ def hide_progressbar(self):
541+ """The progressbar is hidden."""
542+ self.progress_visible = False
543+
544+ def set_progress(self, value):
545+ """The progressbar value is changed."""
546+ self.progress = value
547+
548+
549+class FakeInhibitor(object):
550+ """A fake session inhibitor."""
551+
552+ def inhibit(self, flags, reason):
553+ """Inhibit some events with a given reason."""
554+ self.flags = flags
555+ return defer.succeed(self)
556+
557+ def cancel(self):
558+ """Cancel the inhibition for the current cookie."""
559+ self.flags = 0
560+ return defer.succeed(self)
561+
562+
563+class ProgressBarTestCase(TestCase):
564+ """Tests for the progress bar."""
565+
566+ @defer.inlineCallbacks
567+ def setUp(self):
568+ """Initialize this test instance."""
569+ yield super(ProgressBarTestCase, self).setUp()
570+ self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
571+ self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
572+ self.clock = PatchedClock()
573+ self.bar = aggregator.ProgressBar(clock=self.clock)
574+ self.addCleanup(self.bar.cleanup)
575+ self.timeout_calls = []
576+ original_timeout = self.bar._timeout
577+
578+ def fake_timeout(result):
579+ """A fake _timeout method."""
580+ self.timeout_calls.append(self.bar.progress)
581+ original_timeout(result)
582+
583+ self.patch(self.bar, "_timeout", fake_timeout)
584+
585+ def test_launcher_typeerror_nonfatal(self):
586+ """Test that Launcher raising TypeError is not fatal."""
587+ def raise_typeerror(*args, **kwargs):
588+ raise TypeError
589+
590+ self.patch(aggregator, "UbuntuOneLauncher", raise_typeerror)
591+ aggregator.ProgressBar(clock=self.clock)
592+
593+ def test_shown_when_progress_made(self):
594+ """The progress bar is shown when progress is made."""
595+ self.bar.set_progress(0.5)
596+ self.assertTrue(self.bar.visible)
597+ self.assertTrue(self.bar.launcher.progress_visible)
598+
599+ def test_progress_made_updates_counter(self):
600+ """Progress made updates the counter."""
601+ self.bar.set_progress(0.5)
602+ self.assertEqual(self.bar.progress, 0.5)
603+
604+ def test_no_timer_set_initially(self):
605+ """There's no timer set initially."""
606+ self.assertEqual(self.bar.timer, None)
607+
608+ def test_progress_made_sets_timer(self):
609+ """Progress made sets up a timer."""
610+ self.bar.set_progress(0.5)
611+ self.assertNotEqual(self.bar.timer, None)
612+
613+ def test_cleanup_resets_timer(self):
614+ """The cleanup method resets the timer."""
615+ self.bar.set_progress(0.5)
616+ self.bar.cleanup()
617+ self.assertEqual(self.bar.timer, None)
618+
619+ def test_progress_made_not_updated_initially(self):
620+ """Progress made is not updated initially."""
621+ self.bar.set_progress(0.5)
622+ self.assertEqual(0, len(self.timeout_calls))
623+ self.assertEqual(0.0, self.bar.launcher.progress)
624+
625+ def test_progress_made_updated_after_a_delay(self):
626+ """The progressbar is updated after a delay."""
627+ self.bar.set_progress(0.5)
628+ self.clock.advance(aggregator.ProgressBar.updates_delay)
629+ self.assertIn(0.5, self.timeout_calls)
630+ self.assertEqual(0.5, self.bar.launcher.progress)
631+
632+ def test_progress_updates_are_aggregated(self):
633+ """The progressbar is updated after a delay."""
634+ self.bar.set_progress(0.5)
635+ self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
636+ self.bar.set_progress(0.6)
637+ self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
638+ self.assertEqual(1, len(self.timeout_calls))
639+
640+ def test_progress_updates_are_continuous(self):
641+ """The progressbar updates are continuous."""
642+ self.bar.set_progress(0.5)
643+ self.clock.advance(aggregator.ProgressBar.updates_delay)
644+ self.assertEqual(0.5, self.bar.launcher.progress)
645+ self.bar.set_progress(0.6)
646+ self.clock.advance(aggregator.ProgressBar.updates_delay)
647+ self.assertEqual(0.6, self.bar.launcher.progress)
648+ self.assertEqual(2, len(self.timeout_calls))
649+
650+ def test_hidden_when_completed(self):
651+ """The progressbar is hidden when everything completes."""
652+ self.bar.set_progress(0.5)
653+ self.bar.completed()
654+ self.assertFalse(self.bar.visible)
655+ self.assertFalse(self.bar.launcher.progress_visible)
656+
657+ @defer.inlineCallbacks
658+ def test_progress_made_inhibits_logout_suspend(self):
659+ """Suspend and logout are inhibited when the progressbar is shown."""
660+ self.bar.set_progress(0.5)
661+ expected = aggregator.session.INHIBIT_LOGOUT_SUSPEND
662+ inhibitor = yield self.bar.inhibitor_defer
663+ self.assertEqual(inhibitor.flags, expected)
664+
665+ @defer.inlineCallbacks
666+ def test_completed_uninhibits_logout_suspend(self):
667+ """Suspend and logout are uninhibited when all has completed."""
668+ self.bar.set_progress(0.5)
669+ d = self.bar.inhibitor_defer
670+ self.bar.completed()
671+ inhibitor = yield d
672+ self.assertEqual(inhibitor.flags, 0)
673+
674+
675+class FakeDelayedBuffer(object):
676+ """Appends all status pushed into a list."""
677+ timer_reset = False
678+ processed = False
679+
680+ def __init__(self, *args, **kwargs):
681+ """Initialize this instance."""
682+ self.events = []
683+
684+ def push_event(self, event):
685+ """Push an event into this buffer."""
686+ self.events.append(event)
687+
688+ def reset_threshold_timer(self):
689+ """The status has changed."""
690+ self.timer_reset = True
691+
692+ def process_accumulated(self):
693+ """Process accumulated events."""
694+ self.processed = True
695+
696+
697+class FakeCommand(object):
698+ """A fake command."""
699+
700+ def __init__(self, path=''):
701+ self.path = path
702+ self.share_id = path
703+ self.node_id = path
704+ self.deflated_size = 10000
705+
706+
707+class FakeVolumeManager(object):
708+ """A fake vm."""
709+
710+ def __init__(self):
711+ """Initialize this instance."""
712+ self.volumes = {}
713+ self.root = None
714+
715+ def get_volume(self, volume_id):
716+ """Return a volume given its id."""
717+ return self.volumes[volume_id]
718+
719+
720+class FakeAggregator(object):
721+ """A fake aggregator object."""
722+
723+ def __init__(self, clock):
724+ """Initialize this fake instance."""
725+ self.queued_commands = set()
726+ self.notification_switch = aggregator.NotificationSwitch()
727+ self.connected = False
728+ self.clock = PatchedClock()
729+ self.files_uploading = []
730+ self.files_downloading = []
731+ self.progress_events = []
732+
733+ def queue_done(self):
734+ """The queue completed all operations."""
735+ self.queued_commands.clear()
736+
737+ def get_notification(self):
738+ """Create a new toggleable notification object."""
739+ return self.notification_switch.get_notification()
740+
741+ def download_started(self, command):
742+ """A download just started."""
743+ self.files_downloading.append(command)
744+ self.queued_commands.add(command)
745+
746+ def download_finished(self, command):
747+ """A download just finished."""
748+ if command in self.files_downloading:
749+ self.files_downloading.remove(command)
750+ self.queued_commands.discard(command)
751+
752+ def upload_started(self, command):
753+ """An upload just started."""
754+ self.files_uploading.append(command)
755+ self.queued_commands.add(command)
756+
757+ def upload_finished(self, command):
758+ """An upload just finished."""
759+ if command in self.files_uploading:
760+ self.files_uploading.remove(command)
761+ self.queued_commands.discard(command)
762+
763+ def progress_made(self, share_id, node_id, n_bytes, deflated_size):
764+ """Progress made on up- or download."""
765+ self.progress_events.append(
766+ (share_id, node_id, n_bytes, deflated_size))
767+
768+ def connection_made(self):
769+ """The client made the connection to the server."""
770+ self.connected = True
771+
772+ def connection_lost(self):
773+ """The client lost the connection to the server."""
774+ self.connected = False
775+
776+
777+class StatusFrontendTestCase(BaseTwistedTestCase):
778+ """Test the status frontend."""
779+
780+ @defer.inlineCallbacks
781+ def setUp(self):
782+ """Initialize this test instance."""
783+ yield super(StatusFrontendTestCase, self).setUp()
784+ self.patch(aggregator, "StatusAggregator", FakeAggregator)
785+ self.patch(aggregator, "ToggleableNotification",
786+ FakeNotificationSingleton())
787+ self.patch(aggregator, "Messaging", FakeMessaging)
788+ self.fakefsm = None
789+ self.fakevm = FakeVolumeManager()
790+ self.status_frontend = aggregator.StatusFrontend()
791+ self.listener = status_listener.StatusListener(self.fakefsm,
792+ self.fakevm,
793+ self.status_frontend)
794+
795+ def test_file_published(self):
796+ """A file published event is processed."""
797+ share_id = "fake share id"
798+ node_id = "fake node id"
799+ is_public = True
800+ public_url = "http://fake_public/url"
801+ self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
802+ is_public, public_url)
803+ self.assertEqual(
804+ 1, len(self.status_frontend.notification.notifications_shown))
805+
806+ def test_file_unpublished(self):
807+ """A file unpublished event is processed."""
808+ share_id = "fake share id"
809+ node_id = "fake node id"
810+ is_public = False
811+ public_url = None # SD sends None when unpublishing
812+
813+ self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
814+ is_public, public_url)
815+ self.assertEqual(
816+ 1, len(self.status_frontend.notification.notifications_shown))
817+
818+ def test_download_started(self):
819+ """A download was added to the queue."""
820+ self.patch(status_listener.action_queue, "Download", FakeCommand)
821+ fake_command = FakeCommand()
822+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
823+ qc = self.status_frontend.aggregator.queued_commands
824+ self.assertIn(fake_command, qc)
825+
826+ def test_download_started_with_no_deflated_size(self):
827+ """A download of unknown size was added to the queue."""
828+ self.patch(status_listener.action_queue, "Download", FakeCommand)
829+ fake_command = FakeCommand()
830+ fake_command.deflated_size = None
831+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
832+ qc = self.status_frontend.aggregator.queued_commands
833+ self.assertIn(fake_command, qc)
834+
835+ def test_download_finished(self):
836+ """A download was removed from the queue."""
837+ self.patch(status_listener.action_queue, "Download", FakeCommand)
838+ fake_command = FakeCommand()
839+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
840+ self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
841+ qc = self.status_frontend.aggregator.queued_commands
842+ self.assertNotIn(fake_command, qc)
843+
844+ def test_upload_started(self):
845+ """An upload was added to the queue."""
846+ self.patch(status_listener.action_queue, "Upload", FakeCommand)
847+ fake_command = FakeCommand()
848+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
849+ qc = self.status_frontend.aggregator.queued_commands
850+ self.assertIn(fake_command, qc)
851+
852+ def test_upload_started_with_no_deflated_size(self):
853+ """An upload of unknown size was added to the queue."""
854+ self.patch(status_listener.action_queue, "Upload", FakeCommand)
855+ fake_command = FakeCommand()
856+ fake_command.deflated_size = None
857+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
858+ qc = self.status_frontend.aggregator.queued_commands
859+ self.assertIn(fake_command, qc)
860+
861+ def test_upload_finished(self):
862+ """An upload was removed from the queue."""
863+ self.patch(status_listener.action_queue, "Upload", FakeCommand)
864+ fake_command = FakeCommand()
865+ self.listener.handle_SYS_QUEUE_ADDED(fake_command)
866+ self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
867+ qc = self.status_frontend.aggregator.queued_commands
868+ self.assertNotIn(fake_command, qc)
869+
870+ def test_progress_made_on_upload(self):
871+ """Progress was made on an uploading file."""
872+ share_id = 'fake_share'
873+ node_id = 'fake_node'
874+ n_bytes_written = 100
875+ deflated_size = 10000
876+ self.listener.handle_AQ_UPLOAD_FILE_PROGRESS(
877+ share_id, node_id, n_bytes_written, deflated_size)
878+ pe = self.status_frontend.aggregator.progress_events
879+ self.assertEqual(
880+ [(share_id, node_id, n_bytes_written, deflated_size)], pe,
881+ "progress_made was not called (exactly once) on aggregator.")
882+
883+ def test_progress_made_on_download(self):
884+ """Progress was made on an downloading file."""
885+ share_id = 'fake_share'
886+ node_id = 'fake_node'
887+ n_bytes_written = 200
888+ deflated_size = 20000
889+ self.listener.handle_AQ_DOWNLOAD_FILE_PROGRESS(
890+ share_id, node_id, n_bytes_written, deflated_size)
891+ pe = self.status_frontend.aggregator.progress_events
892+ self.assertEqual(
893+ [(share_id, node_id, n_bytes_written, deflated_size)], pe,
894+ "progress_made was not called (exactly once) on aggregator.")
895+
896+ def test_queue_done(self):
897+ """The queue is empty."""
898+ fake_command = FakeCommand()
899+ qc = self.status_frontend.aggregator.queued_commands
900+ qc.add(fake_command)
901+ self.listener.handle_SYS_QUEUE_DONE()
902+ self.assertEqual(0, len(qc))
903+
904+ def test_new_share_available(self):
905+ """A new share is available for subscription."""
906+ SHARE_ID = "fake share id"
907+ FAKE_SENDER = 'Mom'
908+ share = Share(volume_id=SHARE_ID, other_visible_name=FAKE_SENDER)
909+ self.fakevm.volumes[SHARE_ID] = share
910+ self.listener.handle_VM_SHARE_CREATED(SHARE_ID)
911+ self.assertEqual(
912+ 1, len(self.status_frontend.notification.notifications_shown))
913+ msg = self.status_frontend.messaging.messages_shown[FAKE_SENDER]
914+ # msg did not receive a time argument
915+ self.assertEqual(None, msg[1])
916+ # msg did not receive a count argument
917+ self.assertEqual(None, msg[2])
918+
919+ def test_already_subscribed_new_udf_available(self):
920+ """A new udf that was already subscribed."""
921+ udf = UDF()
922+ udf.subscribed = True
923+ self.listener.handle_VM_UDF_CREATED(udf)
924+ self.assertEqual(
925+ 0, len(self.status_frontend.notification.notifications_shown))
926+ self.assertEqual(
927+ 0, len(self.status_frontend.messaging.messages_shown))
928+ self.assertEqual(
929+ 0, len(self.status_frontend.messaging.messages_updated))
930+
931+ def test_new_udf_available(self):
932+ """A new udf is available for subscription."""
933+ udf = UDF()
934+ self.listener.handle_VM_UDF_CREATED(udf)
935+ self.assertEqual(
936+ 1, len(self.status_frontend.notification.notifications_shown))
937+ self.assertEqual(
938+ 0, len(self.status_frontend.messaging.messages_shown))
939+ self.assertEqual(
940+ 0, len(self.status_frontend.messaging.messages_updated))
941+ self.assertEqual(0, len(self.status_frontend.messaging.callbacks))
942+
943+ def test_two_new_udfs_available(self):
944+ """A new udf is available for subscription."""
945+ udf1 = UDF()
946+ self.listener.handle_VM_UDF_CREATED(udf1)
947+ udf2 = UDF()
948+ self.listener.handle_VM_UDF_CREATED(udf2)
949+ self.assertEqual(
950+ 2, len(self.status_frontend.notification.notifications_shown))
951+ self.assertEqual(
952+ 0, len(self.status_frontend.messaging.messages_shown))
953+ self.assertEqual(
954+ 0, len(self.status_frontend.messaging.messages_updated))
955+
956+ def test_server_connection_lost(self):
957+ """The client connected to the server."""
958+ self.status_frontend.aggregator.connected = True
959+ self.listener.handle_SYS_CONNECTION_LOST()
960+ self.assertEqual(
961+ 0, len(self.status_frontend.notification.notifications_shown))
962+ self.assertFalse(self.status_frontend.aggregator.connected)
963+
964+ def test_server_connection_made(self):
965+ """The client connected to the server."""
966+ self.status_frontend.aggregator.connected = False
967+ self.listener.handle_SYS_CONNECTION_MADE()
968+ self.assertEqual(
969+ 0, len(self.status_frontend.notification.notifications_shown))
970+ self.assertTrue(self.status_frontend.aggregator.connected)
971+
972+ def test_set_show_all_notifications(self):
973+ """Test the set_show_all_notifications method."""
974+ self.status_frontend.set_show_all_notifications(False)
975+ self.assertFalse(self.status_frontend.aggregator.
976+ notification_switch.enabled)
977+
978+ def test_udf_quota_exceeded(self): # pylint: disable=R0201
979+ """Quota exceeded in udf."""
980+ mocker = Mocker()
981+ launcher = mocker.replace(
982+ "ubuntuone.platform.launcher.UbuntuOneLauncher")
983+ launcher()
984+ mock_launcher = mocker.mock()
985+ mocker.result(mock_launcher)
986+ mock_launcher.set_urgent()
987+ mocker.replay()
988+ UDF_ID = 'fake udf id'
989+ udf = UDF(volume_id=UDF_ID)
990+ self.fakevm.volumes[UDF_ID] = udf
991+ self.listener.handle_SYS_QUOTA_EXCEEDED(
992+ volume_id=UDF_ID, free_bytes=0)
993+ self.assertEqual(
994+ 0, len(self.status_frontend.notification.notifications_shown))
995+ mocker.restore()
996+ mocker.verify()
997+
998+ def test_root_quota_exceeded(self): # pylint: disable=R0201
999+ """Quota exceeded in root."""
1000+ mocker = Mocker()
1001+ launcher = mocker.replace(
1002+ "ubuntuone.platform.launcher.UbuntuOneLauncher")
1003+ launcher()
1004+ mock_launcher = mocker.mock()
1005+ mocker.result(mock_launcher)
1006+ mock_launcher.set_urgent()
1007+ mocker.replay()
1008+ ROOT_ID = 'fake root id'
1009+ root = Root(volume_id=ROOT_ID)
1010+ self.fakevm.volumes[ROOT_ID] = root
1011+ self.fakevm.root = root
1012+ self.listener.handle_SYS_QUOTA_EXCEEDED(
1013+ volume_id=ROOT_ID, free_bytes=0)
1014+ self.assertEqual(
1015+ 0, len(self.status_frontend.notification.notifications_shown))
1016+ mocker.restore()
1017+ mocker.verify()
1018+
1019+ def test_share_quota_exceeded(self):
1020+ """Quota exceeded in share."""
1021+ mocker = Mocker()
1022+ launcher = mocker.replace(
1023+ "ubuntuone.platform.launcher.UbuntuOneLauncher")
1024+ launcher()
1025+ mock_launcher = mocker.mock()
1026+ mocker.result(mock_launcher)
1027+ mock_launcher.set_urgent()
1028+ launcher()
1029+ mock_launcher = mocker.mock()
1030+ mocker.result(mock_launcher)
1031+ mock_launcher.set_urgent()
1032+ mocker.replay()
1033+ SHARE_ID = 'fake share id'
1034+ BYTES = 0
1035+ share = Share(volume_id=SHARE_ID)
1036+ self.fakevm.volumes[SHARE_ID] = share
1037+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1038+ self.assertEqual(
1039+ 1, len(self.status_frontend.notification.notifications_shown))
1040+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1041+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1042+ self.assertEqual(
1043+ 1, len(self.status_frontend.notification.notifications_shown))
1044+ self.status_frontend.aggregator.clock.advance(aggregator.ONE_DAY + 1)
1045+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1046+ self.assertEqual(
1047+ 2, len(self.status_frontend.notification.notifications_shown))
1048+ mocker.restore()
1049+ mocker.verify()
1050+
1051+
1052+class StatusEventTestCase(TestCase):
1053+ """Test the status event class and children."""
1054+
1055+ CLASS = aggregator.StatusEvent
1056+ CLASS_KWARGS = {}
1057+ status = None
1058+
1059+ @defer.inlineCallbacks
1060+ def setUp(self):
1061+ """Initialize this test instance."""
1062+ yield super(StatusEventTestCase, self).setUp()
1063+ if type(self) == StatusEventTestCase:
1064+ self.assertRaises(AssertionError, self.CLASS, **self.CLASS_KWARGS)
1065+ else:
1066+ self.status = self.CLASS(**self.CLASS_KWARGS)
1067+
1068+ def test_one_message_defined(self):
1069+ """The singular message is defined as MESSAGE_ONE."""
1070+ if self.status:
1071+ self.assertNotEqual(None, self.CLASS.MESSAGE_ONE)
1072+
1073+ def test_one_message_built_correctly(self):
1074+ """The message returned by one() is returned ok."""
1075+ if self.status:
1076+ self.assertEqual(self.status.one(), self.CLASS.MESSAGE_ONE)
1077+
1078+
1079+class FilePublishingStatusTestCase(StatusEventTestCase):
1080+ """Test the file publishing status class."""
1081+
1082+ CLASS = aggregator.FilePublishingStatus
1083+ CLASS_KWARGS = {"new_public_url": "http://fake_public/url"}
1084+
1085+ def test_one_message_built_correctly(self):
1086+ """The message returned by one() should include the url."""
1087+ expected = self.CLASS.MESSAGE_ONE % self.status.kwargs
1088+ self.assertEqual(self.status.one(), expected)
1089+
1090+
1091+class FileUnpublishingStatusTestCase(StatusEventTestCase):
1092+ """Test the file unpublishing status class."""
1093+
1094+ CLASS = aggregator.FileUnpublishingStatus
1095+ CLASS_KWARGS = {"old_public_url": None}
1096+
1097+
1098+class ShareAvailableEventTestCase(StatusEventTestCase):
1099+ """Test the folder available status class with a Share."""
1100+
1101+ FOLDER_NAME = "folder name"
1102+ OTHER_USER_NAME = "person name"
1103+ SAMPLE_SHARE = Share(accepted=False, name=FOLDER_NAME,
1104+ other_visible_name=OTHER_USER_NAME)
1105+ CLASS = aggregator.ShareAvailableStatus
1106+ CLASS_KWARGS = {"share": SAMPLE_SHARE}
1107+
1108+ def test_one_message_built_correctly(self):
1109+ """one() must return the folder name and user name."""
1110+ format_args = {
1111+ "folder_name": self.FOLDER_NAME,
1112+ "other_user_name": self.OTHER_USER_NAME,
1113+ }
1114+ expected = self.CLASS.MESSAGE_ONE % format_args
1115+ self.assertEqual(self.status.one(), expected)
1116+
1117+
1118+class UDFAvailableEventTestCase(StatusEventTestCase):
1119+ """Test the folder available status class with a UDF."""
1120+
1121+ FOLDER_NAME = "folder name"
1122+ SAMPLE_UDF = UDF(subscribed=False, suggested_path=FOLDER_NAME)
1123+ CLASS = aggregator.UDFAvailableStatus
1124+ CLASS_KWARGS = {'udf': SAMPLE_UDF}
1125+
1126+ def test_one_message_built_correctly(self):
1127+ """one() must return the folder name."""
1128+ format_args = {"folder_name": self.FOLDER_NAME}
1129+ expected = self.CLASS.MESSAGE_ONE % format_args
1130+ self.assertEqual(self.status.one(), expected)
1131+
1132+
1133+class ConnectionLostEventTestCase(StatusEventTestCase):
1134+ """Test the event when the connection is lost."""
1135+
1136+ CLASS = aggregator.ConnectionLostStatus
1137+
1138+ def test_many_message_built_correctly(self):
1139+ """The message returned by many() is returned ok."""
1140+ if self.status:
1141+ count = 99
1142+ test_events = [FakeStatus(88)] * count + [self.CLASS()]
1143+ expected = self.CLASS.MESSAGE_ONE
1144+ self.assertEqual(self.status.many(test_events), expected)
1145+
1146+
1147+class ConnectionMadeEventTestCase(ConnectionLostEventTestCase):
1148+ """Test the event when the connection is made."""
1149+
1150+ CLASS = aggregator.ConnectionMadeStatus
1151+
1152+
1153+class FakeStatus(aggregator.StatusEvent):
1154+ """A fake status to test weight comparisons."""
1155+
1156+ def __init__(self, weight):
1157+ """Initialize with the fake weight."""
1158+ super(FakeStatus, self).__init__()
1159+ self.WEIGHT = weight
1160+
1161+
1162+class FakeFileDiscoveryBubble(object):
1163+ """A fake FileDiscoveryBubble."""
1164+
1165+ count = 0
1166+
1167+ def __init__(self, status_aggregator, clock=None):
1168+ """Initialize this instance."""
1169+ self.status_aggregator = status_aggregator
1170+
1171+ def new_file_found(self):
1172+ """New files were found."""
1173+ self.count += 1
1174+
1175+ def cleanup(self):
1176+ """Cleanup this instance."""
1177+
1178+ def connection_made(self):
1179+ """Connection made."""
1180+
1181+ def connection_lost(self):
1182+ """Connection lost."""
1183+
1184+
1185+class FakeFinalBubble(object):
1186+ """A fake FinalStatusBubble."""
1187+
1188+ shown = False
1189+
1190+ def __init__(self, status_aggregator):
1191+ """Initialize this fake instance."""
1192+ self.status_aggregator = status_aggregator
1193+
1194+ def cleanup(self):
1195+ """Cleanup this instance."""
1196+
1197+ def show(self):
1198+ """Show this bubble."""
1199+ self.shown = True
1200+
1201+
1202+class StatusAggregatorTestCase(TestCase):
1203+ """Test the backend of the status aggregator."""
1204+
1205+ @defer.inlineCallbacks
1206+ def setUp(self):
1207+ """Initialize this test instance."""
1208+ yield super(StatusAggregatorTestCase, self).setUp()
1209+ self.patch(aggregator, "FileDiscoveryBubble",
1210+ FakeFileDiscoveryBubble)
1211+ self.patch(aggregator, "FinalStatusBubble",
1212+ FakeFinalBubble)
1213+ self.patch(aggregator, "ToggleableNotification",
1214+ FakeNotificationSingleton())
1215+ self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1216+ self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1217+ clock = PatchedClock()
1218+ self.status_frontend = aggregator.StatusFrontend(clock=clock)
1219+ self.aggregator = self.status_frontend.aggregator
1220+ self.fake_bubble = self.aggregator.file_discovery_bubble
1221+
1222+ self.handler = MementoHandler()
1223+ self.handler.setLevel(logging.DEBUG)
1224+ aggregator.logger.addHandler(self.handler)
1225+ aggregator.logger.setLevel(logging.DEBUG)
1226+ self.addCleanup(aggregator.logger.removeHandler, self.handler)
1227+ self.addCleanup(self.aggregator.progress_bar.cleanup)
1228+
1229+ def assertStatusReset(self):
1230+ """Assert that the status is at zero."""
1231+ self.assertEqual(0, self.aggregator.download_done)
1232+ self.assertEqual(0, self.aggregator.upload_done)
1233+ self.assertEqual(0, len(self.aggregator.files_uploading))
1234+ self.assertEqual(0, len(self.aggregator.files_downloading))
1235+ self.assertEqual({}, self.aggregator.progress)
1236+ self.assertEqual({}, self.aggregator.to_do)
1237+ self.assertIdentical(None, self.aggregator.queue_done_timer)
1238+
1239+ def assertMiscCommandQueued(self, fc):
1240+ """Assert that some command was queued."""
1241+ self.assertEqual(len(self.aggregator.to_do), 1)
1242+ message = "queueing command (total: 1): %s" % fc.__class__.__name__
1243+ self.assertEqual(fc.deflated_size, sum(self.aggregator.to_do.values()))
1244+ self.assertTrue(self.handler.check_debug(message))
1245+ self.assertTrue(self.aggregator.progress_bar.visible)
1246+
1247+ def assertMiscCommandUnqueued(self, fc):
1248+ """Assert that some command was unqueued."""
1249+ self.assertEqual(
1250+ 1, self.aggregator.download_done + self.aggregator.upload_done)
1251+ message = "unqueueing command: %s" % fc.__class__.__name__
1252+ self.assertTrue(self.handler.check_debug(message))
1253+
1254+ def test_counters_start_at_zero(self):
1255+ """Test that the counters start at zero."""
1256+ self.assertStatusReset()
1257+
1258+ def test_file_download_started(self):
1259+ """Test that a file has started download."""
1260+ fc = FakeCommand(path='testfile.txt')
1261+ self.assertEqual('', self.aggregator.downloading_filename)
1262+ self.status_frontend.download_started(fc)
1263+ self.assertEqual(1, len(self.aggregator.files_downloading))
1264+ self.assertEqual('testfile.txt', self.aggregator.downloading_filename)
1265+ self.assertMiscCommandQueued(fc)
1266+ self.assertEqual(1, self.fake_bubble.count)
1267+ self.assertEqual(
1268+ {(fc.share_id, fc.node_id): (fc.deflated_size)},
1269+ self.aggregator.to_do)
1270+
1271+ def test_file_download_finished(self):
1272+ """Test that a file has finished downloading."""
1273+ fc = FakeCommand()
1274+ self.status_frontend.download_started(fc)
1275+ self.status_frontend.download_finished(fc)
1276+ self.assertEqual(self.aggregator.download_done, 1)
1277+ self.assertMiscCommandUnqueued(fc)
1278+ self.assertEqual(
1279+ {(fc.share_id, fc.node_id): (fc.deflated_size)},
1280+ self.aggregator.progress)
1281+
1282+ def test_file_upload_started(self):
1283+ """Test that a file has started upload."""
1284+ fc = FakeCommand(path='testfile.txt')
1285+ self.assertEqual('', self.aggregator.uploading_filename)
1286+ self.status_frontend.upload_started(fc)
1287+ self.assertEqual(1, len(self.aggregator.files_uploading))
1288+ self.assertEqual('testfile.txt', self.aggregator.uploading_filename)
1289+ self.assertMiscCommandQueued(fc)
1290+ self.assertEqual(1, self.fake_bubble.count)
1291+ self.assertEqual(
1292+ {(fc.share_id, fc.node_id): (fc.deflated_size)},
1293+ self.aggregator.to_do)
1294+
1295+ def test_file_upload_finished(self):
1296+ """Test that a file has finished uploading."""
1297+ fc = FakeCommand()
1298+ self.status_frontend.upload_started(fc)
1299+ self.status_frontend.upload_finished(fc)
1300+ self.assertEqual(self.aggregator.upload_done, 1)
1301+ self.assertMiscCommandUnqueued(fc)
1302+ self.assertEqual(
1303+ {(fc.share_id, fc.node_id): (fc.deflated_size)},
1304+ self.aggregator.progress)
1305+
1306+ def test_progress_made(self):
1307+ """Progress on up and downloads is tracked."""
1308+ share_id = 'fake_share'
1309+ node_id = 'fake_node'
1310+ n_bytes_written = 200
1311+ deflated_size = 100000
1312+ self.aggregator.progress_made(
1313+ share_id, node_id, n_bytes_written, deflated_size)
1314+ self.assertEqual(
1315+ {(share_id, node_id): (n_bytes_written)},
1316+ self.aggregator.progress)
1317+
1318+ def test_get_discovery_message(self):
1319+ """Test the message that's shown on the discovery bubble."""
1320+ uploading = 10
1321+ downloading = 8
1322+ filename = 'upfile0.ext'
1323+ filename2 = 'downfile0.ext'
1324+ self.aggregator.files_uploading.extend([
1325+ FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
1326+ self.aggregator.uploading_filename = filename
1327+ self.aggregator.files_downloading.extend([
1328+ FakeCommand(path='downfile%d.ext' % n) for n in
1329+ range(downloading)])
1330+ self.aggregator.downloading_filename = filename2
1331+ expected = (
1332+ aggregator.files_being_uploaded(filename, uploading) + "\n" +
1333+ aggregator.files_being_downloaded(filename2, downloading))
1334+ result = self.aggregator.get_discovery_message()
1335+ self.assertEqual(expected, result)
1336+
1337+ def test_get_final_status_message(self):
1338+ """The final status message."""
1339+ done = (5, 10)
1340+ self.aggregator.uploading_filename = FILENAME
1341+ self.aggregator.downloading_filename = FILENAME2
1342+ self.aggregator.upload_done, self.aggregator.download_done = done
1343+
1344+ expected = (
1345+ aggregator.FINAL_COMPLETED + "\n" +
1346+ aggregator.files_were_uploaded(
1347+ FILENAME, self.aggregator.upload_done) + "\n" +
1348+ aggregator.files_were_downloaded(
1349+ FILENAME2, self.aggregator.download_done))
1350+
1351+ result = self.aggregator.get_final_status_message()
1352+ self.assertEqual(expected, result)
1353+
1354+ def test_get_final_status_message_no_uploads(self):
1355+ """The final status message when there were no uploads."""
1356+ done = (0, 12)
1357+ self.aggregator.upload_done, self.aggregator.download_done = done
1358+ self.aggregator.downloading_filename = FILENAME2
1359+
1360+ expected = (
1361+ aggregator.FINAL_COMPLETED + "\n" +
1362+ aggregator.files_were_downloaded(
1363+ FILENAME2, self.aggregator.download_done))
1364+
1365+ result = self.aggregator.get_final_status_message()
1366+ self.assertEqual(expected, result)
1367+
1368+ def test_get_final_status_message_no_downloads(self):
1369+ """The final status message when there were no downloads."""
1370+ done = (8, 0)
1371+ self.aggregator.upload_done, self.aggregator.download_done = done
1372+ self.aggregator.uploading_filename = FILENAME
1373+
1374+ expected = (
1375+ aggregator.FINAL_COMPLETED + "\n" +
1376+ aggregator.files_were_uploaded(
1377+ FILENAME, self.aggregator.upload_done))
1378+
1379+ result = self.aggregator.get_final_status_message()
1380+ self.assertEqual(expected, result)
1381+
1382+ def test_queue_done_shows_bubble_when_downloads_happened(self):
1383+ """On queue done, show final bubble if downloads happened."""
1384+ fc = FakeCommand()
1385+ self.status_frontend.download_started(fc)
1386+ self.status_frontend.download_finished(fc)
1387+ old_final_bubble = self.aggregator.final_status_bubble
1388+ self.aggregator.queue_done()
1389+ self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1390+ self.assertTrue(old_final_bubble.shown)
1391+
1392+ def test_queue_done_shows_bubble_when_uploads_happened(self):
1393+ """On queue done, show final bubble if uploads happened."""
1394+ fc = FakeCommand()
1395+ self.status_frontend.upload_started(fc)
1396+ self.status_frontend.upload_finished(fc)
1397+ old_final_bubble = self.aggregator.final_status_bubble
1398+ self.aggregator.queue_done()
1399+ self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1400+ self.assertTrue(old_final_bubble.shown)
1401+
1402+ def test_queue_done_shows_bubble_only_after_delay(self):
1403+ """On queue_done, show final bubble only after a delay."""
1404+ fc = FakeCommand()
1405+ self.status_frontend.upload_started(fc)
1406+ self.status_frontend.upload_finished(fc)
1407+ old_final_bubble = self.aggregator.final_status_bubble
1408+ self.aggregator.queue_done()
1409+ self.assertFalse(old_final_bubble.shown)
1410+ self.aggregator.clock.advance(self.aggregator.finished_delay - 1)
1411+ self.assertFalse(old_final_bubble.shown)
1412+ self.aggregator.queue_done()
1413+ self.assertFalse(old_final_bubble.shown)
1414+ self.aggregator.clock.advance(2)
1415+ self.assertFalse(old_final_bubble.shown)
1416+ self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1417+ self.assertTrue(old_final_bubble.shown)
1418+
1419+ def test_queue_done_does_not_show_bubble_when_no_transfers_happened(self):
1420+ """On queue done, don't show final bubble if no transfers happened."""
1421+ fc = FakeCommand()
1422+ self.status_frontend.upload_started(fc)
1423+ old_final_bubble = self.aggregator.final_status_bubble
1424+ self.aggregator.queue_done()
1425+ self.assertFalse(old_final_bubble.shown)
1426+
1427+ def test_queue_done_resets_status_and_hides_progressbar(self):
1428+ """On queue done, reset counters and hide progressbar."""
1429+ fc = FakeCommand()
1430+ self.status_frontend.upload_started(fc)
1431+ self.aggregator.queue_done()
1432+ self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1433+ self.assertStatusReset()
1434+ self.assertEqual(0.0, self.aggregator.progress_bar.progress)
1435+ self.assertFalse(self.aggregator.progress_bar.visible)
1436+
1437+ def test_download_started_cancels_timer(self):
1438+ """Starting a download cancels the queue_done timer."""
1439+ fc = FakeCommand()
1440+ self.status_frontend.download_started(fc)
1441+ self.aggregator.clock.advance(self.aggregator.finished_delay)
1442+ self.status_frontend.download_finished(fc)
1443+ self.aggregator.queue_done()
1444+ self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1445+ fc2 = FakeCommand()
1446+ self.status_frontend.download_started(fc2)
1447+ self.assertIdentical(self.aggregator.queue_done_timer, None)
1448+ self.aggregator.clock.advance(self.aggregator.finished_delay)
1449+ self.status_frontend.download_finished(fc2)
1450+
1451+ def test_upload_started_cancels_timer(self):
1452+ """Starting an upload cancels the queue_done timer."""
1453+ fc = FakeCommand()
1454+ self.status_frontend.upload_started(fc)
1455+ self.aggregator.clock.advance(self.aggregator.finished_delay)
1456+ self.status_frontend.upload_finished(fc)
1457+ self.aggregator.queue_done()
1458+ self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1459+ fc2 = FakeCommand()
1460+ self.status_frontend.upload_started(fc2)
1461+ self.assertIdentical(self.aggregator.queue_done_timer, None)
1462+ self.aggregator.clock.advance(self.aggregator.finished_delay)
1463+ self.status_frontend.upload_finished(fc2)
1464+
1465+
1466+class StatusGrouperTestCase(TestCase):
1467+ """Tests for the group_statuses function."""
1468+
1469+ def test_group_status(self):
1470+ """The status grouper sorts and groups by weight."""
1471+ status99 = FakeStatus(99)
1472+ statuses = [
1473+ status99,
1474+ status99,
1475+ FakeStatus(12),
1476+ FakeStatus(1)]
1477+
1478+ result = [list(k) for _, k in aggregator.group_statuses(statuses)]
1479+ expected = [
1480+ [statuses[3]],
1481+ [statuses[2]],
1482+ [status99, status99]]
1483+
1484+ self.assertEqual(result, expected)
1485+
1486+
1487+class HundredFeetTestCase(TestCase):
1488+ """Try to make all parts work together."""
1489+
1490+ def test_all_together_now(self):
1491+ """Make all parts work together."""
1492+ self.patch(aggregator, "ToggleableNotification",
1493+ FakeNotificationSingleton())
1494+ self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1495+ self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1496+ clock = PatchedClock()
1497+ upload = FakeCommand(path='upload.foo')
1498+ sf = aggregator.StatusFrontend(clock=clock)
1499+ sf.server_connection_made()
1500+ sf.set_show_all_notifications(True)
1501+
1502+ # the progress bar is not visible yet
1503+ self.assertFalse(sf.aggregator.progress_bar.visible)
1504+ sf.upload_started(upload)
1505+ # the progress bar is now shown
1506+ self.assertTrue(sf.aggregator.progress_bar.visible)
1507+ notifications_shown = (sf.aggregator.file_discovery_bubble.
1508+ notification.notifications_shown)
1509+ # no notifications shown yet
1510+ self.assertEqual(0, len(notifications_shown))
1511+ clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1512+ # files found notification
1513+ self.assertEqual(1, len(notifications_shown))
1514+ download = FakeCommand('download.bar')
1515+ sf.download_started(download)
1516+ self.assertEqual(1, len(notifications_shown))
1517+ # the progress still is zero
1518+ self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1519+ clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1520+ # files count update
1521+ self.assertEqual(2, len(notifications_shown))
1522+ clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1523+ aggregator.FileDiscoveryUpdateState.updates_delay)
1524+ sf.upload_finished(upload)
1525+ sf.download_finished(download)
1526+ # the progress still is now 100%
1527+ self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1528+ sf.queue_done()
1529+ clock.advance(sf.aggregator.finished_delay + 1)
1530+ self.assertEqual(3, len(notifications_shown))
1531+
1532+ def test_all_together_now_off(self):
1533+ """Make all parts work together, but with notifications off."""
1534+ self.patch(aggregator, "ToggleableNotification",
1535+ FakeNotificationSingleton())
1536+ self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1537+ self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1538+ clock = PatchedClock()
1539+ upload = FakeCommand('upload.foo')
1540+ sf = aggregator.StatusFrontend(clock=clock)
1541+ sf.set_show_all_notifications(False)
1542+
1543+ # the progress bar is not visible yet
1544+ self.assertFalse(sf.aggregator.progress_bar.visible)
1545+ sf.upload_started(upload)
1546+ # the progress bar is now shown
1547+ self.assertTrue(sf.aggregator.progress_bar.visible)
1548+ notifications_shown = (sf.aggregator.file_discovery_bubble.
1549+ notification.notifications_shown)
1550+ # no notifications shown, never
1551+ self.assertEqual(0, len(notifications_shown))
1552+ clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1553+ self.assertEqual(0, len(notifications_shown))
1554+ download = FakeCommand('download.bar')
1555+ sf.download_started(download)
1556+ self.assertEqual(0, len(notifications_shown))
1557+ # the progress still is zero
1558+ self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1559+ clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1560+ self.assertEqual(0, len(notifications_shown))
1561+ clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1562+ aggregator.FileDiscoveryUpdateState.updates_delay)
1563+ sf.upload_finished(upload)
1564+ sf.download_finished(download)
1565+ # the progress still is now 100%
1566+ self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1567+ self.assertEqual(0, len(notifications_shown))
1568+ sf.queue_done()
1569+ self.assertEqual(0, len(notifications_shown))
1570
1571=== added directory '.pc/03_reset_notify_name.patch/ubuntuone'
1572=== added directory '.pc/03_reset_notify_name.patch/ubuntuone/status'
1573=== added file '.pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py'
1574--- .pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py 1970-01-01 00:00:00 +0000
1575+++ .pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py 2011-12-14 21:11:28 +0000
1576@@ -0,0 +1,882 @@
1577+# ubuntuone.status.aggregator
1578+#
1579+# Author: Alejandro J. Cura <alecu@canonical.com>
1580+#
1581+# Copyright 2011 Canonical Ltd.
1582+#
1583+# This program is free software: you can redistribute it and/or modify it
1584+# under the terms of the GNU General Public License version 3, as published
1585+# by the Free Software Foundation.
1586+#
1587+# This program is distributed in the hope that it will be useful, but
1588+# WITHOUT ANY WARRANTY; without even the implied warranties of
1589+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1590+# PURPOSE. See the GNU General Public License for more details.
1591+#
1592+# You should have received a copy of the GNU General Public License along
1593+# with this program. If not, see <http://www.gnu.org/licenses/>.
1594+"""Aggregate status events."""
1595+
1596+import itertools
1597+import operator
1598+import os
1599+
1600+
1601+import gettext
1602+
1603+from twisted.internet import reactor, defer
1604+
1605+from ubuntuone.clientdefs import GETTEXT_PACKAGE
1606+from ubuntuone.status.logger import logger
1607+from ubuntuone.platform import session
1608+from ubuntuone.platform.notification import Notification
1609+from ubuntuone.platform.messaging import Messaging
1610+from ubuntuone.platform.launcher import UbuntuOneLauncher, DummyLauncher
1611+
1612+ONE_DAY = 24 * 60 * 60
1613+Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string)
1614+
1615+UBUNTUONE_TITLE = Q_("Ubuntu One")
1616+NEW_UDFS_SENDER = Q_("New cloud folder(s) available")
1617+FINAL_COMPLETED = Q_("File synchronization completed.")
1618+
1619+PROGRESS_COMPLETED = Q_("%(percentage_completed)d%% completed.")
1620+FILE_SYNC_IN_PROGRESS = Q_("File synchronization in progress")
1621+
1622+SHARE_QUOTA_EXCEEDED = Q_(
1623+ 'There is no available space on the folder:\n"%s" shared by %s')
1624+
1625+
1626+def alert_user():
1627+ """Set the launcher to urgent to alert the user."""
1628+ launcher = UbuntuOneLauncher()
1629+ launcher.set_urgent()
1630+
1631+
1632+def files_being_uploaded(filename, files_uploading):
1633+ """Get the i18n string for files being uploaded."""
1634+ other_files = files_uploading - 1
1635+ if other_files < 1:
1636+ return Q_(
1637+ "'%(filename)s' is being uploaded to your personal cloud.") % {
1638+ 'filename': filename}
1639+ format_args = {
1640+ "filename": filename, "other_files": other_files}
1641+ return gettext.dngettext(
1642+ GETTEXT_PACKAGE,
1643+ "'%(filename)s' and %(other_files)d other file are being "
1644+ "uploaded to your personal cloud.",
1645+ "'%(filename)s' and %(other_files)d other files are being "
1646+ "uploaded to your personal cloud.", other_files) % format_args
1647+
1648+
1649+def files_being_downloaded(filename, files_downloading):
1650+ """Get the i18n string for files being downloaded."""
1651+ other_files = files_downloading - 1
1652+ if other_files < 1:
1653+ return Q_(
1654+ "'%(filename)s' is being downloaded to your computer.") % {
1655+ 'filename': filename}
1656+ format_args = {
1657+ "filename": filename, "other_files": other_files}
1658+ return gettext.dngettext(
1659+ GETTEXT_PACKAGE,
1660+ "'%(filename)s' and %(other_files)d other file are being "
1661+ "downloaded to your computer.",
1662+ "'%(filename)s' and %(other_files)d other files are being "
1663+ "downloaded to your computer.", other_files) % format_args
1664+
1665+
1666+def files_were_uploaded(filename, upload_done):
1667+ """Get the i18n string for files that were uploaded."""
1668+ other_files = upload_done - 1
1669+ if other_files < 1:
1670+ return Q_(
1671+ "'%(filename)s' was uploaded to your personal cloud.") % {
1672+ 'filename': filename}
1673+ format_args = {
1674+ 'filename': filename, 'other_files': other_files}
1675+ return gettext.dngettext(
1676+ GETTEXT_PACKAGE,
1677+ "'%(filename)s' and %(other_files)d other file were uploaded to "
1678+ "your personal cloud.",
1679+ "'%(filename)s' and %(other_files)d other files were uploaded "
1680+ "to your personal cloud.", other_files) % format_args
1681+
1682+
1683+def files_were_downloaded(filename, download_done):
1684+ """Get the i18n string for files that were downloaded."""
1685+ other_files = download_done - 1
1686+ if other_files < 1:
1687+ return Q_(
1688+ "'%(filename)s' was downloaded to your computer.") % {
1689+ 'filename': filename}
1690+ format_args = {
1691+ 'filename': filename, 'other_files': other_files}
1692+ return gettext.dngettext(
1693+ GETTEXT_PACKAGE,
1694+ "'%(filename)s' and %(other_files)d other file were "
1695+ "downloaded to your computer.",
1696+ "'%(filename)s' and %(other_files)d other files were "
1697+ "downloaded to your computer.", other_files) % format_args
1698+
1699+
1700+class ToggleableNotification(object):
1701+ """A controller for notifications that can be turned off."""
1702+
1703+ def __init__(self, notification_switch):
1704+ """Initialize this instance."""
1705+ self.notification_switch = notification_switch
1706+ self.notification = Notification()
1707+
1708+ def send_notification(self, *args):
1709+ """Passthru the notification."""
1710+ if self.notification_switch.enabled:
1711+ return self.notification.send_notification(*args)
1712+
1713+
1714+class NotificationSwitch(object):
1715+ """A switch that turns notifications on and off."""
1716+
1717+ enabled = True
1718+
1719+ def __init__(self):
1720+ self.toggleable_notification = ToggleableNotification(self)
1721+
1722+ def get_notification(self):
1723+ """Return a new notification instance."""
1724+ return self.toggleable_notification
1725+
1726+ def enable_notifications(self):
1727+ """Turn the switch on."""
1728+ self.enabled = True
1729+
1730+ def disable_notifications(self):
1731+ """Turn the switch off."""
1732+ self.enabled = False
1733+
1734+
1735+class StatusEvent(object):
1736+ """An event representing a status change."""
1737+
1738+ MESSAGE_ONE = None # to be defined in child classes
1739+ WEIGHT = 99
1740+ DO_NOT_INSTANCE = "Do not instance this class, only children."""
1741+
1742+ def __init__(self, **kwargs):
1743+ """Initialize this instance."""
1744+ assert type(self) != StatusEvent, self.DO_NOT_INSTANCE
1745+ self.kwargs = kwargs
1746+
1747+ def one(self):
1748+ """A message if this is the only event of this type."""
1749+ return self.MESSAGE_ONE
1750+
1751+
1752+class FilePublishingStatus(StatusEvent):
1753+ """Files that are made public with a url."""
1754+
1755+ MESSAGE_ONE = Q_("A file was just made public at %(new_public_url)s")
1756+
1757+ WEIGHT = 50
1758+
1759+ def one(self):
1760+ """Show the url if only one event of this type."""
1761+ return self.MESSAGE_ONE % self.kwargs
1762+
1763+ def many(self, events):
1764+ """Show the number of files if many event of this type."""
1765+ no_of_files = len(events)
1766+ gettext.dngettext(
1767+ GETTEXT_PACKAGE,
1768+ "%(event_count)d file was just made public.",
1769+ "%(event_count)d files were just made public.",
1770+ no_of_files) % {'event_count': no_of_files}
1771+
1772+
1773+class FileUnpublishingStatus(StatusEvent):
1774+ """Files that have stopped being published."""
1775+
1776+ MESSAGE_ONE = Q_("A file is no longer published")
1777+ WEIGHT = 51
1778+
1779+ def many(self, events):
1780+ """Show the number of files if many event of this type."""
1781+ no_of_files = len(events)
1782+ gettext.dngettext(
1783+ GETTEXT_PACKAGE,
1784+ "%(event_count)d file is no longer published.",
1785+ "%(event_count)d files are no longer published.",
1786+ no_of_files) % {'event_count': no_of_files}
1787+
1788+
1789+class FolderAvailableStatus(StatusEvent):
1790+ """Folders available for subscription."""
1791+
1792+ WEIGHT = 60
1793+
1794+ def many(self, events):
1795+ """Show the number of files if many event of this type."""
1796+ no_of_files = len(events)
1797+ gettext.dngettext(
1798+ GETTEXT_PACKAGE,
1799+ "Found %(event_count)d new cloud folder.",
1800+ "Found %(event_count)d new cloud folders.",
1801+ no_of_files) % {'event_count': no_of_files}
1802+
1803+
1804+class ShareAvailableStatus(FolderAvailableStatus):
1805+ """A Share is available for subscription."""
1806+
1807+ MESSAGE_ONE = Q_("New cloud folder available: '%(folder_name)s' "
1808+ "shared by %(other_user_name)s")
1809+
1810+ def one(self):
1811+ """Show the folder information."""
1812+ volume = self.kwargs["share"]
1813+ format_args = {
1814+ "folder_name": volume.name,
1815+ "other_user_name": volume.other_visible_name,
1816+ }
1817+ return self.MESSAGE_ONE % format_args
1818+
1819+
1820+class UDFAvailableStatus(FolderAvailableStatus):
1821+ """An UDF is available for subscription."""
1822+
1823+ MESSAGE_ONE = Q_("New cloud folder available: '%(folder_name)s'")
1824+
1825+ def one(self):
1826+ """Show the folder information."""
1827+ volume = self.kwargs["udf"]
1828+ format_args = {"folder_name": volume.suggested_path}
1829+ return self.MESSAGE_ONE % format_args
1830+
1831+
1832+class ConnectionStatusEvent(StatusEvent):
1833+ """The connection to the server changed status."""
1834+
1835+ WEIGHT = 30
1836+
1837+ def many(self, events):
1838+ """Only the last message if there are many events of this type."""
1839+ return events[-1].one()
1840+
1841+
1842+class ConnectionLostStatus(ConnectionStatusEvent):
1843+ """The connection to the server was lost."""
1844+
1845+ MESSAGE_ONE = Q_("The connection to the server was lost.")
1846+
1847+
1848+class ConnectionMadeStatus(ConnectionStatusEvent):
1849+ """The connection to the server was made."""
1850+
1851+ MESSAGE_ONE = Q_("The connection to the server was restored.")
1852+
1853+
1854+class Timer(defer.Deferred):
1855+ """A deferred that fires past a given delay."""
1856+
1857+ def __init__(self, delay, clock=reactor):
1858+ """Initialize this instance."""
1859+ defer.Deferred.__init__(self)
1860+ self.clock = clock
1861+ self.delay = delay
1862+ self.delay_call = self.clock.callLater(delay, self.callback)
1863+
1864+ def cancel_if_active(self, call):
1865+ """Cancel a call if it is active."""
1866+ if call.active():
1867+ call.cancel()
1868+
1869+ def cleanup(self):
1870+ """Cancel all active calls."""
1871+ self.cancel_if_active(self.delay_call)
1872+
1873+ def callback(self, result=None):
1874+ """Make sure the timers are stopped when firing the callback."""
1875+ self.cleanup()
1876+ defer.Deferred.callback(self, result)
1877+
1878+ def reset(self):
1879+ """Reset the delay."""
1880+ if not self.called:
1881+ self.delay_call.reset(self.delay)
1882+
1883+ @property
1884+ def active(self):
1885+ """Is the delay still active."""
1886+ return self.delay_call.active()
1887+
1888+
1889+class DeadlineTimer(Timer):
1890+ """A Timer with a deadline."""
1891+
1892+ def __init__(self, delay, timeout=None, clock=reactor):
1893+ """Initialize this instance."""
1894+ Timer.__init__(self, delay, clock)
1895+ self.timeout = timeout
1896+ self.timeout_call = self.clock.callLater(timeout, self.callback)
1897+
1898+ def cleanup(self):
1899+ """Cancel all active calls."""
1900+ Timer.cleanup(self)
1901+ self.cancel_if_active(self.timeout_call)
1902+
1903+
1904+class FileDiscoveryBaseState(object):
1905+ """States for file discovery bubble."""
1906+
1907+ def __init__(self, bubble):
1908+ """Initialize this instance."""
1909+ self.bubble = bubble
1910+ self.clock = bubble.clock
1911+
1912+ def new_file_found(self):
1913+ """New files found."""
1914+
1915+ def cleanup(self):
1916+ """Cleanup this instance."""
1917+
1918+
1919+class FileDiscoveryIdleState(FileDiscoveryBaseState):
1920+ """Waiting for first file to appear."""
1921+
1922+ def new_file_found(self):
1923+ """New files found."""
1924+ self.bubble._start()
1925+
1926+
1927+class FileDiscoveryGatheringState(FileDiscoveryBaseState):
1928+ """Files are gathered then a notification is shown."""
1929+
1930+ initial_delay = 0.5
1931+ initial_timeout = 3.0
1932+
1933+ def __init__(self, *args):
1934+ """Initialize this instance."""
1935+ super(FileDiscoveryGatheringState, self).__init__(*args)
1936+ self.timer = DeadlineTimer(self.initial_delay,
1937+ self.initial_timeout,
1938+ clock=self.clock)
1939+ self.timer.addCallback(self._timeout)
1940+
1941+ def _timeout(self, result):
1942+ """Show the notification bubble."""
1943+ self.cleanup()
1944+ self.bubble._popup()
1945+
1946+ def new_file_found(self):
1947+ """New files found."""
1948+ self.timer.reset()
1949+
1950+ def cleanup(self):
1951+ """Cleanup this instance."""
1952+ self.timer.cleanup()
1953+
1954+
1955+class FileDiscoveryUpdateState(FileDiscoveryBaseState):
1956+ """The bubble is updated if more files are found."""
1957+
1958+ updates_delay = 0.5
1959+ updates_timeout = 10.0
1960+
1961+ def __init__(self, *args):
1962+ """Initialize this instance."""
1963+ super(FileDiscoveryUpdateState, self).__init__(*args)
1964+ self.main_timer = Timer(self.updates_timeout, clock=self.clock)
1965+ self.main_timer.addCallback(self._timeout)
1966+ self.updates_timer = None
1967+
1968+ def _timeout(self, result):
1969+ """No more updates on the notification bubble."""
1970+ self.cleanup()
1971+ self.bubble.start_sleeping()
1972+
1973+ def _update(self, result):
1974+ """The bubble should be updated."""
1975+ self.bubble._update()
1976+
1977+ def new_file_found(self):
1978+ """New files found."""
1979+ if self.updates_timer is None:
1980+ self.updates_timer = Timer(self.updates_delay, clock=self.clock)
1981+ self.updates_timer.addCallback(self._update)
1982+
1983+ def cleanup(self):
1984+ """Clean up the timers."""
1985+ self.main_timer.cleanup()
1986+ if self.updates_timer:
1987+ self.updates_timer.cleanup()
1988+
1989+
1990+class FileDiscoverySleepState(FileDiscoveryBaseState):
1991+ """The bubble is not updated while sleeping."""
1992+
1993+ sleep_delay = 300.0
1994+
1995+ def __init__(self, *args):
1996+ """Initialize this instance."""
1997+ super(FileDiscoverySleepState, self).__init__(*args)
1998+ self.main_timer = Timer(self.sleep_delay, clock=self.clock)
1999+ self.main_timer.addCallback(self._timeout)
2000+
2001+ def _timeout(self, result):
2002+ """Move the notification to the idle state."""
2003+ self.bubble._set_idle()
2004+
2005+ def cleanup(self):
2006+ """Clean up the timers."""
2007+ self.main_timer.cleanup()
2008+
2009+
2010+class FileDiscoveryBubble(object):
2011+ """
2012+ Show a notification for file discovery.
2013+
2014+ Waits 3 seconds for the file count to coalesce, then pops up a
2015+ notification. If new files are found the notification is updated,
2016+ but twice per second at most, and for up to 10 seconds.
2017+ Finally, sleeps for 10 minutes so it does not get annoying.
2018+ """
2019+
2020+ state = None
2021+
2022+ def __init__(self, status_aggregator, clock=reactor):
2023+ """Initialize this instance."""
2024+ self.connected = False
2025+ self.files_found = False
2026+ self.clock = clock
2027+ self.status_aggregator = status_aggregator
2028+ self._set_idle()
2029+ self.notification = None
2030+
2031+ def _change_state(self, new_state_class):
2032+ """Change to a new state."""
2033+ if self.state:
2034+ self.state.cleanup()
2035+ self.state = new_state_class(self)
2036+
2037+ def _set_idle(self):
2038+ """Reset this bubble to the initial state."""
2039+ self._change_state(FileDiscoveryIdleState)
2040+
2041+ def _start(self):
2042+ """The first file was found, so start gathering."""
2043+ self.notification = self.status_aggregator.get_notification()
2044+ self._change_state(FileDiscoveryGatheringState)
2045+
2046+ def _popup(self):
2047+ """Display the notification."""
2048+ if not self.connected:
2049+ return
2050+ text = self.status_aggregator.get_discovery_message()
2051+ if text:
2052+ self.notification.send_notification(UBUNTUONE_TITLE, text)
2053+ logger.debug("notification shown: %s", text)
2054+ self._change_state(FileDiscoveryUpdateState)
2055+
2056+ def _update(self):
2057+ """Update the notification."""
2058+ if not self.connected:
2059+ return
2060+ text = self.status_aggregator.get_discovery_message()
2061+ if text:
2062+ logger.debug("notification updated: %s", text)
2063+ self.notification.send_notification(UBUNTUONE_TITLE, text)
2064+
2065+ def start_sleeping(self):
2066+ """Wait for 10 minutes before annoying again."""
2067+ self._change_state(FileDiscoverySleepState)
2068+
2069+ def cleanup(self):
2070+ """Cleanup this instance."""
2071+ self.state.cleanup()
2072+
2073+ def connection_made(self):
2074+ """Connection made."""
2075+ self.connected = True
2076+ if self.files_found:
2077+ self._popup()
2078+
2079+ def connection_lost(self):
2080+ """Connection lost."""
2081+ self.connected = False
2082+
2083+ def new_file_found(self):
2084+ """New files found."""
2085+ self.files_found = True
2086+ self.state.new_file_found()
2087+
2088+
2089+class ProgressBar(object):
2090+ """Update a progressbar no more than 10 times a second."""
2091+ pulsating = True
2092+ visible = False
2093+ progress = 0.0
2094+ updates_delay = 0.1
2095+ timer = None
2096+ inhibitor_defer = None
2097+
2098+ def __init__(self, clock=reactor):
2099+ """Initialize this instance."""
2100+ self.clock = clock
2101+ try:
2102+ self.launcher = UbuntuOneLauncher()
2103+ except TypeError:
2104+ # Unity GIR can cause a TypeError here so we should not fail
2105+ self.launcher = DummyLauncher()
2106+
2107+ def cleanup(self):
2108+ """Cleanup this instance."""
2109+ if self.timer:
2110+ self.timer.cleanup()
2111+ self.timer = None
2112+
2113+ def _timeout(self, result):
2114+ """The aggregating timer has expired, so update the UI."""
2115+ self.timer = None
2116+ self.launcher.set_progress(self.progress)
2117+ logger.debug("progressbar updated: %f", self.progress)
2118+
2119+ def set_progress(self, progress):
2120+ """Steps amount changed. Set up a timer if there isn't one ticking."""
2121+ self.progress = progress
2122+ if not self.visible:
2123+ self.visible = True
2124+ self.launcher.show_progressbar()
2125+ logger.debug("progressbar shown")
2126+ if self.inhibitor_defer is None:
2127+ self.inhibitor_defer = session.inhibit_logout_suspend(
2128+ FILE_SYNC_IN_PROGRESS)
2129+ if not self.timer:
2130+ self.timer = Timer(self.updates_delay, clock=self.clock)
2131+ self.timer.addCallback(self._timeout)
2132+
2133+ def completed(self):
2134+ """All has completed."""
2135+ self.cleanup()
2136+ self.visible = False
2137+ self.launcher.hide_progressbar()
2138+ logger.debug("progressbar hidden")
2139+ if self.inhibitor_defer is not None:
2140+
2141+ def inhibitor_callback(inhibitor):
2142+ """The inhibitor was found, so cancel it."""
2143+ self.inhibitor_defer = None
2144+ return inhibitor.cancel()
2145+
2146+ self.inhibitor_defer.addCallback(inhibitor_callback)
2147+
2148+
2149+class FinalStatusBubble(object):
2150+ """Final bubble that shows the status of transfers."""
2151+
2152+ notification = None
2153+
2154+ def __init__(self, status_aggregator):
2155+ """Initialize this instance."""
2156+ self.status_aggregator = status_aggregator
2157+
2158+ def cleanup(self):
2159+ """Clean up this instance."""
2160+
2161+ def show(self):
2162+ """Show the final status notification."""
2163+ self.notification = self.status_aggregator.get_notification()
2164+ text = self.status_aggregator.get_final_status_message()
2165+ self.notification.send_notification(UBUNTUONE_TITLE, text)
2166+
2167+
2168+def group_statuses(status_events):
2169+ """Groups statuses by weight."""
2170+ weight_getter = operator.attrgetter("WEIGHT")
2171+ sorted_status_events = sorted(status_events, key=weight_getter)
2172+ return itertools.groupby(sorted_status_events, weight_getter)
2173+
2174+
2175+class StatusAggregator(object):
2176+ """The status aggregator backend."""
2177+
2178+ file_discovery_bubble = None
2179+ final_status_bubble = None
2180+
2181+ def __init__(self, clock=reactor):
2182+ """Initialize this instance."""
2183+ self.clock = clock
2184+ self.notification_switch = NotificationSwitch()
2185+ self.queue_done_timer = None
2186+ self.reset()
2187+ self.progress_bar = ProgressBar(clock=self.clock)
2188+ self.finished_delay = 10
2189+ self.progress = {}
2190+ self.to_do = {}
2191+
2192+ def get_notification(self):
2193+ """Create a new toggleable notification object."""
2194+ return self.notification_switch.get_notification()
2195+
2196+ # pylint: disable=W0201
2197+ def reset(self):
2198+ """Reset all counters and notifications."""
2199+ self.download_done = 0
2200+ self.upload_done = 0
2201+ self.files_uploading = []
2202+ self.uploading_filename = ''
2203+ self.files_downloading = []
2204+ self.downloading_filename = ''
2205+ if self.queue_done_timer is not None:
2206+ self.queue_done_timer.cleanup()
2207+ self.queue_done_timer = None
2208+
2209+ if self.file_discovery_bubble:
2210+ self.file_discovery_bubble.cleanup()
2211+ self.file_discovery_bubble = FileDiscoveryBubble(self,
2212+ clock=self.clock)
2213+
2214+ if self.final_status_bubble:
2215+ self.final_status_bubble.cleanup()
2216+ self.final_status_bubble = FinalStatusBubble(self)
2217+ self.progress = {}
2218+ self.to_do = {}
2219+ # pylint: enable=W0201
2220+
2221+ def get_discovery_message(self):
2222+ """Get the text for the discovery bubble."""
2223+ lines = []
2224+ files_uploading = len(self.files_uploading)
2225+ if files_uploading > 0:
2226+ lines.append(files_being_uploaded(
2227+ self.uploading_filename, files_uploading))
2228+ files_downloading = len(self.files_downloading)
2229+ if files_downloading > 0:
2230+ self.downloading_filename = self.files_downloading[0].path.split(
2231+ os.path.sep)[-1]
2232+ lines.append(files_being_downloaded(
2233+ self.downloading_filename, files_downloading))
2234+ return "\n".join(lines)
2235+
2236+ def get_final_status_message(self):
2237+ """Get some lines describing all we did."""
2238+ parts = []
2239+ parts.append(FINAL_COMPLETED)
2240+ upload_done = self.upload_done
2241+ if upload_done:
2242+ parts.append(files_were_uploaded(
2243+ self.uploading_filename, upload_done))
2244+
2245+ download_done = self.download_done
2246+ if download_done:
2247+ parts.append(files_were_downloaded(
2248+ self.downloading_filename, download_done))
2249+ return "\n".join(parts)
2250+
2251+ def _queue_done(self, _):
2252+ """Show final bubble and reset counters."""
2253+ self.queue_done_timer.cleanup()
2254+ self.queue_done_timer = None
2255+ logger.debug("queue done callback fired")
2256+ if self.upload_done + self.download_done > 0:
2257+ self.final_status_bubble.show()
2258+ self.progress_bar.completed()
2259+ self.reset()
2260+
2261+ def queue_done(self):
2262+ """Queue is finished."""
2263+ if not self.to_do:
2264+ return
2265+ if self.queue_done_timer is None:
2266+ logger.debug("queue done callback added")
2267+ self.queue_done_timer = Timer(
2268+ self.finished_delay, clock=self.clock)
2269+ self.queue_done_timer.addCallback(self._queue_done)
2270+ return
2271+ logger.debug("queue done callback reset")
2272+ self.queue_done_timer.reset()
2273+
2274+ def update_progressbar(self):
2275+ """Update the counters of the progressbar."""
2276+ if len(self.to_do) > 0:
2277+ progress = float(
2278+ sum(self.progress.values())) / sum(self.to_do.values())
2279+ self.progress_bar.set_progress(progress)
2280+
2281+ def download_started(self, command):
2282+ """A download just started."""
2283+ if self.queue_done_timer is not None:
2284+ self.queue_done_timer.cleanup()
2285+ self.queue_done_timer = None
2286+ self.files_downloading.append(command)
2287+ if command.deflated_size is not None:
2288+ self.to_do[
2289+ (command.share_id, command.node_id)] = command.deflated_size
2290+ # pylint: disable=W0201
2291+ if not self.downloading_filename:
2292+ self.downloading_filename = self.files_downloading[0].path.split(
2293+ os.path.sep)[-1]
2294+ # pylint: enable=W0201
2295+ self.update_progressbar()
2296+ logger.debug(
2297+ "queueing command (total: %d): %s",
2298+ len(self.to_do), command.__class__.__name__)
2299+ self.file_discovery_bubble.new_file_found()
2300+
2301+ def download_finished(self, command):
2302+ """A download just finished."""
2303+ if command in self.files_downloading:
2304+ self.files_downloading.remove(command)
2305+ self.download_done += 1
2306+ if command.deflated_size is not None:
2307+ self.progress[
2308+ (command.share_id, command.node_id)] = command.deflated_size
2309+ logger.debug("unqueueing command: %s", command.__class__.__name__)
2310+ self.update_progressbar()
2311+
2312+ def upload_started(self, command):
2313+ """An upload just started."""
2314+ if self.queue_done_timer is not None:
2315+ self.queue_done_timer.cleanup()
2316+ self.queue_done_timer = None
2317+ self.files_uploading.append(command)
2318+ if command.deflated_size is not None:
2319+ self.to_do[
2320+ (command.share_id, command.node_id)] = command.deflated_size
2321+ # pylint: disable=W0201
2322+ if not self.uploading_filename:
2323+ self.uploading_filename = self.files_uploading[0].path.split(
2324+ os.path.sep)[-1]
2325+ # pylint: enable=W0201
2326+ self.update_progressbar()
2327+ logger.debug(
2328+ "queueing command (total: %d): %s", len(self.to_do),
2329+ command.__class__.__name__)
2330+ self.file_discovery_bubble.new_file_found()
2331+
2332+ def upload_finished(self, command):
2333+ """An upload just finished."""
2334+ if command in self.files_uploading:
2335+ self.files_uploading.remove(command)
2336+ self.upload_done += 1
2337+ if command.deflated_size is not None:
2338+ self.progress[
2339+ (command.share_id, command.node_id)] = command.deflated_size
2340+ logger.debug("unqueueing command: %s", command.__class__.__name__)
2341+ self.update_progressbar()
2342+
2343+ def progress_made(self, share_id, node_id, n_bytes_written, deflated_size):
2344+ """Progress made on up- or download."""
2345+ if n_bytes_written is not None:
2346+ # if we haven't gotten the total size yet, set it now
2347+ if deflated_size and (share_id, node_id) not in self.to_do:
2348+ self.to_do[(share_id, node_id)] = deflated_size
2349+ self.progress[(share_id, node_id)] = n_bytes_written
2350+ self.update_progressbar()
2351+
2352+ def connection_lost(self):
2353+ """The connection to the server was lost."""
2354+ self.file_discovery_bubble.connection_lost()
2355+
2356+ def connection_made(self):
2357+ """The connection to the server was made."""
2358+ self.file_discovery_bubble.connection_made()
2359+
2360+
2361+class StatusFrontend(object):
2362+ """Frontend for the status aggregator, used by the StatusListener."""
2363+
2364+ def __init__(self, clock=reactor):
2365+ """Initialize this instance."""
2366+ self.aggregator = StatusAggregator(clock=clock)
2367+ self.notification = self.aggregator.get_notification()
2368+ self.messaging = Messaging()
2369+ self.quota_timer = None
2370+
2371+ def file_published(self, public_url):
2372+ """A file was published."""
2373+ status_event = FilePublishingStatus(new_public_url=public_url)
2374+ self.notification.send_notification(
2375+ UBUNTUONE_TITLE, status_event.one())
2376+
2377+ def file_unpublished(self, public_url): # pylint: disable=W0613
2378+ """A file was unpublished."""
2379+ self.notification.send_notification(
2380+ UBUNTUONE_TITLE, FileUnpublishingStatus().one())
2381+
2382+ def download_started(self, command):
2383+ """A file was queued for download."""
2384+ self.aggregator.download_started(command)
2385+
2386+ def download_finished(self, command):
2387+ """A file download was unqueued."""
2388+ self.aggregator.download_finished(command)
2389+
2390+ def upload_started(self, command):
2391+ """A file was queued for upload."""
2392+ self.aggregator.upload_started(command)
2393+
2394+ def upload_finished(self, command):
2395+ """A file upload was unqueued."""
2396+ self.aggregator.upload_finished(command)
2397+
2398+ def progress_made(self, share_id, node_id, n_bytes_written, deflated_size):
2399+ """Progress made on up- or download."""
2400+ self.aggregator.progress_made(
2401+ share_id, node_id, n_bytes_written, deflated_size)
2402+
2403+ def queue_done(self):
2404+ """The queue is empty."""
2405+ self.aggregator.queue_done()
2406+
2407+ def new_share_available(self, share):
2408+ """A new share is available for subscription."""
2409+ self.messaging.show_message(share.other_visible_name)
2410+ self.notification.send_notification(
2411+ UBUNTUONE_TITLE, ShareAvailableStatus(share=share).one())
2412+
2413+ def new_udf_available(self, udf):
2414+ """A new udf is available for subscription."""
2415+ if udf.subscribed:
2416+ return
2417+ self.notification.send_notification(
2418+ UBUNTUONE_TITLE, UDFAvailableStatus(udf=udf).one())
2419+
2420+ def server_connection_lost(self):
2421+ """The client lost the connection to the server."""
2422+ logger.debug("server connection lost")
2423+ self.aggregator.connection_lost()
2424+
2425+ def server_connection_made(self):
2426+ """The client made the connection to the server."""
2427+ logger.debug("server connection made")
2428+ self.aggregator.connection_made()
2429+
2430+ def udf_quota_exceeded(self, volume_dict):
2431+ """Quota exceeded in UDF."""
2432+ logger.debug("UDF quota exceeded for volume %r." % volume_dict)
2433+ alert_user()
2434+
2435+ def share_quota_exceeded(self, volume_dict):
2436+ """Sharing user's quota exceeded in share."""
2437+ logger.debug("Share quota exceeded for volume %r." % volume_dict)
2438+ if self.quota_timer is not None:
2439+ if self.quota_timer.active:
2440+ return
2441+ else:
2442+ self.quota_timer = Timer(ONE_DAY, clock=self.aggregator.clock)
2443+ self.notification.send_notification(
2444+ UBUNTUONE_TITLE, SHARE_QUOTA_EXCEEDED % (
2445+ volume_dict['path'], volume_dict['other_visible_name']))
2446+ alert_user()
2447+
2448+ def root_quota_exceeded(self, volume_dict):
2449+ """Quota exceeded in root."""
2450+ logger.debug("Root quota exceeded for volume %r." % volume_dict)
2451+ alert_user()
2452+
2453+ def set_show_all_notifications(self, value):
2454+ """Set the flag to show all notifications."""
2455+ if value:
2456+ self.aggregator.notification_switch.enable_notifications()
2457+ else:
2458+ self.aggregator.notification_switch.disable_notifications()
2459
2460=== added file 'debian/patches/03_reset_notify_name.patch'
2461--- debian/patches/03_reset_notify_name.patch 1970-01-01 00:00:00 +0000
2462+++ debian/patches/03_reset_notify_name.patch 2011-12-14 21:11:28 +0000
2463@@ -0,0 +1,74 @@
2464+=== modified file 'tests/status/test_aggregator.py'
2465+--- old/tests/status/test_aggregator.py 2011-10-27 13:47:09 +0000
2466++++ new/tests/status/test_aggregator.py 2011-12-07 20:41:48 +0000
2467+@@ -1327,6 +1327,26 @@
2468+ result = self.aggregator.get_discovery_message()
2469+ self.assertEqual(expected, result)
2470+
2471++ def test_get_discovery_message_clears_filenames(self):
2472++ """Test the message that's shown on the discovery bubble."""
2473++ uploading = 10
2474++ downloading = 8
2475++ filename = 'upfile0.ext'
2476++ filename2 = 'downfile0.ext'
2477++ self.aggregator.files_uploading.extend([
2478++ FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
2479++ self.aggregator.uploading_filename = filename
2480++ self.aggregator.files_downloading.extend([
2481++ FakeCommand(path='downfile%d.ext' % n) for n in
2482++ range(downloading)])
2483++ self.aggregator.downloading_filename = 'STALE FILENAME'
2484++ self.aggregator.uploading_filename = 'STALE FILENAME'
2485++ expected = (
2486++ aggregator.files_being_uploaded(filename, uploading) + "\n" +
2487++ aggregator.files_being_downloaded(filename2, downloading))
2488++ result = self.aggregator.get_discovery_message()
2489++ self.assertEqual(expected, result)
2490++
2491+ def test_get_final_status_message(self):
2492+ """The final status message."""
2493+ done = (5, 10)
2494+
2495+=== modified file 'ubuntuone/status/aggregator.py'
2496+--- old/ubuntuone/status/aggregator.py 2011-10-21 15:49:18 +0000
2497++++ new/ubuntuone/status/aggregator.py 2011-12-12 22:50:55 +0000
2498+@@ -646,12 +646,14 @@
2499+ lines = []
2500+ files_uploading = len(self.files_uploading)
2501+ if files_uploading > 0:
2502++ self.uploading_filename = os.path.basename(
2503++ self.files_uploading[0].path)
2504+ lines.append(files_being_uploaded(
2505+ self.uploading_filename, files_uploading))
2506+ files_downloading = len(self.files_downloading)
2507+ if files_downloading > 0:
2508+- self.downloading_filename = self.files_downloading[0].path.split(
2509+- os.path.sep)[-1]
2510++ self.downloading_filename = os.path.basename(
2511++ self.files_downloading[0].path)
2512+ lines.append(files_being_downloaded(
2513+ self.downloading_filename, files_downloading))
2514+ return "\n".join(lines)
2515+@@ -712,8 +714,8 @@
2516+ (command.share_id, command.node_id)] = command.deflated_size
2517+ # pylint: disable=W0201
2518+ if not self.downloading_filename:
2519+- self.downloading_filename = self.files_downloading[0].path.split(
2520+- os.path.sep)[-1]
2521++ self.downloading_filename = os.path.basename(
2522++ self.files_downloading[0].path)
2523+ # pylint: enable=W0201
2524+ self.update_progressbar()
2525+ logger.debug(
2526+@@ -743,8 +745,8 @@
2527+ (command.share_id, command.node_id)] = command.deflated_size
2528+ # pylint: disable=W0201
2529+ if not self.uploading_filename:
2530+- self.uploading_filename = self.files_uploading[0].path.split(
2531+- os.path.sep)[-1]
2532++ self.uploading_filename = os.path.basename(
2533++ self.files_uploading[0].path)
2534+ # pylint: enable=W0201
2535+ self.update_progressbar()
2536+ logger.debug(
2537+
2538
2539=== renamed file 'debian/patches/03_reset_notify_name.patch' => 'debian/patches/03_reset_notify_name.patch.moved'

Subscribers

People subscribed via source and target branches

to all changes: