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
=== added directory '.pc/03_reset_notify_name.patch'
=== renamed directory '.pc/03_reset_notify_name.patch' => '.pc/03_reset_notify_name.patch.moved'
=== added file '.pc/03_reset_notify_name.patch/.timestamp'
=== added directory '.pc/03_reset_notify_name.patch/tests'
=== added directory '.pc/03_reset_notify_name.patch/tests/status'
=== added file '.pc/03_reset_notify_name.patch/tests/status/test_aggregator.py'
--- .pc/03_reset_notify_name.patch/tests/status/test_aggregator.py 1970-01-01 00:00:00 +0000
+++ .pc/03_reset_notify_name.patch/tests/status/test_aggregator.py 2011-12-14 21:11:28 +0000
@@ -0,0 +1,1560 @@
1# tests.status.test_aggregator
2#
3# Author: Alejandro J. Cura <alecu@canonical.com>
4#
5# Copyright 2011 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Tests for the status events aggregator."""
19
20import logging
21
22from twisted.internet import defer
23from twisted.internet.task import Clock
24from twisted.trial.unittest import TestCase
25from mocker import Mocker
26
27from contrib.testing.testcase import BaseTwistedTestCase
28from ubuntuone.devtools.handlers import MementoHandler
29from ubuntuone.status import aggregator
30from ubuntuone.status.notification import AbstractNotification
31from ubuntuone.status.messaging import AbstractMessaging
32from ubuntuone.syncdaemon import status_listener
33from ubuntuone.syncdaemon.volume_manager import Share, UDF, Root
34
35FILENAME = 'example.txt'
36FILENAME2 = 'another_example.mp3'
37
38
39class PatchedClock(Clock):
40 """Patch the clock to fix twisted bug #4823."""
41
42 def advance(self, amount):
43 """Sort the calls before advancing the clock."""
44 self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
45 Clock.advance(self, amount)
46
47
48class TimerTestCase(TestCase):
49 """Test the Timer class."""
50
51 TIMEOUT = 3.0
52
53 @defer.inlineCallbacks
54 def setUp(self):
55 """Initialize this test instance."""
56 yield super(TimerTestCase, self).setUp()
57 self.clock = PatchedClock()
58 self.timer = aggregator.Timer(delay=3.0, clock=self.clock)
59
60 def test_not_fired_initially(self):
61 """The timer is not fired initially"""
62 self.assertFalse(self.timer.called)
63
64 def test_fired_after_delay(self):
65 """The timer is fired after the initial delay."""
66 self.clock.advance(self.timer.delay)
67 self.assertTrue(self.timer.called)
68
69 def test_cleanup_cancels_delay_call(self):
70 """Calling cleanup cancels the delay call."""
71 self.timer.cleanup()
72 self.assertTrue(self.timer.delay_call.cancelled)
73
74 def test_not_fired_immediately(self):
75 """The timer is not fired immediately."""
76 self.timer.reset()
77 self.assertFalse(self.timer.called)
78
79 def test_fired_after_initial_wait(self):
80 """The timer is fired after an initial wait."""
81 self.timer.reset()
82 self.clock.advance(self.timer.delay)
83 self.assertTrue(self.timer.called)
84
85 def test_not_fired_if_reset_within_delay(self):
86 """The timer is not fired if it is reset within the delay."""
87 self.timer.reset()
88 self.clock.advance(self.timer.delay / 0.8)
89 self.timer.reset()
90 self.clock.advance(self.timer.delay / 0.8)
91 self.assertTrue(self.timer.called)
92
93 def test_active(self):
94 """The timer is active until the delay is reached."""
95 self.timer.reset()
96 self.assertTrue(self.timer.active)
97 self.clock.advance(self.timer.delay + 1)
98 self.assertFalse(self.timer.active)
99
100
101class DeadlineTimerTestCase(TimerTestCase):
102 """Test the DeadlineTimer class."""
103
104 DELAY = 0.5
105
106 @defer.inlineCallbacks
107 def setUp(self):
108 """Initialize this test instance."""
109 yield super(DeadlineTimerTestCase, self).setUp()
110 self.clock = PatchedClock()
111 self.timer = aggregator.DeadlineTimer(delay=0.5, timeout=3.0,
112 clock=self.clock)
113
114 def test_fired_if_initial_timeout_exceeded(self):
115 """Timer is fired if the initial timeout is exceeded."""
116 small_delay = self.timer.delay * 0.8
117 for n in range(int(self.timer.timeout / small_delay) + 1):
118 self.timer.reset()
119 self.clock.advance(small_delay)
120 self.assertTrue(self.timer.called)
121
122 def test_not_fired_twice_if_delay_exceeded(self):
123 """Timer is not fired twice if the delay is exceeded."""
124 large_delay = self.timer.delay * 1.2
125 for n in range(int(self.timer.timeout / large_delay) + 1):
126 self.timer.reset()
127 self.clock.advance(large_delay)
128 self.clock.advance(self.timer.delay)
129 self.assertTrue(self.timer.called)
130
131 def test_not_fired_twice_if_timeout_exceeded(self):
132 """Timer is not fired twice if the timeout is exceeded."""
133 small_delay = self.timer.delay * 0.8
134 for n in range(int(self.timer.timeout / small_delay) + 1):
135 self.timer.reset()
136 self.clock.advance(small_delay)
137 self.clock.advance(self.timer.delay)
138 self.assertTrue(self.timer.called)
139
140 def test_cleanup_cancels_timeout_call(self):
141 """Calling cleanup cancels the delay call."""
142 self.timer.cleanup()
143 self.assertTrue(self.timer.timeout_call.cancelled)
144
145
146class FakeNotification(AbstractNotification):
147 """A fake notification class."""
148
149 def __init__(self, application_name="fake app"):
150 """Initialize this instance."""
151 self.notifications_shown = []
152 self.notification_switch = None
153 self.application_name = application_name
154 self.notification = None
155
156 def send_notification(self, title, message, icon=None, append=False):
157 """Show a notification to the user."""
158 if (self.notification_switch is not None
159 and not self.notification_switch.enabled):
160 return
161 self.notification = (title, message, icon, append)
162 self.notifications_shown.append((title, message, icon, append))
163 return len(self.notifications_shown) - 1
164
165
166def FakeNotificationSingleton():
167 """Builds a notification singleton, that logs all notifications shown."""
168 instance = FakeNotification()
169
170 def get_instance(notification_switch):
171 """Returns the single instance."""
172 instance.notification_switch = notification_switch
173 return instance
174
175 return get_instance
176
177
178class FakeMessaging(AbstractMessaging):
179 """A fake messaging class."""
180
181 def __init__(self): # pylint: disable=W0231
182 self.messages_shown = {}
183 self.messages_updated = {}
184 self.callbacks = []
185
186 # pylint: disable=R0913
187 def show_message(self, sender, callback=None, message_time=None,
188 message_count=None, icon=None):
189 """Show a message to the user."""
190 if message_count and sender in self.messages_shown:
191 self.update_count(sender, message_count)
192 self.messages_shown[sender] = (
193 callback, message_time, message_count, icon)
194 # pylint: enable=R0913
195
196 def update_count(self, sender, add_count):
197 """Update the count for an existing indicator."""
198 self.messages_updated[sender] = (sender, add_count)
199
200 def _callback(self, indicator, message_time=None):
201 """Fake callback."""
202 self.callbacks.append((indicator, message_time))
203
204 def create_callback(self):
205 """Create the callback."""
206 return self._callback
207
208
209class FakeStatusAggregator(object):
210 """A fake status aggregator."""
211
212 def __init__(self, clock): # pylint: disable=W0613
213 """Initialize this instance."""
214 self.discovered = 0
215 self.completed = 0
216 self.notification_switch = aggregator.NotificationSwitch()
217
218 def get_discovery_message(self):
219 """Return the file discovery message."""
220 self.discovered += 1
221 return self.build_discovery_message()
222
223 def build_discovery_message(self):
224 """Build the file discovery message."""
225 return "a lot of files found (%d).""" % self.discovered
226
227 def get_progress_message(self):
228 """Return the progress message."""
229 self.completed += 1
230 return self.build_progress_message()
231
232 def build_progress_message(self):
233 """Build the progress message."""
234 params = (self.discovered, self.completed)
235 return "a lot of files transferring (%d/%d).""" % params
236
237 def get_final_status_message(self):
238 """Return the final status message."""
239 return "a lot of files completed."""
240
241 def get_notification(self):
242 """Create a new toggleable notification object."""
243 return self.notification_switch.get_notification()
244
245
246class ToggleableNotificationTestCase(TestCase):
247 """Test the ToggleableNotification class."""
248
249 @defer.inlineCallbacks
250 def setUp(self):
251 """Initialize this test instance."""
252 yield super(ToggleableNotificationTestCase, self).setUp()
253 self.patch(aggregator, "Notification", FakeNotification)
254 self.notification_switch = aggregator.NotificationSwitch()
255 self.toggleable = self.notification_switch.get_notification()
256
257 def assertShown(self, notification):
258 """Assert that the notification was shown."""
259 self.assertIn(notification,
260 self.toggleable.notification.notifications_shown)
261
262 def assertNotShown(self, notification):
263 """Assert that the notification was shown."""
264 self.assertNotIn(notification,
265 self.toggleable.notification.notifications_shown)
266
267 def test_send_notification_passes_thru(self):
268 """The send_notification method passes thru."""
269 args = (1, 2, 3, 4)
270 self.toggleable.send_notification(*args)
271 self.assertShown(args)
272
273 def test_send_notification_honored_when_enabled(self):
274 """The send_notification method is honored when enabled."""
275 self.notification_switch.enable_notifications()
276 args = (aggregator.UBUNTUONE_TITLE, "hello", None, False)
277 self.toggleable.send_notification(*args)
278 self.assertShown(args)
279
280 def test_send_notification_ignored_when_disabled(self):
281 """The send_notification method is ignored when disabled."""
282 self.notification_switch.disable_notifications()
283 args = (aggregator.UBUNTUONE_TITLE, "hello", None, False)
284 self.toggleable.send_notification(*args)
285 self.assertNotShown(args)
286
287
288class NotificationSwitchTestCase(TestCase):
289 """Test the NotificationSwitch class."""
290
291 @defer.inlineCallbacks
292 def setUp(self):
293 """Initialize this test instance."""
294 yield super(NotificationSwitchTestCase, self).setUp()
295 self.notification_switch = aggregator.NotificationSwitch()
296
297 def test_get_notification(self):
298 """A new notification instance is returned."""
299 notification = self.notification_switch.get_notification()
300 self.assertEqual(notification.notification_switch,
301 self.notification_switch)
302
303 def test_enable_notifications(self):
304 """The switch is turned on."""
305 self.notification_switch.enable_notifications()
306 self.assertTrue(self.notification_switch.enabled)
307
308 def test_disable_notifications(self):
309 """The switch is turned off."""
310 self.notification_switch.disable_notifications()
311 self.assertFalse(self.notification_switch.enabled)
312
313
314class FileDiscoveryBubbleTestCase(TestCase):
315 """Test the FileDiscoveryBubble class."""
316
317 @defer.inlineCallbacks
318 def setUp(self):
319 """Initialize this test instance."""
320 yield super(FileDiscoveryBubbleTestCase, self).setUp()
321 self.patch(aggregator, "ToggleableNotification",
322 FakeNotificationSingleton())
323 self.clock = PatchedClock()
324 self.aggregator = FakeStatusAggregator(clock=self.clock)
325 self.bubble = aggregator.FileDiscoveryBubble(self.aggregator,
326 clock=self.clock)
327 self.addCleanup(self.bubble.cleanup)
328 fdis = aggregator.FileDiscoveryGatheringState
329 self.initial_delay = fdis.initial_delay
330 self.smaller_delay = self.initial_delay * 0.8
331 self.initial_timeout = fdis.initial_timeout
332 fdus = aggregator.FileDiscoveryUpdateState
333 self.updates_delay = fdus.updates_delay
334 self.updates_timeout = fdus.updates_timeout
335 fdss = aggregator.FileDiscoverySleepState
336 self.sleep_delay = fdss.sleep_delay
337
338 self.handler = MementoHandler()
339 self.handler.setLevel(logging.DEBUG)
340 aggregator.logger.addHandler(self.handler)
341 aggregator.logger.setLevel(logging.DEBUG)
342 self.addCleanup(aggregator.logger.removeHandler, self.handler)
343
344 def get_notifications_shown(self):
345 """The list of notifications shown."""
346 return self.bubble.notification.notifications_shown
347
348 def test_popup_shows_notification_when_connected(self):
349 """The popup callback shows notifications."""
350 self.bubble.connection_made()
351 self.bubble.new_file_found()
352 self.bubble._popup()
353 message = self.aggregator.build_discovery_message()
354 notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
355 self.assertIn(notification, self.get_notifications_shown())
356
357 def test_popup_shows_notification_after_connected(self):
358 """The popup callback shows notifications."""
359 self.bubble.new_file_found()
360 self.bubble.connection_made()
361 message = self.aggregator.build_discovery_message()
362 notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
363 self.assertIn(notification, self.get_notifications_shown())
364
365 def test_popup_shows_no_notification_before_connection_made(self):
366 """The popup callback shows notifications."""
367 self.bubble.new_file_found()
368 self.bubble._popup()
369 message = self.aggregator.build_discovery_message()
370 notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
371 self.assertNotIn(notification, self.get_notifications_shown())
372
373 def test_popup_shows_no_notification_after_connection_lost(self):
374 """The popup callback shows notifications."""
375 self.bubble.connection_made()
376 self.bubble.connection_lost()
377 self.bubble.new_file_found()
378 self.bubble._popup()
379 message = self.aggregator.build_discovery_message()
380 notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
381 self.assertNotIn(notification, self.get_notifications_shown())
382
383 def test_notification_is_logged_in_debug(self):
384 """The notification is printed in the debug log."""
385 self.bubble.connection_made()
386 self.bubble.new_file_found()
387 self.bubble._popup()
388 msg = "notification shown: %s" % self.get_notifications_shown()[0][1]
389 self.assertTrue(self.handler.check_debug(msg))
390
391 def test_bubble_is_not_shown_initially(self):
392 """The bubble is not shown initially."""
393 self.bubble.new_file_found()
394 self.assertEqual(0, len(self.get_notifications_shown()))
395
396 def test_bubble_is_shown_after_delay(self):
397 """The bubble is shown after a delay."""
398 self.bubble.connection_made()
399 self.bubble.new_file_found()
400 self.clock.advance(self.initial_delay)
401 self.assertEqual(1, len(self.get_notifications_shown()))
402
403 def test_bubble_not_shown_if_more_files_found(self):
404 """The bubble is not shown if more files found within delay."""
405 self.clock.advance(self.smaller_delay)
406 self.bubble.new_file_found()
407 self.clock.advance(self.smaller_delay)
408 self.assertEqual(0, len(self.get_notifications_shown()))
409
410 def test_bubble_shown_if_timeout_exceeded(self):
411 """The bubble is shown if the timeout is exceeded."""
412 self.bubble.connection_made()
413 self.bubble.new_file_found()
414 count = int(self.initial_timeout / self.smaller_delay) + 1
415 for n in range(count):
416 self.clock.advance(self.smaller_delay)
417 self.bubble.new_file_found()
418 self.assertEqual(1, len(self.get_notifications_shown()))
419
420 def test_idle_state(self):
421 """The idle state is verified."""
422 self.assertEqual(type(self.bubble.state),
423 aggregator.FileDiscoveryIdleState)
424
425 def test_gathering_state(self):
426 """The gathering state is set after the first file is found."""
427 self.bubble.new_file_found()
428 self.assertEqual(type(self.bubble.state),
429 aggregator.FileDiscoveryGatheringState)
430
431 def test_update_state(self):
432 """When the gathering state finishes, the update state is started."""
433 self.bubble.connection_made()
434 self.bubble.new_file_found()
435 self.clock.advance(self.initial_delay)
436 self.assertEqual(type(self.bubble.state),
437 aggregator.FileDiscoveryUpdateState)
438
439 def test_sleeping_state(self):
440 """When the update state finishes, the sleeping state is started."""
441 self.bubble.connection_made()
442 self.bubble.new_file_found()
443 self.clock.advance(self.initial_delay)
444 self.clock.advance(self.updates_timeout)
445 self.assertEqual(type(self.bubble.state),
446 aggregator.FileDiscoverySleepState)
447
448 def test_back_to_initial_state(self):
449 """When the last state finishes, we return to the idle state."""
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.clock.advance(self.sleep_delay)
455 self.assertEqual(type(self.bubble.state),
456 aggregator.FileDiscoveryIdleState)
457
458 def test_new_files_found_while_updating_not_shown_immediately(self):
459 """New files found in the updating state are not shown immediately."""
460 self.bubble.connection_made()
461 self.bubble.new_file_found()
462 self.clock.advance(self.initial_delay)
463 self.bubble.new_file_found()
464 self.assertEqual(1, len(self.get_notifications_shown()))
465
466 def test_new_files_found_while_updating_are_shown_after_a_delay(self):
467 """New files found in the updating state are shown after a delay."""
468 self.bubble.connection_made()
469 self.bubble.new_file_found()
470 self.clock.advance(self.initial_delay)
471 self.bubble.new_file_found()
472 self.clock.advance(self.updates_delay)
473 self.assertEqual(2, len(self.get_notifications_shown()))
474
475 def test_update_modifies_notification(self):
476 """The update callback updates notifications."""
477 self.bubble.connection_made()
478 self.bubble.new_file_found()
479 self.bubble._popup()
480 self.bubble.new_file_found()
481 self.bubble._update()
482 message = self.aggregator.build_discovery_message()
483 notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
484 self.assertIn(notification, self.get_notifications_shown())
485
486 def test_update_is_logged_in_debug(self):
487 """The notification is logged when _update is called."""
488 self.bubble.connection_made()
489 self.bubble.new_file_found()
490 self.bubble._popup()
491 self.bubble.new_file_found()
492 self.bubble._update()
493 msg = "notification updated: %s" % self.get_notifications_shown()[1][1]
494 self.assertTrue(self.handler.check_debug(msg))
495
496
497class FinalBubbleTestCase(TestCase):
498 """Test for the final status notification bubble."""
499
500 @defer.inlineCallbacks
501 def setUp(self):
502 """Initialize this test instance."""
503 yield super(FinalBubbleTestCase, self).setUp()
504 self.patch(aggregator, "ToggleableNotification",
505 FakeNotificationSingleton())
506 self.clock = PatchedClock()
507 self.aggregator = FakeStatusAggregator(clock=self.clock)
508 self.bubble = aggregator.FinalStatusBubble(self.aggregator)
509 self.addCleanup(self.bubble.cleanup)
510
511 def test_notification_not_shown_initially(self):
512 """The notification is not shown initially."""
513 self.assertEqual(None, self.bubble.notification)
514
515 def test_show_pops_bubble(self):
516 """The show method pops the bubble immediately."""
517 self.bubble.show()
518 self.assertEqual(1, len(self.bubble.notification.notifications_shown))
519
520
521class FakeLauncher(object):
522 """A fake UbuntuOneLauncher."""
523
524 progress_visible = False
525 progress = 0.0
526
527 def show_progressbar(self):
528 """The progressbar is shown."""
529 self.progress_visible = True
530
531 def hide_progressbar(self):
532 """The progressbar is hidden."""
533 self.progress_visible = False
534
535 def set_progress(self, value):
536 """The progressbar value is changed."""
537 self.progress = value
538
539
540class FakeInhibitor(object):
541 """A fake session inhibitor."""
542
543 def inhibit(self, flags, reason):
544 """Inhibit some events with a given reason."""
545 self.flags = flags
546 return defer.succeed(self)
547
548 def cancel(self):
549 """Cancel the inhibition for the current cookie."""
550 self.flags = 0
551 return defer.succeed(self)
552
553
554class ProgressBarTestCase(TestCase):
555 """Tests for the progress bar."""
556
557 @defer.inlineCallbacks
558 def setUp(self):
559 """Initialize this test instance."""
560 yield super(ProgressBarTestCase, self).setUp()
561 self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
562 self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
563 self.clock = PatchedClock()
564 self.bar = aggregator.ProgressBar(clock=self.clock)
565 self.addCleanup(self.bar.cleanup)
566 self.timeout_calls = []
567 original_timeout = self.bar._timeout
568
569 def fake_timeout(result):
570 """A fake _timeout method."""
571 self.timeout_calls.append(self.bar.progress)
572 original_timeout(result)
573
574 self.patch(self.bar, "_timeout", fake_timeout)
575
576 def test_launcher_typeerror_nonfatal(self):
577 """Test that Launcher raising TypeError is not fatal."""
578 def raise_typeerror(*args, **kwargs):
579 raise TypeError
580
581 self.patch(aggregator, "UbuntuOneLauncher", raise_typeerror)
582 aggregator.ProgressBar(clock=self.clock)
583
584 def test_shown_when_progress_made(self):
585 """The progress bar is shown when progress is made."""
586 self.bar.set_progress(0.5)
587 self.assertTrue(self.bar.visible)
588 self.assertTrue(self.bar.launcher.progress_visible)
589
590 def test_progress_made_updates_counter(self):
591 """Progress made updates the counter."""
592 self.bar.set_progress(0.5)
593 self.assertEqual(self.bar.progress, 0.5)
594
595 def test_no_timer_set_initially(self):
596 """There's no timer set initially."""
597 self.assertEqual(self.bar.timer, None)
598
599 def test_progress_made_sets_timer(self):
600 """Progress made sets up a timer."""
601 self.bar.set_progress(0.5)
602 self.assertNotEqual(self.bar.timer, None)
603
604 def test_cleanup_resets_timer(self):
605 """The cleanup method resets the timer."""
606 self.bar.set_progress(0.5)
607 self.bar.cleanup()
608 self.assertEqual(self.bar.timer, None)
609
610 def test_progress_made_not_updated_initially(self):
611 """Progress made is not updated initially."""
612 self.bar.set_progress(0.5)
613 self.assertEqual(0, len(self.timeout_calls))
614 self.assertEqual(0.0, self.bar.launcher.progress)
615
616 def test_progress_made_updated_after_a_delay(self):
617 """The progressbar is updated after a delay."""
618 self.bar.set_progress(0.5)
619 self.clock.advance(aggregator.ProgressBar.updates_delay)
620 self.assertIn(0.5, self.timeout_calls)
621 self.assertEqual(0.5, self.bar.launcher.progress)
622
623 def test_progress_updates_are_aggregated(self):
624 """The progressbar is updated after a delay."""
625 self.bar.set_progress(0.5)
626 self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
627 self.bar.set_progress(0.6)
628 self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
629 self.assertEqual(1, len(self.timeout_calls))
630
631 def test_progress_updates_are_continuous(self):
632 """The progressbar updates are continuous."""
633 self.bar.set_progress(0.5)
634 self.clock.advance(aggregator.ProgressBar.updates_delay)
635 self.assertEqual(0.5, self.bar.launcher.progress)
636 self.bar.set_progress(0.6)
637 self.clock.advance(aggregator.ProgressBar.updates_delay)
638 self.assertEqual(0.6, self.bar.launcher.progress)
639 self.assertEqual(2, len(self.timeout_calls))
640
641 def test_hidden_when_completed(self):
642 """The progressbar is hidden when everything completes."""
643 self.bar.set_progress(0.5)
644 self.bar.completed()
645 self.assertFalse(self.bar.visible)
646 self.assertFalse(self.bar.launcher.progress_visible)
647
648 @defer.inlineCallbacks
649 def test_progress_made_inhibits_logout_suspend(self):
650 """Suspend and logout are inhibited when the progressbar is shown."""
651 self.bar.set_progress(0.5)
652 expected = aggregator.session.INHIBIT_LOGOUT_SUSPEND
653 inhibitor = yield self.bar.inhibitor_defer
654 self.assertEqual(inhibitor.flags, expected)
655
656 @defer.inlineCallbacks
657 def test_completed_uninhibits_logout_suspend(self):
658 """Suspend and logout are uninhibited when all has completed."""
659 self.bar.set_progress(0.5)
660 d = self.bar.inhibitor_defer
661 self.bar.completed()
662 inhibitor = yield d
663 self.assertEqual(inhibitor.flags, 0)
664
665
666class FakeDelayedBuffer(object):
667 """Appends all status pushed into a list."""
668 timer_reset = False
669 processed = False
670
671 def __init__(self, *args, **kwargs):
672 """Initialize this instance."""
673 self.events = []
674
675 def push_event(self, event):
676 """Push an event into this buffer."""
677 self.events.append(event)
678
679 def reset_threshold_timer(self):
680 """The status has changed."""
681 self.timer_reset = True
682
683 def process_accumulated(self):
684 """Process accumulated events."""
685 self.processed = True
686
687
688class FakeCommand(object):
689 """A fake command."""
690
691 def __init__(self, path=''):
692 self.path = path
693 self.share_id = path
694 self.node_id = path
695 self.deflated_size = 10000
696
697
698class FakeVolumeManager(object):
699 """A fake vm."""
700
701 def __init__(self):
702 """Initialize this instance."""
703 self.volumes = {}
704 self.root = None
705
706 def get_volume(self, volume_id):
707 """Return a volume given its id."""
708 return self.volumes[volume_id]
709
710
711class FakeAggregator(object):
712 """A fake aggregator object."""
713
714 def __init__(self, clock):
715 """Initialize this fake instance."""
716 self.queued_commands = set()
717 self.notification_switch = aggregator.NotificationSwitch()
718 self.connected = False
719 self.clock = PatchedClock()
720 self.files_uploading = []
721 self.files_downloading = []
722 self.progress_events = []
723
724 def queue_done(self):
725 """The queue completed all operations."""
726 self.queued_commands.clear()
727
728 def get_notification(self):
729 """Create a new toggleable notification object."""
730 return self.notification_switch.get_notification()
731
732 def download_started(self, command):
733 """A download just started."""
734 self.files_downloading.append(command)
735 self.queued_commands.add(command)
736
737 def download_finished(self, command):
738 """A download just finished."""
739 if command in self.files_downloading:
740 self.files_downloading.remove(command)
741 self.queued_commands.discard(command)
742
743 def upload_started(self, command):
744 """An upload just started."""
745 self.files_uploading.append(command)
746 self.queued_commands.add(command)
747
748 def upload_finished(self, command):
749 """An upload just finished."""
750 if command in self.files_uploading:
751 self.files_uploading.remove(command)
752 self.queued_commands.discard(command)
753
754 def progress_made(self, share_id, node_id, n_bytes, deflated_size):
755 """Progress made on up- or download."""
756 self.progress_events.append(
757 (share_id, node_id, n_bytes, deflated_size))
758
759 def connection_made(self):
760 """The client made the connection to the server."""
761 self.connected = True
762
763 def connection_lost(self):
764 """The client lost the connection to the server."""
765 self.connected = False
766
767
768class StatusFrontendTestCase(BaseTwistedTestCase):
769 """Test the status frontend."""
770
771 @defer.inlineCallbacks
772 def setUp(self):
773 """Initialize this test instance."""
774 yield super(StatusFrontendTestCase, self).setUp()
775 self.patch(aggregator, "StatusAggregator", FakeAggregator)
776 self.patch(aggregator, "ToggleableNotification",
777 FakeNotificationSingleton())
778 self.patch(aggregator, "Messaging", FakeMessaging)
779 self.fakefsm = None
780 self.fakevm = FakeVolumeManager()
781 self.status_frontend = aggregator.StatusFrontend()
782 self.listener = status_listener.StatusListener(self.fakefsm,
783 self.fakevm,
784 self.status_frontend)
785
786 def test_file_published(self):
787 """A file published event is processed."""
788 share_id = "fake share id"
789 node_id = "fake node id"
790 is_public = True
791 public_url = "http://fake_public/url"
792 self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
793 is_public, public_url)
794 self.assertEqual(
795 1, len(self.status_frontend.notification.notifications_shown))
796
797 def test_file_unpublished(self):
798 """A file unpublished event is processed."""
799 share_id = "fake share id"
800 node_id = "fake node id"
801 is_public = False
802 public_url = None # SD sends None when unpublishing
803
804 self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
805 is_public, public_url)
806 self.assertEqual(
807 1, len(self.status_frontend.notification.notifications_shown))
808
809 def test_download_started(self):
810 """A download was added to the queue."""
811 self.patch(status_listener.action_queue, "Download", FakeCommand)
812 fake_command = FakeCommand()
813 self.listener.handle_SYS_QUEUE_ADDED(fake_command)
814 qc = self.status_frontend.aggregator.queued_commands
815 self.assertIn(fake_command, qc)
816
817 def test_download_started_with_no_deflated_size(self):
818 """A download of unknown size was added to the queue."""
819 self.patch(status_listener.action_queue, "Download", FakeCommand)
820 fake_command = FakeCommand()
821 fake_command.deflated_size = None
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_finished(self):
827 """A download was removed from the queue."""
828 self.patch(status_listener.action_queue, "Download", FakeCommand)
829 fake_command = FakeCommand()
830 self.listener.handle_SYS_QUEUE_ADDED(fake_command)
831 self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
832 qc = self.status_frontend.aggregator.queued_commands
833 self.assertNotIn(fake_command, qc)
834
835 def test_upload_started(self):
836 """An upload was added to the queue."""
837 self.patch(status_listener.action_queue, "Upload", FakeCommand)
838 fake_command = FakeCommand()
839 self.listener.handle_SYS_QUEUE_ADDED(fake_command)
840 qc = self.status_frontend.aggregator.queued_commands
841 self.assertIn(fake_command, qc)
842
843 def test_upload_started_with_no_deflated_size(self):
844 """An upload of unknown size was added to the queue."""
845 self.patch(status_listener.action_queue, "Upload", FakeCommand)
846 fake_command = FakeCommand()
847 fake_command.deflated_size = None
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_finished(self):
853 """An upload was removed from the queue."""
854 self.patch(status_listener.action_queue, "Upload", FakeCommand)
855 fake_command = FakeCommand()
856 self.listener.handle_SYS_QUEUE_ADDED(fake_command)
857 self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
858 qc = self.status_frontend.aggregator.queued_commands
859 self.assertNotIn(fake_command, qc)
860
861 def test_progress_made_on_upload(self):
862 """Progress was made on an uploading file."""
863 share_id = 'fake_share'
864 node_id = 'fake_node'
865 n_bytes_written = 100
866 deflated_size = 10000
867 self.listener.handle_AQ_UPLOAD_FILE_PROGRESS(
868 share_id, node_id, n_bytes_written, deflated_size)
869 pe = self.status_frontend.aggregator.progress_events
870 self.assertEqual(
871 [(share_id, node_id, n_bytes_written, deflated_size)], pe,
872 "progress_made was not called (exactly once) on aggregator.")
873
874 def test_progress_made_on_download(self):
875 """Progress was made on an downloading file."""
876 share_id = 'fake_share'
877 node_id = 'fake_node'
878 n_bytes_written = 200
879 deflated_size = 20000
880 self.listener.handle_AQ_DOWNLOAD_FILE_PROGRESS(
881 share_id, node_id, n_bytes_written, deflated_size)
882 pe = self.status_frontend.aggregator.progress_events
883 self.assertEqual(
884 [(share_id, node_id, n_bytes_written, deflated_size)], pe,
885 "progress_made was not called (exactly once) on aggregator.")
886
887 def test_queue_done(self):
888 """The queue is empty."""
889 fake_command = FakeCommand()
890 qc = self.status_frontend.aggregator.queued_commands
891 qc.add(fake_command)
892 self.listener.handle_SYS_QUEUE_DONE()
893 self.assertEqual(0, len(qc))
894
895 def test_new_share_available(self):
896 """A new share is available for subscription."""
897 SHARE_ID = "fake share id"
898 FAKE_SENDER = 'Mom'
899 share = Share(volume_id=SHARE_ID, other_visible_name=FAKE_SENDER)
900 self.fakevm.volumes[SHARE_ID] = share
901 self.listener.handle_VM_SHARE_CREATED(SHARE_ID)
902 self.assertEqual(
903 1, len(self.status_frontend.notification.notifications_shown))
904 msg = self.status_frontend.messaging.messages_shown[FAKE_SENDER]
905 # msg did not receive a time argument
906 self.assertEqual(None, msg[1])
907 # msg did not receive a count argument
908 self.assertEqual(None, msg[2])
909
910 def test_already_subscribed_new_udf_available(self):
911 """A new udf that was already subscribed."""
912 udf = UDF()
913 udf.subscribed = True
914 self.listener.handle_VM_UDF_CREATED(udf)
915 self.assertEqual(
916 0, len(self.status_frontend.notification.notifications_shown))
917 self.assertEqual(
918 0, len(self.status_frontend.messaging.messages_shown))
919 self.assertEqual(
920 0, len(self.status_frontend.messaging.messages_updated))
921
922 def test_new_udf_available(self):
923 """A new udf is available for subscription."""
924 udf = UDF()
925 self.listener.handle_VM_UDF_CREATED(udf)
926 self.assertEqual(
927 1, len(self.status_frontend.notification.notifications_shown))
928 self.assertEqual(
929 0, len(self.status_frontend.messaging.messages_shown))
930 self.assertEqual(
931 0, len(self.status_frontend.messaging.messages_updated))
932 self.assertEqual(0, len(self.status_frontend.messaging.callbacks))
933
934 def test_two_new_udfs_available(self):
935 """A new udf is available for subscription."""
936 udf1 = UDF()
937 self.listener.handle_VM_UDF_CREATED(udf1)
938 udf2 = UDF()
939 self.listener.handle_VM_UDF_CREATED(udf2)
940 self.assertEqual(
941 2, len(self.status_frontend.notification.notifications_shown))
942 self.assertEqual(
943 0, len(self.status_frontend.messaging.messages_shown))
944 self.assertEqual(
945 0, len(self.status_frontend.messaging.messages_updated))
946
947 def test_server_connection_lost(self):
948 """The client connected to the server."""
949 self.status_frontend.aggregator.connected = True
950 self.listener.handle_SYS_CONNECTION_LOST()
951 self.assertEqual(
952 0, len(self.status_frontend.notification.notifications_shown))
953 self.assertFalse(self.status_frontend.aggregator.connected)
954
955 def test_server_connection_made(self):
956 """The client connected to the server."""
957 self.status_frontend.aggregator.connected = False
958 self.listener.handle_SYS_CONNECTION_MADE()
959 self.assertEqual(
960 0, len(self.status_frontend.notification.notifications_shown))
961 self.assertTrue(self.status_frontend.aggregator.connected)
962
963 def test_set_show_all_notifications(self):
964 """Test the set_show_all_notifications method."""
965 self.status_frontend.set_show_all_notifications(False)
966 self.assertFalse(self.status_frontend.aggregator.
967 notification_switch.enabled)
968
969 def test_udf_quota_exceeded(self): # pylint: disable=R0201
970 """Quota exceeded in udf."""
971 mocker = Mocker()
972 launcher = mocker.replace(
973 "ubuntuone.platform.launcher.UbuntuOneLauncher")
974 launcher()
975 mock_launcher = mocker.mock()
976 mocker.result(mock_launcher)
977 mock_launcher.set_urgent()
978 mocker.replay()
979 UDF_ID = 'fake udf id'
980 udf = UDF(volume_id=UDF_ID)
981 self.fakevm.volumes[UDF_ID] = udf
982 self.listener.handle_SYS_QUOTA_EXCEEDED(
983 volume_id=UDF_ID, free_bytes=0)
984 self.assertEqual(
985 0, len(self.status_frontend.notification.notifications_shown))
986 mocker.restore()
987 mocker.verify()
988
989 def test_root_quota_exceeded(self): # pylint: disable=R0201
990 """Quota exceeded in root."""
991 mocker = Mocker()
992 launcher = mocker.replace(
993 "ubuntuone.platform.launcher.UbuntuOneLauncher")
994 launcher()
995 mock_launcher = mocker.mock()
996 mocker.result(mock_launcher)
997 mock_launcher.set_urgent()
998 mocker.replay()
999 ROOT_ID = 'fake root id'
1000 root = Root(volume_id=ROOT_ID)
1001 self.fakevm.volumes[ROOT_ID] = root
1002 self.fakevm.root = root
1003 self.listener.handle_SYS_QUOTA_EXCEEDED(
1004 volume_id=ROOT_ID, free_bytes=0)
1005 self.assertEqual(
1006 0, len(self.status_frontend.notification.notifications_shown))
1007 mocker.restore()
1008 mocker.verify()
1009
1010 def test_share_quota_exceeded(self):
1011 """Quota exceeded in share."""
1012 mocker = Mocker()
1013 launcher = mocker.replace(
1014 "ubuntuone.platform.launcher.UbuntuOneLauncher")
1015 launcher()
1016 mock_launcher = mocker.mock()
1017 mocker.result(mock_launcher)
1018 mock_launcher.set_urgent()
1019 launcher()
1020 mock_launcher = mocker.mock()
1021 mocker.result(mock_launcher)
1022 mock_launcher.set_urgent()
1023 mocker.replay()
1024 SHARE_ID = 'fake share id'
1025 BYTES = 0
1026 share = Share(volume_id=SHARE_ID)
1027 self.fakevm.volumes[SHARE_ID] = share
1028 self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1029 self.assertEqual(
1030 1, len(self.status_frontend.notification.notifications_shown))
1031 self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1032 self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1033 self.assertEqual(
1034 1, len(self.status_frontend.notification.notifications_shown))
1035 self.status_frontend.aggregator.clock.advance(aggregator.ONE_DAY + 1)
1036 self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1037 self.assertEqual(
1038 2, len(self.status_frontend.notification.notifications_shown))
1039 mocker.restore()
1040 mocker.verify()
1041
1042
1043class StatusEventTestCase(TestCase):
1044 """Test the status event class and children."""
1045
1046 CLASS = aggregator.StatusEvent
1047 CLASS_KWARGS = {}
1048 status = None
1049
1050 @defer.inlineCallbacks
1051 def setUp(self):
1052 """Initialize this test instance."""
1053 yield super(StatusEventTestCase, self).setUp()
1054 if type(self) == StatusEventTestCase:
1055 self.assertRaises(AssertionError, self.CLASS, **self.CLASS_KWARGS)
1056 else:
1057 self.status = self.CLASS(**self.CLASS_KWARGS)
1058
1059 def test_one_message_defined(self):
1060 """The singular message is defined as MESSAGE_ONE."""
1061 if self.status:
1062 self.assertNotEqual(None, self.CLASS.MESSAGE_ONE)
1063
1064 def test_one_message_built_correctly(self):
1065 """The message returned by one() is returned ok."""
1066 if self.status:
1067 self.assertEqual(self.status.one(), self.CLASS.MESSAGE_ONE)
1068
1069
1070class FilePublishingStatusTestCase(StatusEventTestCase):
1071 """Test the file publishing status class."""
1072
1073 CLASS = aggregator.FilePublishingStatus
1074 CLASS_KWARGS = {"new_public_url": "http://fake_public/url"}
1075
1076 def test_one_message_built_correctly(self):
1077 """The message returned by one() should include the url."""
1078 expected = self.CLASS.MESSAGE_ONE % self.status.kwargs
1079 self.assertEqual(self.status.one(), expected)
1080
1081
1082class FileUnpublishingStatusTestCase(StatusEventTestCase):
1083 """Test the file unpublishing status class."""
1084
1085 CLASS = aggregator.FileUnpublishingStatus
1086 CLASS_KWARGS = {"old_public_url": None}
1087
1088
1089class ShareAvailableEventTestCase(StatusEventTestCase):
1090 """Test the folder available status class with a Share."""
1091
1092 FOLDER_NAME = "folder name"
1093 OTHER_USER_NAME = "person name"
1094 SAMPLE_SHARE = Share(accepted=False, name=FOLDER_NAME,
1095 other_visible_name=OTHER_USER_NAME)
1096 CLASS = aggregator.ShareAvailableStatus
1097 CLASS_KWARGS = {"share": SAMPLE_SHARE}
1098
1099 def test_one_message_built_correctly(self):
1100 """one() must return the folder name and user name."""
1101 format_args = {
1102 "folder_name": self.FOLDER_NAME,
1103 "other_user_name": self.OTHER_USER_NAME,
1104 }
1105 expected = self.CLASS.MESSAGE_ONE % format_args
1106 self.assertEqual(self.status.one(), expected)
1107
1108
1109class UDFAvailableEventTestCase(StatusEventTestCase):
1110 """Test the folder available status class with a UDF."""
1111
1112 FOLDER_NAME = "folder name"
1113 SAMPLE_UDF = UDF(subscribed=False, suggested_path=FOLDER_NAME)
1114 CLASS = aggregator.UDFAvailableStatus
1115 CLASS_KWARGS = {'udf': SAMPLE_UDF}
1116
1117 def test_one_message_built_correctly(self):
1118 """one() must return the folder name."""
1119 format_args = {"folder_name": self.FOLDER_NAME}
1120 expected = self.CLASS.MESSAGE_ONE % format_args
1121 self.assertEqual(self.status.one(), expected)
1122
1123
1124class ConnectionLostEventTestCase(StatusEventTestCase):
1125 """Test the event when the connection is lost."""
1126
1127 CLASS = aggregator.ConnectionLostStatus
1128
1129 def test_many_message_built_correctly(self):
1130 """The message returned by many() is returned ok."""
1131 if self.status:
1132 count = 99
1133 test_events = [FakeStatus(88)] * count + [self.CLASS()]
1134 expected = self.CLASS.MESSAGE_ONE
1135 self.assertEqual(self.status.many(test_events), expected)
1136
1137
1138class ConnectionMadeEventTestCase(ConnectionLostEventTestCase):
1139 """Test the event when the connection is made."""
1140
1141 CLASS = aggregator.ConnectionMadeStatus
1142
1143
1144class FakeStatus(aggregator.StatusEvent):
1145 """A fake status to test weight comparisons."""
1146
1147 def __init__(self, weight):
1148 """Initialize with the fake weight."""
1149 super(FakeStatus, self).__init__()
1150 self.WEIGHT = weight
1151
1152
1153class FakeFileDiscoveryBubble(object):
1154 """A fake FileDiscoveryBubble."""
1155
1156 count = 0
1157
1158 def __init__(self, status_aggregator, clock=None):
1159 """Initialize this instance."""
1160 self.status_aggregator = status_aggregator
1161
1162 def new_file_found(self):
1163 """New files were found."""
1164 self.count += 1
1165
1166 def cleanup(self):
1167 """Cleanup this instance."""
1168
1169 def connection_made(self):
1170 """Connection made."""
1171
1172 def connection_lost(self):
1173 """Connection lost."""
1174
1175
1176class FakeFinalBubble(object):
1177 """A fake FinalStatusBubble."""
1178
1179 shown = False
1180
1181 def __init__(self, status_aggregator):
1182 """Initialize this fake instance."""
1183 self.status_aggregator = status_aggregator
1184
1185 def cleanup(self):
1186 """Cleanup this instance."""
1187
1188 def show(self):
1189 """Show this bubble."""
1190 self.shown = True
1191
1192
1193class StatusAggregatorTestCase(TestCase):
1194 """Test the backend of the status aggregator."""
1195
1196 @defer.inlineCallbacks
1197 def setUp(self):
1198 """Initialize this test instance."""
1199 yield super(StatusAggregatorTestCase, self).setUp()
1200 self.patch(aggregator, "FileDiscoveryBubble",
1201 FakeFileDiscoveryBubble)
1202 self.patch(aggregator, "FinalStatusBubble",
1203 FakeFinalBubble)
1204 self.patch(aggregator, "ToggleableNotification",
1205 FakeNotificationSingleton())
1206 self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1207 self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1208 clock = PatchedClock()
1209 self.status_frontend = aggregator.StatusFrontend(clock=clock)
1210 self.aggregator = self.status_frontend.aggregator
1211 self.fake_bubble = self.aggregator.file_discovery_bubble
1212
1213 self.handler = MementoHandler()
1214 self.handler.setLevel(logging.DEBUG)
1215 aggregator.logger.addHandler(self.handler)
1216 aggregator.logger.setLevel(logging.DEBUG)
1217 self.addCleanup(aggregator.logger.removeHandler, self.handler)
1218 self.addCleanup(self.aggregator.progress_bar.cleanup)
1219
1220 def assertStatusReset(self):
1221 """Assert that the status is at zero."""
1222 self.assertEqual(0, self.aggregator.download_done)
1223 self.assertEqual(0, self.aggregator.upload_done)
1224 self.assertEqual(0, len(self.aggregator.files_uploading))
1225 self.assertEqual(0, len(self.aggregator.files_downloading))
1226 self.assertEqual({}, self.aggregator.progress)
1227 self.assertEqual({}, self.aggregator.to_do)
1228 self.assertIdentical(None, self.aggregator.queue_done_timer)
1229
1230 def assertMiscCommandQueued(self, fc):
1231 """Assert that some command was queued."""
1232 self.assertEqual(len(self.aggregator.to_do), 1)
1233 message = "queueing command (total: 1): %s" % fc.__class__.__name__
1234 self.assertEqual(fc.deflated_size, sum(self.aggregator.to_do.values()))
1235 self.assertTrue(self.handler.check_debug(message))
1236 self.assertTrue(self.aggregator.progress_bar.visible)
1237
1238 def assertMiscCommandUnqueued(self, fc):
1239 """Assert that some command was unqueued."""
1240 self.assertEqual(
1241 1, self.aggregator.download_done + self.aggregator.upload_done)
1242 message = "unqueueing command: %s" % fc.__class__.__name__
1243 self.assertTrue(self.handler.check_debug(message))
1244
1245 def test_counters_start_at_zero(self):
1246 """Test that the counters start at zero."""
1247 self.assertStatusReset()
1248
1249 def test_file_download_started(self):
1250 """Test that a file has started download."""
1251 fc = FakeCommand(path='testfile.txt')
1252 self.assertEqual('', self.aggregator.downloading_filename)
1253 self.status_frontend.download_started(fc)
1254 self.assertEqual(1, len(self.aggregator.files_downloading))
1255 self.assertEqual('testfile.txt', self.aggregator.downloading_filename)
1256 self.assertMiscCommandQueued(fc)
1257 self.assertEqual(1, self.fake_bubble.count)
1258 self.assertEqual(
1259 {(fc.share_id, fc.node_id): (fc.deflated_size)},
1260 self.aggregator.to_do)
1261
1262 def test_file_download_finished(self):
1263 """Test that a file has finished downloading."""
1264 fc = FakeCommand()
1265 self.status_frontend.download_started(fc)
1266 self.status_frontend.download_finished(fc)
1267 self.assertEqual(self.aggregator.download_done, 1)
1268 self.assertMiscCommandUnqueued(fc)
1269 self.assertEqual(
1270 {(fc.share_id, fc.node_id): (fc.deflated_size)},
1271 self.aggregator.progress)
1272
1273 def test_file_upload_started(self):
1274 """Test that a file has started upload."""
1275 fc = FakeCommand(path='testfile.txt')
1276 self.assertEqual('', self.aggregator.uploading_filename)
1277 self.status_frontend.upload_started(fc)
1278 self.assertEqual(1, len(self.aggregator.files_uploading))
1279 self.assertEqual('testfile.txt', self.aggregator.uploading_filename)
1280 self.assertMiscCommandQueued(fc)
1281 self.assertEqual(1, self.fake_bubble.count)
1282 self.assertEqual(
1283 {(fc.share_id, fc.node_id): (fc.deflated_size)},
1284 self.aggregator.to_do)
1285
1286 def test_file_upload_finished(self):
1287 """Test that a file has finished uploading."""
1288 fc = FakeCommand()
1289 self.status_frontend.upload_started(fc)
1290 self.status_frontend.upload_finished(fc)
1291 self.assertEqual(self.aggregator.upload_done, 1)
1292 self.assertMiscCommandUnqueued(fc)
1293 self.assertEqual(
1294 {(fc.share_id, fc.node_id): (fc.deflated_size)},
1295 self.aggregator.progress)
1296
1297 def test_progress_made(self):
1298 """Progress on up and downloads is tracked."""
1299 share_id = 'fake_share'
1300 node_id = 'fake_node'
1301 n_bytes_written = 200
1302 deflated_size = 100000
1303 self.aggregator.progress_made(
1304 share_id, node_id, n_bytes_written, deflated_size)
1305 self.assertEqual(
1306 {(share_id, node_id): (n_bytes_written)},
1307 self.aggregator.progress)
1308
1309 def test_get_discovery_message(self):
1310 """Test the message that's shown on the discovery bubble."""
1311 uploading = 10
1312 downloading = 8
1313 filename = 'upfile0.ext'
1314 filename2 = 'downfile0.ext'
1315 self.aggregator.files_uploading.extend([
1316 FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
1317 self.aggregator.uploading_filename = filename
1318 self.aggregator.files_downloading.extend([
1319 FakeCommand(path='downfile%d.ext' % n) for n in
1320 range(downloading)])
1321 self.aggregator.downloading_filename = filename2
1322 expected = (
1323 aggregator.files_being_uploaded(filename, uploading) + "\n" +
1324 aggregator.files_being_downloaded(filename2, downloading))
1325 result = self.aggregator.get_discovery_message()
1326 self.assertEqual(expected, result)
1327
1328 def test_get_final_status_message(self):
1329 """The final status message."""
1330 done = (5, 10)
1331 self.aggregator.uploading_filename = FILENAME
1332 self.aggregator.downloading_filename = FILENAME2
1333 self.aggregator.upload_done, self.aggregator.download_done = done
1334
1335 expected = (
1336 aggregator.FINAL_COMPLETED + "\n" +
1337 aggregator.files_were_uploaded(
1338 FILENAME, self.aggregator.upload_done) + "\n" +
1339 aggregator.files_were_downloaded(
1340 FILENAME2, self.aggregator.download_done))
1341
1342 result = self.aggregator.get_final_status_message()
1343 self.assertEqual(expected, result)
1344
1345 def test_get_final_status_message_no_uploads(self):
1346 """The final status message when there were no uploads."""
1347 done = (0, 12)
1348 self.aggregator.upload_done, self.aggregator.download_done = done
1349 self.aggregator.downloading_filename = FILENAME2
1350
1351 expected = (
1352 aggregator.FINAL_COMPLETED + "\n" +
1353 aggregator.files_were_downloaded(
1354 FILENAME2, self.aggregator.download_done))
1355
1356 result = self.aggregator.get_final_status_message()
1357 self.assertEqual(expected, result)
1358
1359 def test_get_final_status_message_no_downloads(self):
1360 """The final status message when there were no downloads."""
1361 done = (8, 0)
1362 self.aggregator.upload_done, self.aggregator.download_done = done
1363 self.aggregator.uploading_filename = FILENAME
1364
1365 expected = (
1366 aggregator.FINAL_COMPLETED + "\n" +
1367 aggregator.files_were_uploaded(
1368 FILENAME, self.aggregator.upload_done))
1369
1370 result = self.aggregator.get_final_status_message()
1371 self.assertEqual(expected, result)
1372
1373 def test_queue_done_shows_bubble_when_downloads_happened(self):
1374 """On queue done, show final bubble if downloads happened."""
1375 fc = FakeCommand()
1376 self.status_frontend.download_started(fc)
1377 self.status_frontend.download_finished(fc)
1378 old_final_bubble = self.aggregator.final_status_bubble
1379 self.aggregator.queue_done()
1380 self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1381 self.assertTrue(old_final_bubble.shown)
1382
1383 def test_queue_done_shows_bubble_when_uploads_happened(self):
1384 """On queue done, show final bubble if uploads happened."""
1385 fc = FakeCommand()
1386 self.status_frontend.upload_started(fc)
1387 self.status_frontend.upload_finished(fc)
1388 old_final_bubble = self.aggregator.final_status_bubble
1389 self.aggregator.queue_done()
1390 self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1391 self.assertTrue(old_final_bubble.shown)
1392
1393 def test_queue_done_shows_bubble_only_after_delay(self):
1394 """On queue_done, show final bubble only after a delay."""
1395 fc = FakeCommand()
1396 self.status_frontend.upload_started(fc)
1397 self.status_frontend.upload_finished(fc)
1398 old_final_bubble = self.aggregator.final_status_bubble
1399 self.aggregator.queue_done()
1400 self.assertFalse(old_final_bubble.shown)
1401 self.aggregator.clock.advance(self.aggregator.finished_delay - 1)
1402 self.assertFalse(old_final_bubble.shown)
1403 self.aggregator.queue_done()
1404 self.assertFalse(old_final_bubble.shown)
1405 self.aggregator.clock.advance(2)
1406 self.assertFalse(old_final_bubble.shown)
1407 self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1408 self.assertTrue(old_final_bubble.shown)
1409
1410 def test_queue_done_does_not_show_bubble_when_no_transfers_happened(self):
1411 """On queue done, don't show final bubble if no transfers happened."""
1412 fc = FakeCommand()
1413 self.status_frontend.upload_started(fc)
1414 old_final_bubble = self.aggregator.final_status_bubble
1415 self.aggregator.queue_done()
1416 self.assertFalse(old_final_bubble.shown)
1417
1418 def test_queue_done_resets_status_and_hides_progressbar(self):
1419 """On queue done, reset counters and hide progressbar."""
1420 fc = FakeCommand()
1421 self.status_frontend.upload_started(fc)
1422 self.aggregator.queue_done()
1423 self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1424 self.assertStatusReset()
1425 self.assertEqual(0.0, self.aggregator.progress_bar.progress)
1426 self.assertFalse(self.aggregator.progress_bar.visible)
1427
1428 def test_download_started_cancels_timer(self):
1429 """Starting a download cancels the queue_done timer."""
1430 fc = FakeCommand()
1431 self.status_frontend.download_started(fc)
1432 self.aggregator.clock.advance(self.aggregator.finished_delay)
1433 self.status_frontend.download_finished(fc)
1434 self.aggregator.queue_done()
1435 self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1436 fc2 = FakeCommand()
1437 self.status_frontend.download_started(fc2)
1438 self.assertIdentical(self.aggregator.queue_done_timer, None)
1439 self.aggregator.clock.advance(self.aggregator.finished_delay)
1440 self.status_frontend.download_finished(fc2)
1441
1442 def test_upload_started_cancels_timer(self):
1443 """Starting an upload cancels the queue_done timer."""
1444 fc = FakeCommand()
1445 self.status_frontend.upload_started(fc)
1446 self.aggregator.clock.advance(self.aggregator.finished_delay)
1447 self.status_frontend.upload_finished(fc)
1448 self.aggregator.queue_done()
1449 self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1450 fc2 = FakeCommand()
1451 self.status_frontend.upload_started(fc2)
1452 self.assertIdentical(self.aggregator.queue_done_timer, None)
1453 self.aggregator.clock.advance(self.aggregator.finished_delay)
1454 self.status_frontend.upload_finished(fc2)
1455
1456
1457class StatusGrouperTestCase(TestCase):
1458 """Tests for the group_statuses function."""
1459
1460 def test_group_status(self):
1461 """The status grouper sorts and groups by weight."""
1462 status99 = FakeStatus(99)
1463 statuses = [
1464 status99,
1465 status99,
1466 FakeStatus(12),
1467 FakeStatus(1)]
1468
1469 result = [list(k) for _, k in aggregator.group_statuses(statuses)]
1470 expected = [
1471 [statuses[3]],
1472 [statuses[2]],
1473 [status99, status99]]
1474
1475 self.assertEqual(result, expected)
1476
1477
1478class HundredFeetTestCase(TestCase):
1479 """Try to make all parts work together."""
1480
1481 def test_all_together_now(self):
1482 """Make all parts work together."""
1483 self.patch(aggregator, "ToggleableNotification",
1484 FakeNotificationSingleton())
1485 self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1486 self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1487 clock = PatchedClock()
1488 upload = FakeCommand(path='upload.foo')
1489 sf = aggregator.StatusFrontend(clock=clock)
1490 sf.server_connection_made()
1491 sf.set_show_all_notifications(True)
1492
1493 # the progress bar is not visible yet
1494 self.assertFalse(sf.aggregator.progress_bar.visible)
1495 sf.upload_started(upload)
1496 # the progress bar is now shown
1497 self.assertTrue(sf.aggregator.progress_bar.visible)
1498 notifications_shown = (sf.aggregator.file_discovery_bubble.
1499 notification.notifications_shown)
1500 # no notifications shown yet
1501 self.assertEqual(0, len(notifications_shown))
1502 clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1503 # files found notification
1504 self.assertEqual(1, len(notifications_shown))
1505 download = FakeCommand('download.bar')
1506 sf.download_started(download)
1507 self.assertEqual(1, len(notifications_shown))
1508 # the progress still is zero
1509 self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1510 clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1511 # files count update
1512 self.assertEqual(2, len(notifications_shown))
1513 clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1514 aggregator.FileDiscoveryUpdateState.updates_delay)
1515 sf.upload_finished(upload)
1516 sf.download_finished(download)
1517 # the progress still is now 100%
1518 self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1519 sf.queue_done()
1520 clock.advance(sf.aggregator.finished_delay + 1)
1521 self.assertEqual(3, len(notifications_shown))
1522
1523 def test_all_together_now_off(self):
1524 """Make all parts work together, but with notifications off."""
1525 self.patch(aggregator, "ToggleableNotification",
1526 FakeNotificationSingleton())
1527 self.patch(aggregator, "UbuntuOneLauncher", FakeLauncher)
1528 self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1529 clock = PatchedClock()
1530 upload = FakeCommand('upload.foo')
1531 sf = aggregator.StatusFrontend(clock=clock)
1532 sf.set_show_all_notifications(False)
1533
1534 # the progress bar is not visible yet
1535 self.assertFalse(sf.aggregator.progress_bar.visible)
1536 sf.upload_started(upload)
1537 # the progress bar is now shown
1538 self.assertTrue(sf.aggregator.progress_bar.visible)
1539 notifications_shown = (sf.aggregator.file_discovery_bubble.
1540 notification.notifications_shown)
1541 # no notifications shown, never
1542 self.assertEqual(0, len(notifications_shown))
1543 clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1544 self.assertEqual(0, len(notifications_shown))
1545 download = FakeCommand('download.bar')
1546 sf.download_started(download)
1547 self.assertEqual(0, len(notifications_shown))
1548 # the progress still is zero
1549 self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1550 clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1551 self.assertEqual(0, len(notifications_shown))
1552 clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1553 aggregator.FileDiscoveryUpdateState.updates_delay)
1554 sf.upload_finished(upload)
1555 sf.download_finished(download)
1556 # the progress still is now 100%
1557 self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1558 self.assertEqual(0, len(notifications_shown))
1559 sf.queue_done()
1560 self.assertEqual(0, len(notifications_shown))
01561
=== added directory '.pc/03_reset_notify_name.patch/ubuntuone'
=== added directory '.pc/03_reset_notify_name.patch/ubuntuone/status'
=== added file '.pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py'
--- .pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py 1970-01-01 00:00:00 +0000
+++ .pc/03_reset_notify_name.patch/ubuntuone/status/aggregator.py 2011-12-14 21:11:28 +0000
@@ -0,0 +1,882 @@
1# ubuntuone.status.aggregator
2#
3# Author: Alejandro J. Cura <alecu@canonical.com>
4#
5# Copyright 2011 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Aggregate status events."""
19
20import itertools
21import operator
22import os
23
24
25import gettext
26
27from twisted.internet import reactor, defer
28
29from ubuntuone.clientdefs import GETTEXT_PACKAGE
30from ubuntuone.status.logger import logger
31from ubuntuone.platform import session
32from ubuntuone.platform.notification import Notification
33from ubuntuone.platform.messaging import Messaging
34from ubuntuone.platform.launcher import UbuntuOneLauncher, DummyLauncher
35
36ONE_DAY = 24 * 60 * 60
37Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string)
38
39UBUNTUONE_TITLE = Q_("Ubuntu One")
40NEW_UDFS_SENDER = Q_("New cloud folder(s) available")
41FINAL_COMPLETED = Q_("File synchronization completed.")
42
43PROGRESS_COMPLETED = Q_("%(percentage_completed)d%% completed.")
44FILE_SYNC_IN_PROGRESS = Q_("File synchronization in progress")
45
46SHARE_QUOTA_EXCEEDED = Q_(
47 'There is no available space on the folder:\n"%s" shared by %s')
48
49
50def alert_user():
51 """Set the launcher to urgent to alert the user."""
52 launcher = UbuntuOneLauncher()
53 launcher.set_urgent()
54
55
56def files_being_uploaded(filename, files_uploading):
57 """Get the i18n string for files being uploaded."""
58 other_files = files_uploading - 1
59 if other_files < 1:
60 return Q_(
61 "'%(filename)s' is being uploaded to your personal cloud.") % {
62 'filename': filename}
63 format_args = {
64 "filename": filename, "other_files": other_files}
65 return gettext.dngettext(
66 GETTEXT_PACKAGE,
67 "'%(filename)s' and %(other_files)d other file are being "
68 "uploaded to your personal cloud.",
69 "'%(filename)s' and %(other_files)d other files are being "
70 "uploaded to your personal cloud.", other_files) % format_args
71
72
73def files_being_downloaded(filename, files_downloading):
74 """Get the i18n string for files being downloaded."""
75 other_files = files_downloading - 1
76 if other_files < 1:
77 return Q_(
78 "'%(filename)s' is being downloaded to your computer.") % {
79 'filename': filename}
80 format_args = {
81 "filename": filename, "other_files": other_files}
82 return gettext.dngettext(
83 GETTEXT_PACKAGE,
84 "'%(filename)s' and %(other_files)d other file are being "
85 "downloaded to your computer.",
86 "'%(filename)s' and %(other_files)d other files are being "
87 "downloaded to your computer.", other_files) % format_args
88
89
90def files_were_uploaded(filename, upload_done):
91 """Get the i18n string for files that were uploaded."""
92 other_files = upload_done - 1
93 if other_files < 1:
94 return Q_(
95 "'%(filename)s' was uploaded to your personal cloud.") % {
96 'filename': filename}
97 format_args = {
98 'filename': filename, 'other_files': other_files}
99 return gettext.dngettext(
100 GETTEXT_PACKAGE,
101 "'%(filename)s' and %(other_files)d other file were uploaded to "
102 "your personal cloud.",
103 "'%(filename)s' and %(other_files)d other files were uploaded "
104 "to your personal cloud.", other_files) % format_args
105
106
107def files_were_downloaded(filename, download_done):
108 """Get the i18n string for files that were downloaded."""
109 other_files = download_done - 1
110 if other_files < 1:
111 return Q_(
112 "'%(filename)s' was downloaded to your computer.") % {
113 'filename': filename}
114 format_args = {
115 'filename': filename, 'other_files': other_files}
116 return gettext.dngettext(
117 GETTEXT_PACKAGE,
118 "'%(filename)s' and %(other_files)d other file were "
119 "downloaded to your computer.",
120 "'%(filename)s' and %(other_files)d other files were "
121 "downloaded to your computer.", other_files) % format_args
122
123
124class ToggleableNotification(object):
125 """A controller for notifications that can be turned off."""
126
127 def __init__(self, notification_switch):
128 """Initialize this instance."""
129 self.notification_switch = notification_switch
130 self.notification = Notification()
131
132 def send_notification(self, *args):
133 """Passthru the notification."""
134 if self.notification_switch.enabled:
135 return self.notification.send_notification(*args)
136
137
138class NotificationSwitch(object):
139 """A switch that turns notifications on and off."""
140
141 enabled = True
142
143 def __init__(self):
144 self.toggleable_notification = ToggleableNotification(self)
145
146 def get_notification(self):
147 """Return a new notification instance."""
148 return self.toggleable_notification
149
150 def enable_notifications(self):
151 """Turn the switch on."""
152 self.enabled = True
153
154 def disable_notifications(self):
155 """Turn the switch off."""
156 self.enabled = False
157
158
159class StatusEvent(object):
160 """An event representing a status change."""
161
162 MESSAGE_ONE = None # to be defined in child classes
163 WEIGHT = 99
164 DO_NOT_INSTANCE = "Do not instance this class, only children."""
165
166 def __init__(self, **kwargs):
167 """Initialize this instance."""
168 assert type(self) != StatusEvent, self.DO_NOT_INSTANCE
169 self.kwargs = kwargs
170
171 def one(self):
172 """A message if this is the only event of this type."""
173 return self.MESSAGE_ONE
174
175
176class FilePublishingStatus(StatusEvent):
177 """Files that are made public with a url."""
178
179 MESSAGE_ONE = Q_("A file was just made public at %(new_public_url)s")
180
181 WEIGHT = 50
182
183 def one(self):
184 """Show the url if only one event of this type."""
185 return self.MESSAGE_ONE % self.kwargs
186
187 def many(self, events):
188 """Show the number of files if many event of this type."""
189 no_of_files = len(events)
190 gettext.dngettext(
191 GETTEXT_PACKAGE,
192 "%(event_count)d file was just made public.",
193 "%(event_count)d files were just made public.",
194 no_of_files) % {'event_count': no_of_files}
195
196
197class FileUnpublishingStatus(StatusEvent):
198 """Files that have stopped being published."""
199
200 MESSAGE_ONE = Q_("A file is no longer published")
201 WEIGHT = 51
202
203 def many(self, events):
204 """Show the number of files if many event of this type."""
205 no_of_files = len(events)
206 gettext.dngettext(
207 GETTEXT_PACKAGE,
208 "%(event_count)d file is no longer published.",
209 "%(event_count)d files are no longer published.",
210 no_of_files) % {'event_count': no_of_files}
211
212
213class FolderAvailableStatus(StatusEvent):
214 """Folders available for subscription."""
215
216 WEIGHT = 60
217
218 def many(self, events):
219 """Show the number of files if many event of this type."""
220 no_of_files = len(events)
221 gettext.dngettext(
222 GETTEXT_PACKAGE,
223 "Found %(event_count)d new cloud folder.",
224 "Found %(event_count)d new cloud folders.",
225 no_of_files) % {'event_count': no_of_files}
226
227
228class ShareAvailableStatus(FolderAvailableStatus):
229 """A Share is available for subscription."""
230
231 MESSAGE_ONE = Q_("New cloud folder available: '%(folder_name)s' "
232 "shared by %(other_user_name)s")
233
234 def one(self):
235 """Show the folder information."""
236 volume = self.kwargs["share"]
237 format_args = {
238 "folder_name": volume.name,
239 "other_user_name": volume.other_visible_name,
240 }
241 return self.MESSAGE_ONE % format_args
242
243
244class UDFAvailableStatus(FolderAvailableStatus):
245 """An UDF is available for subscription."""
246
247 MESSAGE_ONE = Q_("New cloud folder available: '%(folder_name)s'")
248
249 def one(self):
250 """Show the folder information."""
251 volume = self.kwargs["udf"]
252 format_args = {"folder_name": volume.suggested_path}
253 return self.MESSAGE_ONE % format_args
254
255
256class ConnectionStatusEvent(StatusEvent):
257 """The connection to the server changed status."""
258
259 WEIGHT = 30
260
261 def many(self, events):
262 """Only the last message if there are many events of this type."""
263 return events[-1].one()
264
265
266class ConnectionLostStatus(ConnectionStatusEvent):
267 """The connection to the server was lost."""
268
269 MESSAGE_ONE = Q_("The connection to the server was lost.")
270
271
272class ConnectionMadeStatus(ConnectionStatusEvent):
273 """The connection to the server was made."""
274
275 MESSAGE_ONE = Q_("The connection to the server was restored.")
276
277
278class Timer(defer.Deferred):
279 """A deferred that fires past a given delay."""
280
281 def __init__(self, delay, clock=reactor):
282 """Initialize this instance."""
283 defer.Deferred.__init__(self)
284 self.clock = clock
285 self.delay = delay
286 self.delay_call = self.clock.callLater(delay, self.callback)
287
288 def cancel_if_active(self, call):
289 """Cancel a call if it is active."""
290 if call.active():
291 call.cancel()
292
293 def cleanup(self):
294 """Cancel all active calls."""
295 self.cancel_if_active(self.delay_call)
296
297 def callback(self, result=None):
298 """Make sure the timers are stopped when firing the callback."""
299 self.cleanup()
300 defer.Deferred.callback(self, result)
301
302 def reset(self):
303 """Reset the delay."""
304 if not self.called:
305 self.delay_call.reset(self.delay)
306
307 @property
308 def active(self):
309 """Is the delay still active."""
310 return self.delay_call.active()
311
312
313class DeadlineTimer(Timer):
314 """A Timer with a deadline."""
315
316 def __init__(self, delay, timeout=None, clock=reactor):
317 """Initialize this instance."""
318 Timer.__init__(self, delay, clock)
319 self.timeout = timeout
320 self.timeout_call = self.clock.callLater(timeout, self.callback)
321
322 def cleanup(self):
323 """Cancel all active calls."""
324 Timer.cleanup(self)
325 self.cancel_if_active(self.timeout_call)
326
327
328class FileDiscoveryBaseState(object):
329 """States for file discovery bubble."""
330
331 def __init__(self, bubble):
332 """Initialize this instance."""
333 self.bubble = bubble
334 self.clock = bubble.clock
335
336 def new_file_found(self):
337 """New files found."""
338
339 def cleanup(self):
340 """Cleanup this instance."""
341
342
343class FileDiscoveryIdleState(FileDiscoveryBaseState):
344 """Waiting for first file to appear."""
345
346 def new_file_found(self):
347 """New files found."""
348 self.bubble._start()
349
350
351class FileDiscoveryGatheringState(FileDiscoveryBaseState):
352 """Files are gathered then a notification is shown."""
353
354 initial_delay = 0.5
355 initial_timeout = 3.0
356
357 def __init__(self, *args):
358 """Initialize this instance."""
359 super(FileDiscoveryGatheringState, self).__init__(*args)
360 self.timer = DeadlineTimer(self.initial_delay,
361 self.initial_timeout,
362 clock=self.clock)
363 self.timer.addCallback(self._timeout)
364
365 def _timeout(self, result):
366 """Show the notification bubble."""
367 self.cleanup()
368 self.bubble._popup()
369
370 def new_file_found(self):
371 """New files found."""
372 self.timer.reset()
373
374 def cleanup(self):
375 """Cleanup this instance."""
376 self.timer.cleanup()
377
378
379class FileDiscoveryUpdateState(FileDiscoveryBaseState):
380 """The bubble is updated if more files are found."""
381
382 updates_delay = 0.5
383 updates_timeout = 10.0
384
385 def __init__(self, *args):
386 """Initialize this instance."""
387 super(FileDiscoveryUpdateState, self).__init__(*args)
388 self.main_timer = Timer(self.updates_timeout, clock=self.clock)
389 self.main_timer.addCallback(self._timeout)
390 self.updates_timer = None
391
392 def _timeout(self, result):
393 """No more updates on the notification bubble."""
394 self.cleanup()
395 self.bubble.start_sleeping()
396
397 def _update(self, result):
398 """The bubble should be updated."""
399 self.bubble._update()
400
401 def new_file_found(self):
402 """New files found."""
403 if self.updates_timer is None:
404 self.updates_timer = Timer(self.updates_delay, clock=self.clock)
405 self.updates_timer.addCallback(self._update)
406
407 def cleanup(self):
408 """Clean up the timers."""
409 self.main_timer.cleanup()
410 if self.updates_timer:
411 self.updates_timer.cleanup()
412
413
414class FileDiscoverySleepState(FileDiscoveryBaseState):
415 """The bubble is not updated while sleeping."""
416
417 sleep_delay = 300.0
418
419 def __init__(self, *args):
420 """Initialize this instance."""
421 super(FileDiscoverySleepState, self).__init__(*args)
422 self.main_timer = Timer(self.sleep_delay, clock=self.clock)
423 self.main_timer.addCallback(self._timeout)
424
425 def _timeout(self, result):
426 """Move the notification to the idle state."""
427 self.bubble._set_idle()
428
429 def cleanup(self):
430 """Clean up the timers."""
431 self.main_timer.cleanup()
432
433
434class FileDiscoveryBubble(object):
435 """
436 Show a notification for file discovery.
437
438 Waits 3 seconds for the file count to coalesce, then pops up a
439 notification. If new files are found the notification is updated,
440 but twice per second at most, and for up to 10 seconds.
441 Finally, sleeps for 10 minutes so it does not get annoying.
442 """
443
444 state = None
445
446 def __init__(self, status_aggregator, clock=reactor):
447 """Initialize this instance."""
448 self.connected = False
449 self.files_found = False
450 self.clock = clock
451 self.status_aggregator = status_aggregator
452 self._set_idle()
453 self.notification = None
454
455 def _change_state(self, new_state_class):
456 """Change to a new state."""
457 if self.state:
458 self.state.cleanup()
459 self.state = new_state_class(self)
460
461 def _set_idle(self):
462 """Reset this bubble to the initial state."""
463 self._change_state(FileDiscoveryIdleState)
464
465 def _start(self):
466 """The first file was found, so start gathering."""
467 self.notification = self.status_aggregator.get_notification()
468 self._change_state(FileDiscoveryGatheringState)
469
470 def _popup(self):
471 """Display the notification."""
472 if not self.connected:
473 return
474 text = self.status_aggregator.get_discovery_message()
475 if text:
476 self.notification.send_notification(UBUNTUONE_TITLE, text)
477 logger.debug("notification shown: %s", text)
478 self._change_state(FileDiscoveryUpdateState)
479
480 def _update(self):
481 """Update the notification."""
482 if not self.connected:
483 return
484 text = self.status_aggregator.get_discovery_message()
485 if text:
486 logger.debug("notification updated: %s", text)
487 self.notification.send_notification(UBUNTUONE_TITLE, text)
488
489 def start_sleeping(self):
490 """Wait for 10 minutes before annoying again."""
491 self._change_state(FileDiscoverySleepState)
492
493 def cleanup(self):
494 """Cleanup this instance."""
495 self.state.cleanup()
496
497 def connection_made(self):
498 """Connection made."""
499 self.connected = True
500 if self.files_found:
501 self._popup()
502
503 def connection_lost(self):
504 """Connection lost."""
505 self.connected = False
506
507 def new_file_found(self):
508 """New files found."""
509 self.files_found = True
510 self.state.new_file_found()
511
512
513class ProgressBar(object):
514 """Update a progressbar no more than 10 times a second."""
515 pulsating = True
516 visible = False
517 progress = 0.0
518 updates_delay = 0.1
519 timer = None
520 inhibitor_defer = None
521
522 def __init__(self, clock=reactor):
523 """Initialize this instance."""
524 self.clock = clock
525 try:
526 self.launcher = UbuntuOneLauncher()
527 except TypeError:
528 # Unity GIR can cause a TypeError here so we should not fail
529 self.launcher = DummyLauncher()
530
531 def cleanup(self):
532 """Cleanup this instance."""
533 if self.timer:
534 self.timer.cleanup()
535 self.timer = None
536
537 def _timeout(self, result):
538 """The aggregating timer has expired, so update the UI."""
539 self.timer = None
540 self.launcher.set_progress(self.progress)
541 logger.debug("progressbar updated: %f", self.progress)
542
543 def set_progress(self, progress):
544 """Steps amount changed. Set up a timer if there isn't one ticking."""
545 self.progress = progress
546 if not self.visible:
547 self.visible = True
548 self.launcher.show_progressbar()
549 logger.debug("progressbar shown")
550 if self.inhibitor_defer is None:
551 self.inhibitor_defer = session.inhibit_logout_suspend(
552 FILE_SYNC_IN_PROGRESS)
553 if not self.timer:
554 self.timer = Timer(self.updates_delay, clock=self.clock)
555 self.timer.addCallback(self._timeout)
556
557 def completed(self):
558 """All has completed."""
559 self.cleanup()
560 self.visible = False
561 self.launcher.hide_progressbar()
562 logger.debug("progressbar hidden")
563 if self.inhibitor_defer is not None:
564
565 def inhibitor_callback(inhibitor):
566 """The inhibitor was found, so cancel it."""
567 self.inhibitor_defer = None
568 return inhibitor.cancel()
569
570 self.inhibitor_defer.addCallback(inhibitor_callback)
571
572
573class FinalStatusBubble(object):
574 """Final bubble that shows the status of transfers."""
575
576 notification = None
577
578 def __init__(self, status_aggregator):
579 """Initialize this instance."""
580 self.status_aggregator = status_aggregator
581
582 def cleanup(self):
583 """Clean up this instance."""
584
585 def show(self):
586 """Show the final status notification."""
587 self.notification = self.status_aggregator.get_notification()
588 text = self.status_aggregator.get_final_status_message()
589 self.notification.send_notification(UBUNTUONE_TITLE, text)
590
591
592def group_statuses(status_events):
593 """Groups statuses by weight."""
594 weight_getter = operator.attrgetter("WEIGHT")
595 sorted_status_events = sorted(status_events, key=weight_getter)
596 return itertools.groupby(sorted_status_events, weight_getter)
597
598
599class StatusAggregator(object):
600 """The status aggregator backend."""
601
602 file_discovery_bubble = None
603 final_status_bubble = None
604
605 def __init__(self, clock=reactor):
606 """Initialize this instance."""
607 self.clock = clock
608 self.notification_switch = NotificationSwitch()
609 self.queue_done_timer = None
610 self.reset()
611 self.progress_bar = ProgressBar(clock=self.clock)
612 self.finished_delay = 10
613 self.progress = {}
614 self.to_do = {}
615
616 def get_notification(self):
617 """Create a new toggleable notification object."""
618 return self.notification_switch.get_notification()
619
620 # pylint: disable=W0201
621 def reset(self):
622 """Reset all counters and notifications."""
623 self.download_done = 0
624 self.upload_done = 0
625 self.files_uploading = []
626 self.uploading_filename = ''
627 self.files_downloading = []
628 self.downloading_filename = ''
629 if self.queue_done_timer is not None:
630 self.queue_done_timer.cleanup()
631 self.queue_done_timer = None
632
633 if self.file_discovery_bubble:
634 self.file_discovery_bubble.cleanup()
635 self.file_discovery_bubble = FileDiscoveryBubble(self,
636 clock=self.clock)
637
638 if self.final_status_bubble:
639 self.final_status_bubble.cleanup()
640 self.final_status_bubble = FinalStatusBubble(self)
641 self.progress = {}
642 self.to_do = {}
643 # pylint: enable=W0201
644
645 def get_discovery_message(self):
646 """Get the text for the discovery bubble."""
647 lines = []
648 files_uploading = len(self.files_uploading)
649 if files_uploading > 0:
650 lines.append(files_being_uploaded(
651 self.uploading_filename, files_uploading))
652 files_downloading = len(self.files_downloading)
653 if files_downloading > 0:
654 self.downloading_filename = self.files_downloading[0].path.split(
655 os.path.sep)[-1]
656 lines.append(files_being_downloaded(
657 self.downloading_filename, files_downloading))
658 return "\n".join(lines)
659
660 def get_final_status_message(self):
661 """Get some lines describing all we did."""
662 parts = []
663 parts.append(FINAL_COMPLETED)
664 upload_done = self.upload_done
665 if upload_done:
666 parts.append(files_were_uploaded(
667 self.uploading_filename, upload_done))
668
669 download_done = self.download_done
670 if download_done:
671 parts.append(files_were_downloaded(
672 self.downloading_filename, download_done))
673 return "\n".join(parts)
674
675 def _queue_done(self, _):
676 """Show final bubble and reset counters."""
677 self.queue_done_timer.cleanup()
678 self.queue_done_timer = None
679 logger.debug("queue done callback fired")
680 if self.upload_done + self.download_done > 0:
681 self.final_status_bubble.show()
682 self.progress_bar.completed()
683 self.reset()
684
685 def queue_done(self):
686 """Queue is finished."""
687 if not self.to_do:
688 return
689 if self.queue_done_timer is None:
690 logger.debug("queue done callback added")
691 self.queue_done_timer = Timer(
692 self.finished_delay, clock=self.clock)
693 self.queue_done_timer.addCallback(self._queue_done)
694 return
695 logger.debug("queue done callback reset")
696 self.queue_done_timer.reset()
697
698 def update_progressbar(self):
699 """Update the counters of the progressbar."""
700 if len(self.to_do) > 0:
701 progress = float(
702 sum(self.progress.values())) / sum(self.to_do.values())
703 self.progress_bar.set_progress(progress)
704
705 def download_started(self, command):
706 """A download just started."""
707 if self.queue_done_timer is not None:
708 self.queue_done_timer.cleanup()
709 self.queue_done_timer = None
710 self.files_downloading.append(command)
711 if command.deflated_size is not None:
712 self.to_do[
713 (command.share_id, command.node_id)] = command.deflated_size
714 # pylint: disable=W0201
715 if not self.downloading_filename:
716 self.downloading_filename = self.files_downloading[0].path.split(
717 os.path.sep)[-1]
718 # pylint: enable=W0201
719 self.update_progressbar()
720 logger.debug(
721 "queueing command (total: %d): %s",
722 len(self.to_do), command.__class__.__name__)
723 self.file_discovery_bubble.new_file_found()
724
725 def download_finished(self, command):
726 """A download just finished."""
727 if command in self.files_downloading:
728 self.files_downloading.remove(command)
729 self.download_done += 1
730 if command.deflated_size is not None:
731 self.progress[
732 (command.share_id, command.node_id)] = command.deflated_size
733 logger.debug("unqueueing command: %s", command.__class__.__name__)
734 self.update_progressbar()
735
736 def upload_started(self, command):
737 """An upload just started."""
738 if self.queue_done_timer is not None:
739 self.queue_done_timer.cleanup()
740 self.queue_done_timer = None
741 self.files_uploading.append(command)
742 if command.deflated_size is not None:
743 self.to_do[
744 (command.share_id, command.node_id)] = command.deflated_size
745 # pylint: disable=W0201
746 if not self.uploading_filename:
747 self.uploading_filename = self.files_uploading[0].path.split(
748 os.path.sep)[-1]
749 # pylint: enable=W0201
750 self.update_progressbar()
751 logger.debug(
752 "queueing command (total: %d): %s", len(self.to_do),
753 command.__class__.__name__)
754 self.file_discovery_bubble.new_file_found()
755
756 def upload_finished(self, command):
757 """An upload just finished."""
758 if command in self.files_uploading:
759 self.files_uploading.remove(command)
760 self.upload_done += 1
761 if command.deflated_size is not None:
762 self.progress[
763 (command.share_id, command.node_id)] = command.deflated_size
764 logger.debug("unqueueing command: %s", command.__class__.__name__)
765 self.update_progressbar()
766
767 def progress_made(self, share_id, node_id, n_bytes_written, deflated_size):
768 """Progress made on up- or download."""
769 if n_bytes_written is not None:
770 # if we haven't gotten the total size yet, set it now
771 if deflated_size and (share_id, node_id) not in self.to_do:
772 self.to_do[(share_id, node_id)] = deflated_size
773 self.progress[(share_id, node_id)] = n_bytes_written
774 self.update_progressbar()
775
776 def connection_lost(self):
777 """The connection to the server was lost."""
778 self.file_discovery_bubble.connection_lost()
779
780 def connection_made(self):
781 """The connection to the server was made."""
782 self.file_discovery_bubble.connection_made()
783
784
785class StatusFrontend(object):
786 """Frontend for the status aggregator, used by the StatusListener."""
787
788 def __init__(self, clock=reactor):
789 """Initialize this instance."""
790 self.aggregator = StatusAggregator(clock=clock)
791 self.notification = self.aggregator.get_notification()
792 self.messaging = Messaging()
793 self.quota_timer = None
794
795 def file_published(self, public_url):
796 """A file was published."""
797 status_event = FilePublishingStatus(new_public_url=public_url)
798 self.notification.send_notification(
799 UBUNTUONE_TITLE, status_event.one())
800
801 def file_unpublished(self, public_url): # pylint: disable=W0613
802 """A file was unpublished."""
803 self.notification.send_notification(
804 UBUNTUONE_TITLE, FileUnpublishingStatus().one())
805
806 def download_started(self, command):
807 """A file was queued for download."""
808 self.aggregator.download_started(command)
809
810 def download_finished(self, command):
811 """A file download was unqueued."""
812 self.aggregator.download_finished(command)
813
814 def upload_started(self, command):
815 """A file was queued for upload."""
816 self.aggregator.upload_started(command)
817
818 def upload_finished(self, command):
819 """A file upload was unqueued."""
820 self.aggregator.upload_finished(command)
821
822 def progress_made(self, share_id, node_id, n_bytes_written, deflated_size):
823 """Progress made on up- or download."""
824 self.aggregator.progress_made(
825 share_id, node_id, n_bytes_written, deflated_size)
826
827 def queue_done(self):
828 """The queue is empty."""
829 self.aggregator.queue_done()
830
831 def new_share_available(self, share):
832 """A new share is available for subscription."""
833 self.messaging.show_message(share.other_visible_name)
834 self.notification.send_notification(
835 UBUNTUONE_TITLE, ShareAvailableStatus(share=share).one())
836
837 def new_udf_available(self, udf):
838 """A new udf is available for subscription."""
839 if udf.subscribed:
840 return
841 self.notification.send_notification(
842 UBUNTUONE_TITLE, UDFAvailableStatus(udf=udf).one())
843
844 def server_connection_lost(self):
845 """The client lost the connection to the server."""
846 logger.debug("server connection lost")
847 self.aggregator.connection_lost()
848
849 def server_connection_made(self):
850 """The client made the connection to the server."""
851 logger.debug("server connection made")
852 self.aggregator.connection_made()
853
854 def udf_quota_exceeded(self, volume_dict):
855 """Quota exceeded in UDF."""
856 logger.debug("UDF quota exceeded for volume %r." % volume_dict)
857 alert_user()
858
859 def share_quota_exceeded(self, volume_dict):
860 """Sharing user's quota exceeded in share."""
861 logger.debug("Share quota exceeded for volume %r." % volume_dict)
862 if self.quota_timer is not None:
863 if self.quota_timer.active:
864 return
865 else:
866 self.quota_timer = Timer(ONE_DAY, clock=self.aggregator.clock)
867 self.notification.send_notification(
868 UBUNTUONE_TITLE, SHARE_QUOTA_EXCEEDED % (
869 volume_dict['path'], volume_dict['other_visible_name']))
870 alert_user()
871
872 def root_quota_exceeded(self, volume_dict):
873 """Quota exceeded in root."""
874 logger.debug("Root quota exceeded for volume %r." % volume_dict)
875 alert_user()
876
877 def set_show_all_notifications(self, value):
878 """Set the flag to show all notifications."""
879 if value:
880 self.aggregator.notification_switch.enable_notifications()
881 else:
882 self.aggregator.notification_switch.disable_notifications()
0883
=== added file 'debian/patches/03_reset_notify_name.patch'
--- debian/patches/03_reset_notify_name.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/03_reset_notify_name.patch 2011-12-14 21:11:28 +0000
@@ -0,0 +1,74 @@
1=== modified file 'tests/status/test_aggregator.py'
2--- old/tests/status/test_aggregator.py 2011-10-27 13:47:09 +0000
3+++ new/tests/status/test_aggregator.py 2011-12-07 20:41:48 +0000
4@@ -1327,6 +1327,26 @@
5 result = self.aggregator.get_discovery_message()
6 self.assertEqual(expected, result)
7
8+ def test_get_discovery_message_clears_filenames(self):
9+ """Test the message that's shown on the discovery bubble."""
10+ uploading = 10
11+ downloading = 8
12+ filename = 'upfile0.ext'
13+ filename2 = 'downfile0.ext'
14+ self.aggregator.files_uploading.extend([
15+ FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
16+ self.aggregator.uploading_filename = filename
17+ self.aggregator.files_downloading.extend([
18+ FakeCommand(path='downfile%d.ext' % n) for n in
19+ range(downloading)])
20+ self.aggregator.downloading_filename = 'STALE FILENAME'
21+ self.aggregator.uploading_filename = 'STALE FILENAME'
22+ expected = (
23+ aggregator.files_being_uploaded(filename, uploading) + "\n" +
24+ aggregator.files_being_downloaded(filename2, downloading))
25+ result = self.aggregator.get_discovery_message()
26+ self.assertEqual(expected, result)
27+
28 def test_get_final_status_message(self):
29 """The final status message."""
30 done = (5, 10)
31
32=== modified file 'ubuntuone/status/aggregator.py'
33--- old/ubuntuone/status/aggregator.py 2011-10-21 15:49:18 +0000
34+++ new/ubuntuone/status/aggregator.py 2011-12-12 22:50:55 +0000
35@@ -646,12 +646,14 @@
36 lines = []
37 files_uploading = len(self.files_uploading)
38 if files_uploading > 0:
39+ self.uploading_filename = os.path.basename(
40+ self.files_uploading[0].path)
41 lines.append(files_being_uploaded(
42 self.uploading_filename, files_uploading))
43 files_downloading = len(self.files_downloading)
44 if files_downloading > 0:
45- self.downloading_filename = self.files_downloading[0].path.split(
46- os.path.sep)[-1]
47+ self.downloading_filename = os.path.basename(
48+ self.files_downloading[0].path)
49 lines.append(files_being_downloaded(
50 self.downloading_filename, files_downloading))
51 return "\n".join(lines)
52@@ -712,8 +714,8 @@
53 (command.share_id, command.node_id)] = command.deflated_size
54 # pylint: disable=W0201
55 if not self.downloading_filename:
56- self.downloading_filename = self.files_downloading[0].path.split(
57- os.path.sep)[-1]
58+ self.downloading_filename = os.path.basename(
59+ self.files_downloading[0].path)
60 # pylint: enable=W0201
61 self.update_progressbar()
62 logger.debug(
63@@ -743,8 +745,8 @@
64 (command.share_id, command.node_id)] = command.deflated_size
65 # pylint: disable=W0201
66 if not self.uploading_filename:
67- self.uploading_filename = self.files_uploading[0].path.split(
68- os.path.sep)[-1]
69+ self.uploading_filename = os.path.basename(
70+ self.files_uploading[0].path)
71 # pylint: enable=W0201
72 self.update_progressbar()
73 logger.debug(
74
075
=== 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: