Merge lp:~thisfred/ubuntuone-client/quota-notifications into lp:ubuntuone-client

Proposed by Eric Casteleijn on 2011-03-31
Status: Merged
Approved by: Eric Casteleijn on 2011-04-07
Approved revision: 923
Merged at revision: 946
Proposed branch: lp:~thisfred/ubuntuone-client/quota-notifications
Merge into: lp:ubuntuone-client
Diff against target: 849 lines (+282/-122)
8 files modified
tests/platform/linux/test_unity.py (+12/-30)
tests/platform/windows/test_filesystem_notifications.py (+23/-24)
tests/status/test_aggregator.py (+89/-38)
tests/syncdaemon/test_status_listener.py (+51/-0)
ubuntuone/platform/linux/messaging.py (+37/-2)
ubuntuone/platform/linux/unity.py (+5/-16)
ubuntuone/status/aggregator.py (+41/-12)
ubuntuone/syncdaemon/status_listener.py (+24/-0)
To merge this branch: bzr merge lp:~thisfred/ubuntuone-client/quota-notifications
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve on 2011-04-06
Alejandro J. Cura (community) 2011-03-31 Approve on 2011-04-06
Review via email: mp+55812@code.launchpad.net

Commit message

* Bug #702172: Syncdaemon sends a notification when a folder shared to the user exceeds the owning user's quota
* Bug #702176: Syncdaemon opens the control-panel to volumes when a folder shared to the user exceeds the owning user's quota
* Bug #702183: Syncdaemon opens the control panel in the background and change the launcher icon to urgent when the user exceeds their quota

Description of the change

* Bug #702172: Syncdaemon sends a notification when a folder shared to the user exceeds the owning user's quota
* Bug #702176: Syncdaemon opens the control-panel to volumes when a folder shared to the user exceeds the owning user's quota
* Bug #702183: Syncdaemon opens the control panel in the background and change the launcher icon to urgent when the user exceeds their quota

To post a comment you must log in.
Eric Casteleijn (thisfred) wrote :

manual test instructions:

in tests/platform/linux/test_messaging.py change L99-103 to:

    def test_show_message(self):
        """On message, libnotify receives the proper calls."""
        #self._show_message_setup()
        messaging = Messaging()
        messaging.show_message(FAKE_SENDER) #, callback)
        import pdb; pdb.set_trace()

and then run the tests. When they halt at the pdb statement, your messaging menu will contain one message from 'Mom' (In a second Ubuntu One entry, because we use a different application name for testing.) Clicking this message should open the control panel to the volumes tab, or if it is open already, switch to that panel.

Eric Casteleijn (thisfred) wrote :

To prevent confusion: this is not the way quota notification works in the client: It will not add a message, but rather open the panel directly. This was just the easiest hack to show the functionality.

Eric Casteleijn (thisfred) wrote :

Ok, found a better test for this:

change the method at line 940- of tests/status/test_aggregator.py to this:

    def test_root_quota_exceeded(self): # pylint: disable=R0201
        """Quota exceeded in root."""
        ## mocker = Mocker()
        ## open_volumes = mocker.replace(
        ## "ubuntuone.platform.messaging.open_volumes")
        ## open_volumes()
        ## mocker.replay()
        ROOT_ID = 'fake root id'
        root = Root(volume_id=ROOT_ID)
        self.fakevm.volumes[ROOT_ID] = root
        self.fakevm.root = root
        self.listener.handle_SYS_QUOTA_EXCEEDED(
            volume_id=ROOT_ID, free_bytes=0)
        import pdb; pdb.set_trace()

and run:

PYTHONPATH=. u1trial tests/status/test_aggregator.py

you should now see the panel pop up/switch to volumes.

Eric Casteleijn (thisfred) wrote :

Waiting on bug #747677

Eric Casteleijn (thisfred) wrote :

Changed to use launcher entry urgency hint, so now, when running the above manual test, the launcher entry gets urgentized. To test this, we need these branches of libunity and unity:

lp:~unity-team/unity/unity.remote-urgent/
lp:~unity-team/libunity/libunity.urgent/

920. By Eric Casteleijn on 2011-04-04

changed assertion messages

921. By Eric Casteleijn on 2011-04-04

removed unnecessary main loop

Alejandro J. Cura (alecu) wrote :

Lovely branch! Approved, pending unneeded DBusGMainLoop removal.

review: Approve
922. By Eric Casteleijn on 2011-04-04

don't need dbusgmainloop either

923. By Eric Casteleijn on 2011-04-06

removed emblems and merged trunk

Alejandro J. Cura (alecu) wrote :

Re-approving after the emblems were removed.

review: Approve
Roberto Alsina (ralsina) wrote :

+1 looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/platform/linux/test_unity.py'
2--- tests/platform/linux/test_unity.py 2011-02-21 22:29:50 +0000
3+++ tests/platform/linux/test_unity.py 2011-04-06 12:54:32 +0000
4@@ -28,8 +28,7 @@
5
6 progress = 0.0
7 progress_visible = False
8- emblem = None
9- emblem_visible = False
10+ urgent = False
11
12
13 class FakeLauncherEntry(object):
14@@ -45,23 +44,14 @@
15 self.props = FakeLauncherEntryProps()
16
17
18-class FakeThemedIcon(object):
19- """A fake gio.ThemedIcon."""
20-
21- def __init__(self, name):
22- """Initialize this fake instance."""
23- self.name = name
24-
25-
26 class LauncherProgressbarEmblemTestCase(TestCase):
27 """Test the Launcher progressbar and emblem."""
28-
29+
30 skip = None if unity.use_libunity else "libunity not installed."
31
32 def setUp(self):
33 """Initialize this test instance."""
34 self.patch(unity.Unity, "LauncherEntry", FakeLauncherEntry)
35- self.patch(unity.gio, "ThemedIcon", FakeThemedIcon)
36 self.launcher = unity.UbuntuOneLauncher()
37
38 def test_progress_starts_hidden(self):
39@@ -88,21 +78,13 @@
40 self.launcher.set_progress(value)
41 self.assertEqual(self.launcher.entry.props.progress, value)
42
43- def test_warning_emblem_made_visible(self):
44- """The emblem is made visible."""
45- self.launcher.show_warning_emblem()
46- self.assertTrue(self.launcher.entry.props.emblem_visible,
47- "The emblem is made visible.")
48-
49- def test_right_emblem_displayed(self):
50- """The right emblem is displayed."""
51- emblem_name = unity.EMBLEM_IMPORTANT
52- self.launcher.show_warning_emblem()
53- self.assertEqual(self.launcher.entry.props.emblem.name, emblem_name)
54-
55- def test_emblem_hidden_after_shown(self):
56- """The emblem is hidden after being shown."""
57- self.launcher.show_warning_emblem()
58- self.launcher.hide_emblem()
59- self.assertFalse(self.launcher.entry.props.emblem_visible,
60- "The emblem is hidden.")
61+ def test_urgency_set(self):
62+ """The urgency of the launcher is set."""
63+ self.launcher.set_urgent()
64+ self.assertTrue(
65+ self.launcher.entry.props.urgent,
66+ "The launcher should be set to urgent.")
67+ self.launcher.set_urgent(False)
68+ self.assertFalse(
69+ self.launcher.entry.props.urgent,
70+ "The launcher should not be set to urgent.")
71
72=== modified file 'tests/platform/windows/test_filesystem_notifications.py'
73--- tests/platform/windows/test_filesystem_notifications.py 2011-02-08 06:51:30 +0000
74+++ tests/platform/windows/test_filesystem_notifications.py 2011-04-06 12:54:32 +0000
75@@ -39,7 +39,7 @@
76
77 class TestCaseHandler(ProcessEvent):
78 """ProcessEvent used for test cases."""
79-
80+
81 def my_init(self, **kwargs):
82 """Init the event notifier."""
83 self.processed_events = []
84@@ -88,12 +88,12 @@
85 def test_file_create(self):
86 """Test that the correct event is returned on a file create."""
87 file_name = os.path.join(self.basedir, 'test_file_create')
88-
89+
90 def create_file():
91 """Action used for the test."""
92 # simply create a new file
93 open(file_name, 'w').close()
94-
95+
96 event = self._perform_operations(self.basedir, self.mask, False,
97 create_file, 1)[0]
98 self.assertFalse(event.dir)
99@@ -107,7 +107,7 @@
100 def test_dir_create(self):
101 """Test that the correct event is returned on a dir creation."""
102 dir_name = os.path.join(self.basedir, 'test_dir_create')
103-
104+
105 def create_dir():
106 """Action for the test."""
107 os.mkdir(dir_name)
108@@ -127,11 +127,11 @@
109 file_name = os.path.join(self.basedir, 'test_file_remove')
110 # create the file before recording
111 open(file_name, 'w').close()
112-
113+
114 def remove_file():
115 """Action for the test."""
116 os.remove(file_name)
117-
118+
119 event = self._perform_operations(self.basedir, self.mask, False,
120 remove_file, 1)[0]
121 self.assertFalse(event.dir)
122@@ -151,7 +151,7 @@
123 def remove_dir():
124 """Action for the test."""
125 os.rmdir(dir_name)
126-
127+
128 event = self._perform_operations(self.basedir, self.mask, False,
129 remove_dir, 1)[0]
130 self.assertTrue(event.dir)
131@@ -170,7 +170,7 @@
132 """Action for the test."""
133 fd.write('test')
134 fd.close()
135-
136+
137 event = self._perform_operations(self.basedir, self.mask, False,
138 write_file, 1)[0]
139 self.assertFalse(event.dir)
140@@ -189,9 +189,9 @@
141 'test_file_moved_to_watched_dir_same_watcher')
142 to_file_name = os.path.join(self.basedir,
143 'test_file_moved_to_watched_dir_same_watcher_2')
144- open(from_file_name, 'w').close()
145+ open(from_file_name, 'w').close()
146 #create file before recording
147-
148+
149 def move_file():
150 """Action for the test."""
151 os.rename(from_file_name, to_file_name)
152@@ -214,7 +214,7 @@
153 self.assertEqual('IN_MOVED_TO', move_to_event.maskname)
154 self.assertEqual(os.path.split(to_file_name)[1], move_to_event.name)
155 self.assertEqual('.', move_to_event.path)
156- self.assertEqual(os.path.join(self.basedir, to_file_name),
157+ self.assertEqual(os.path.join(self.basedir, to_file_name),
158 move_to_event.pathname)
159 self.assertEqual(os.path.split(from_file_name)[1],
160 move_to_event.src_pathname)
161@@ -227,12 +227,12 @@
162 from_file_name = os.path.join(self.basedir,
163 'test_file_moved_to_not_watched_dir')
164 open(from_file_name, 'w').close()
165-
166+
167 def move_file():
168 """Action for the test."""
169 os.rename(from_file_name, os.path.join(tempfile.mkdtemp(),
170 'test_file_moved_to_not_watched_dir'))
171-
172+
173 # while on linux we will have to do some sort of magic like facundo
174 # did, on windows we will get a deleted event which is much more
175 # cleaner, first time ever windows is nicer!
176@@ -254,7 +254,7 @@
177 'test_file_move_from_not_watched_dir')
178 # create file before we record
179 open(from_file_name, 'w').close()
180-
181+
182 def move_files():
183 """Action for the test."""
184 os.rename(from_file_name, to_file_name)
185@@ -282,7 +282,7 @@
186 def move_file():
187 """Action for the test."""
188 os.rename(from_dir_name, to_dir_name)
189-
190+
191 move_from_event, move_to_event = self._perform_operations(self.basedir,
192 self.mask, False, move_file, 2)
193 # first test the move from
194@@ -300,7 +300,7 @@
195 self.assertEqual('IN_MOVED_TO|IN_ISDIR', move_to_event.maskname)
196 self.assertEqual(os.path.split(to_dir_name)[1], move_to_event.name)
197 self.assertEqual('.', move_to_event.path)
198- self.assertEqual(os.path.join(self.basedir, to_dir_name),
199+ self.assertEqual(os.path.join(self.basedir, to_dir_name),
200 move_to_event.pathname)
201 self.assertEqual(os.path.split(from_dir_name)[1], move_to_event.src_pathname)
202 self.assertEqual(0, move_to_event.wd)
203@@ -312,7 +312,7 @@
204 dir_name = os.path.join(self.basedir,
205 'test_dir_moved_to_not_watched_dir')
206 os.mkdir(dir_name)
207-
208+
209 def move_dir():
210 """Action for the test."""
211 os.rename(dir_name, os.path.join(tempfile.mkdtemp(),
212@@ -329,18 +329,18 @@
213 self.assertEqual(0, event.wd)
214
215 def test_dir_move_from_not_watched_dir(self):
216- """Test that the correct event is raised when a file is moved."""
217+ """Test that the correct event is raised when a file is moved."""
218 from_dir_name = os.path.join(tempfile.mkdtemp(),
219 'test_dir_move_from_not_watched_dir')
220 to_dir_name = os.path.join(self.basedir,
221 'test_dir_move_from_not_watched_dir')
222 # create file before we record
223 os.mkdir(from_dir_name)
224-
225+
226 def move_dir():
227 """Action for the test."""
228 os.rename(from_dir_name, to_dir_name)
229-
230+
231 event = self._perform_operations(self.basedir, self.mask, False,
232 move_dir, 1)[0]
233 self.assertTrue(event.dir)
234@@ -366,7 +366,6 @@
235 time.sleep(1)
236 notifier.process_events()
237 notifier.stop()
238- print handler.processed_events
239 self.assertEqual(0, len(handler.processed_events))
240
241
242@@ -414,8 +413,8 @@
243 exclude_filter = lambda x: False
244 proc_fun = lambda x: None
245 # expect the factory to be called with the passed values
246- self.watch_factory(wd, path, mask, auto_add,
247- events_queue=events_queue, exclude_filter=exclude_filter,
248+ self.watch_factory(wd, path, mask, auto_add,
249+ events_queue=events_queue, exclude_filter=exclude_filter,
250 proc_fun=proc_fun)
251 self.mocker.result(self.watch)
252 self.watch.start_watching()
253@@ -488,7 +487,7 @@
254 self.watch.stop_watching()
255 self.mocker.replay()
256 self.manager.rm_path(path)
257- self.assertEqual(None, self.manager._wdm.get(1))
258+ self.assertEqual(None, self.manager._wdm.get(1))
259
260 def test_rm_child_path(self):
261 """Test the removal of a child path."""
262
263=== modified file 'tests/status/test_aggregator.py'
264--- tests/status/test_aggregator.py 2011-04-01 18:29:59 +0000
265+++ tests/status/test_aggregator.py 2011-04-06 12:54:32 +0000
266@@ -22,6 +22,7 @@
267 from twisted.internet import defer
268 from twisted.internet.task import Clock
269 from twisted.trial.unittest import TestCase
270+from mocker import Mocker
271
272 from contrib.testing.testcase import BaseTwistedTestCase
273 from ubuntuone.devtools.handlers import MementoHandler
274@@ -29,7 +30,7 @@
275 from ubuntuone.status.notification import AbstractNotification
276 from ubuntuone.status.messaging import AbstractMessaging
277 from ubuntuone.syncdaemon import status_listener
278-from ubuntuone.syncdaemon.volume_manager import Share, UDF
279+from ubuntuone.syncdaemon.volume_manager import Share, UDF, Root
280
281 FILENAME = 'example.txt'
282 FILENAME2 = 'another_example.mp3'
283@@ -82,11 +83,18 @@
284 def test_not_fired_if_reset_within_delay(self):
285 """The timer is not fired if it is reset within the delay."""
286 self.timer.reset()
287- self.clock.advance(self.timer.delay/0.8)
288+ self.clock.advance(self.timer.delay / 0.8)
289 self.timer.reset()
290- self.clock.advance(self.timer.delay/0.8)
291+ self.clock.advance(self.timer.delay / 0.8)
292 self.assertTrue(self.timer.called)
293
294+ def test_active(self):
295+ """The timer is active until the delay is reached."""
296+ self.timer.reset()
297+ self.assertTrue(self.timer.active)
298+ self.clock.advance(self.timer.delay + 1)
299+ self.assertFalse(self.timer.active)
300+
301
302 class DeadlineTimerTestCase(TimerTestCase):
303 """Test the DeadlineTimer class."""
304@@ -503,8 +511,6 @@
305
306 progress_visible = False
307 progress = 0.0
308- emblem_visible = False
309- emblem = None
310
311 def show_progressbar(self):
312 """The progressbar is shown."""
313@@ -518,14 +524,6 @@
314 """The progressbar value is changed."""
315 self.progress = value
316
317- def show_warning_emblem(self):
318- """Show a warning emblem."""
319- self.emblem_visible = True
320-
321- def hide_emblem(self):
322- """Hide the current emblem."""
323- self.emblem_visible = False
324-
325
326 class FakeInhibitor(object):
327 """A fake session inhibitor."""
328@@ -650,20 +648,6 @@
329 inhibitor = yield d
330 self.assertEqual(inhibitor.flags, 0)
331
332- def test_show_warning_emblem(self):
333- """The warning emblem is shown."""
334- self.bar.hide_emblem()
335- self.bar.show_warning_emblem()
336- self.assertTrue(self.bar.emblem_visible)
337- self.assertTrue(self.bar.launcher.emblem_visible)
338-
339- def test_hide_emblem(self):
340- """The emblem is hidden."""
341- self.bar.show_warning_emblem()
342- self.bar.hide_emblem()
343- self.assertFalse(self.bar.emblem_visible)
344- self.assertFalse(self.bar.launcher.emblem_visible)
345-
346
347 class FakeDelayedBuffer(object):
348 """Appends all status pushed into a list."""
349@@ -700,6 +684,7 @@
350 def __init__(self):
351 """Initialize this instance."""
352 self.volumes = {}
353+ self.root = None
354
355 def get_volume(self, volume_id):
356 """Return a volume given its id."""
357@@ -714,6 +699,7 @@
358 self.queued_commands = set()
359 self.notification_switch = aggregator.NotificationSwitch()
360 self.connected = False
361+ self.clock = PatchedClock()
362 self.files_uploading = []
363 self.files_downloading = []
364
365@@ -759,6 +745,9 @@
366 class StatusFrontendTestCase(BaseTwistedTestCase):
367 """Test the status frontend."""
368
369+ def _fake_popunder(self):
370+ self.popunders += 1
371+
372 def setUp(self):
373 """Initialize this test instance."""
374 BaseTwistedTestCase.setUp(self)
375@@ -888,7 +877,6 @@
376 # msg did receive a count argument
377 self.assertEqual(1, msg[2])
378 # call callback
379- print msg
380 msg[0]('fake_indicator')
381 self.assertEqual(1, len(self.status_frontend.messaging.callbacks))
382
383@@ -945,6 +933,79 @@
384 self.assertFalse(self.status_frontend.aggregator.
385 notification_switch.enabled)
386
387+ def test_udf_quota_exceeded(self): # pylint: disable=R0201
388+ """Quota exceeded in udf."""
389+ mocker = Mocker()
390+ launcher = mocker.replace(
391+ "ubuntuone.platform.unity.UbuntuOneLauncher")
392+ launcher()
393+ mock_launcher = mocker.mock()
394+ mocker.result(mock_launcher)
395+ mock_launcher.set_urgent()
396+ mocker.replay()
397+ UDF_ID = 'fake udf id'
398+ udf = UDF(volume_id=UDF_ID)
399+ self.fakevm.volumes[UDF_ID] = udf
400+ self.listener.handle_SYS_QUOTA_EXCEEDED(
401+ volume_id=UDF_ID, free_bytes=0)
402+ self.assertEqual(
403+ 0, len(self.status_frontend.notification.notifications_shown))
404+ mocker.restore()
405+ mocker.verify()
406+
407+ def test_root_quota_exceeded(self): # pylint: disable=R0201
408+ """Quota exceeded in root."""
409+ mocker = Mocker()
410+ launcher = mocker.replace(
411+ "ubuntuone.platform.unity.UbuntuOneLauncher")
412+ launcher()
413+ mock_launcher = mocker.mock()
414+ mocker.result(mock_launcher)
415+ mock_launcher.set_urgent()
416+ mocker.replay()
417+ ROOT_ID = 'fake root id'
418+ root = Root(volume_id=ROOT_ID)
419+ self.fakevm.volumes[ROOT_ID] = root
420+ self.fakevm.root = root
421+ self.listener.handle_SYS_QUOTA_EXCEEDED(
422+ volume_id=ROOT_ID, free_bytes=0)
423+ self.assertEqual(
424+ 0, len(self.status_frontend.notification.notifications_shown))
425+ mocker.restore()
426+ mocker.verify()
427+
428+ def test_share_quota_exceeded(self):
429+ """Quota exceeded in share."""
430+ mocker = Mocker()
431+ launcher = mocker.replace(
432+ "ubuntuone.platform.unity.UbuntuOneLauncher")
433+ launcher()
434+ mock_launcher = mocker.mock()
435+ mocker.result(mock_launcher)
436+ mock_launcher.set_urgent()
437+ launcher()
438+ mock_launcher = mocker.mock()
439+ mocker.result(mock_launcher)
440+ mock_launcher.set_urgent()
441+ mocker.replay()
442+ SHARE_ID = 'fake share id'
443+ BYTES = 0
444+ share = Share(volume_id=SHARE_ID)
445+ self.fakevm.volumes[SHARE_ID] = share
446+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
447+ self.assertEqual(
448+ 1, len(self.status_frontend.notification.notifications_shown))
449+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
450+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
451+ self.assertEqual(
452+ 1, len(self.status_frontend.notification.notifications_shown))
453+ self.status_frontend.aggregator.clock.advance(aggregator.ONE_DAY + 1)
454+ self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
455+ self.assertEqual(
456+ 2, len(self.status_frontend.notification.notifications_shown))
457+ mocker.restore()
458+ mocker.verify()
459+
460
461 class StatusEventTestCase(TestCase):
462 """Test the status event class and children."""
463@@ -1302,16 +1363,6 @@
464 self.assertEqual(0.0, self.aggregator.progress_bar.progress)
465 self.assertFalse(self.aggregator.progress_bar.visible)
466
467- def test_connection_lost(self):
468- """The connection to the server was lost."""
469- self.status_frontend.server_connection_lost()
470- self.assertTrue(self.aggregator.progress_bar.emblem_visible)
471-
472- def test_connection_made(self):
473- """The connection to the server was made."""
474- self.status_frontend.server_connection_made()
475- self.assertFalse(self.aggregator.progress_bar.emblem_visible)
476-
477
478 class StatusGrouperTestCase(TestCase):
479 """Tests for the group_statuses function."""
480
481=== modified file 'tests/syncdaemon/test_status_listener.py'
482--- tests/syncdaemon/test_status_listener.py 2011-03-23 14:21:17 +0000
483+++ tests/syncdaemon/test_status_listener.py 2011-04-06 12:54:32 +0000
484@@ -311,3 +311,54 @@
485 yield d
486 call = ("server_connection_made", (), {})
487 self.assertIn(call, self.status_frontend.call_log)
488+
489+
490+class QuotaExceededStatusTestCase(StatusListenerTestCase):
491+ """Quota for UDFs/Shares/Root exceeded."""
492+
493+ @defer.inlineCallbacks
494+ def test_root_quota_exceeded(self):
495+ """Quota for root exceeded."""
496+ d = defer.Deferred()
497+ root = self.main.vm.root
498+ BYTES = 0
499+ self._listen_for('SYS_QUOTA_EXCEEDED', d.callback)
500+ self.main.event_q.push(
501+ 'SYS_QUOTA_EXCEEDED', volume_id=root.volume_id, free_bytes=BYTES)
502+ yield d
503+ self.assertIn("root_quota_exceeded", (
504+ call[0] for call in self.status_frontend.call_log))
505+
506+ @defer.inlineCallbacks
507+ def test_share_quota_exceeded(self):
508+ """Quota for a share exceeded."""
509+ SHARE_ID = 'fake share id'
510+ BYTES = 0
511+ d = defer.Deferred()
512+ share = Share(volume_id=SHARE_ID)
513+ yield self.main.vm.add_share(share)
514+ self._listen_for('SYS_QUOTA_EXCEEDED', d.callback)
515+ self.main.event_q.push(
516+ 'SYS_QUOTA_EXCEEDED', volume_id=SHARE_ID, free_bytes=BYTES)
517+ yield d
518+ self.assertIn("share_quota_exceeded", (
519+ call[0] for call in self.status_frontend.call_log))
520+
521+ @defer.inlineCallbacks
522+ def test_udf_quota_exceeded(self):
523+ """Quota for a UDF exceeded."""
524+ UDF_ID = 'fake udf id'
525+ PATH = 'test_udf_quota_exceeded/bar/baz'
526+ BYTES = 0
527+ d = defer.Deferred()
528+ udf = UDF(
529+ volume_id=UDF_ID, node_id='test', suggested_path=None, path=PATH,
530+ subscribed=True)
531+ yield self.main.vm.add_udf(udf)
532+ self._listen_for('SYS_QUOTA_EXCEEDED', d.callback)
533+ self.main.event_q.push(
534+ 'SYS_QUOTA_EXCEEDED', volume_id=UDF_ID, free_bytes=BYTES)
535+ yield d
536+ self.assertIn(
537+ "udf_quota_exceeded",
538+ [call[0] for call in self.status_frontend.call_log])
539
540=== modified file 'ubuntuone/platform/linux/messaging.py'
541--- ubuntuone/platform/linux/messaging.py 2011-03-10 14:48:06 +0000
542+++ ubuntuone/platform/linux/messaging.py 2011-04-06 12:54:32 +0000
543@@ -21,8 +21,14 @@
544 # of them are available, we should fall back to silently discarding
545 # messages.
546
547+import dbus
548+import os
549 import subprocess
550+import sys
551+
552 from time import time
553+from ubuntuone.logger import basic_formatter, logging
554+
555
556 try:
557 import indicate
558@@ -30,17 +36,46 @@
559 except ImportError:
560 USE_INDICATE = False
561
562+DBUS_BUS_NAME = 'com.ubuntuone.controlpanel.gui'
563+DBUS_PATH = '/gui'
564+DBUS_IFACE_GUI = 'com.ubuntuone.controlpanel.gui'
565+TRANSLATION_DOMAIN = 'ubuntuone-control-panel'
566
567 from ubuntuone.status.messaging import AbstractMessaging
568
569 APPLICATION_NAME = 'Ubuntu One Client'
570
571+LOG_LEVEL = logging.DEBUG
572+logger = logging.getLogger('ubuntuone.status')
573+logger.setLevel(LOG_LEVEL)
574+
575+if os.environ.get('DEBUG_STATUS'):
576+ debug_handler = logging.StreamHandler(sys.stderr)
577+ debug_handler.setFormatter(basic_formatter)
578+ debug_handler.setLevel(LOG_LEVEL)
579+ logger.addHandler(debug_handler)
580+
581
582 # pylint: disable=W0613
583 def open_volumes():
584 """Open the control panel to the shares tab."""
585- subprocess.Popen(
586- ['ubuntuone-control-panel-gtk', '--switch-to', 'volumes'])
587+ bus = dbus.SessionBus()
588+ obj = bus.get_object(DBUS_BUS_NAME, DBUS_PATH)
589+ service = dbus.Interface(obj, dbus_interface=DBUS_IFACE_GUI)
590+
591+ def error_handler(*args, **kwargs):
592+ """Log errors when calling D-Bus methods in a async way."""
593+ logger.error(
594+ 'Dbus call to com.ubuntuone.controlpanel.gui failed: %r %r', args,
595+ kwargs)
596+
597+ def reply_handler(*args, **kwargs):
598+ """Exit when done."""
599+ pass
600+
601+ service.switch_to_alert(
602+ 'volumes', True, reply_handler=reply_handler,
603+ error_handler=error_handler)
604
605
606 def _server_callback(the_indicator, message_time=None):
607
608=== modified file 'ubuntuone/platform/linux/unity.py'
609--- ubuntuone/platform/linux/unity.py 2011-02-24 16:32:24 +0000
610+++ ubuntuone/platform/linux/unity.py 2011-04-06 12:54:32 +0000
611@@ -17,8 +17,6 @@
612 # with this program. If not, see <http://www.gnu.org/licenses/>.
613 """Use libunity to show a progressbar and emblems on the launcher icon."""
614
615-import gio
616-
617 try:
618 from gi.repository import Unity
619 use_libunity = True
620@@ -26,7 +24,6 @@
621 use_libunity = False
622
623 U1_DOTDESKTOP = "ubuntuone-control-panel-gtk.desktop"
624-EMBLEM_IMPORTANT = "emblem-important"
625
626
627 class UbuntuOneLauncherUnity(object):
628@@ -48,14 +45,9 @@
629 """The progressbar value is changed."""
630 self.entry.props.progress = value
631
632- def show_warning_emblem(self):
633- """Show a warning emblem."""
634- self.entry.props.emblem = gio.ThemedIcon(EMBLEM_IMPORTANT)
635- self.entry.props.emblem_visible = True
636-
637- def hide_emblem(self):
638- """Hide the current emblem."""
639- self.entry.props.emblem_visible = False
640+ def set_urgent(self, value=True):
641+ """Set the launcher to urgent."""
642+ self.entry.props.urgent = value
643
644
645 class DummyLauncher(object):
646@@ -73,11 +65,8 @@
647 def set_progress(self, value):
648 """The progressbar value is changed."""
649
650- def show_warning_emblem(self):
651- """Show a warning emblem."""
652-
653- def hide_emblem(self):
654- """Hide the current emblem."""
655+ def set_urgent(self, value=True):
656+ """Set the launcher to urgent."""
657
658
659 UbuntuOneLauncher = UbuntuOneLauncherUnity if use_libunity else DummyLauncher
660
661=== modified file 'ubuntuone/status/aggregator.py'
662--- ubuntuone/status/aggregator.py 2011-04-01 18:46:22 +0000
663+++ ubuntuone/status/aggregator.py 2011-04-06 12:54:32 +0000
664@@ -33,6 +33,7 @@
665 from ubuntuone.platform.messaging import Messaging
666 from ubuntuone.platform.unity import UbuntuOneLauncher, DummyLauncher
667
668+ONE_DAY = 24 * 60 * 60
669 Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string)
670
671 LOG_LEVEL = logging.DEBUG
672@@ -52,6 +53,15 @@
673 PROGRESS_COMPLETED = Q_("%(percentage_completed)d%% completed.")
674 FILE_SYNC_IN_PROGRESS = Q_("File synchronization in progress")
675
676+SHARE_QUOTA_EXCEEDED = Q_(
677+ 'There is no available space on the folder:\n"%s" shared by %s')
678+
679+
680+def alert_user():
681+ """Set the launcher to urgent to alert the user."""
682+ launcher = UbuntuOneLauncher()
683+ launcher.set_urgent()
684+
685
686 def files_being_uploaded(filename, files_uploading):
687 """Get the i18n string for files being uploaded."""
688@@ -120,6 +130,7 @@
689 "'%(filename)s' and %(other_files)d other files were "
690 "downloaded to your computer.", other_files) % format_args
691
692+
693 def files_were_made_public(no_of_files):
694 """Get the i18n string for files that were made public."""
695
696@@ -307,6 +318,11 @@
697 if not self.called:
698 self.delay_call.reset(self.delay)
699
700+ @property
701+ def active(self):
702+ """Is the delay still active."""
703+ return self.delay_call.active()
704+
705
706 class DeadlineTimer(Timer):
707 """A Timer with a deadline."""
708@@ -448,6 +464,7 @@
709 self.clock = clock
710 self.status_aggregator = status_aggregator
711 self._set_idle()
712+ self.notification = None
713
714 def _change_state(self, new_state_class):
715 """Change to a new state."""
716@@ -531,16 +548,6 @@
717 self.timer.cleanup()
718 self.timer = None
719
720- def hide_emblem(self):
721- """Hide the emblem on the launcher icon."""
722- self.emblem_visible = False
723- self.launcher.hide_emblem()
724-
725- def show_warning_emblem(self):
726- """Show the warning emblem on the launcher icon."""
727- self.emblem_visible = True
728- self.launcher.show_warning_emblem()
729-
730 def _timeout(self, result):
731 """The aggregating timer has expired, so update the UI."""
732 self.timer = None
733@@ -763,12 +770,10 @@
734 def connection_lost(self):
735 """The connection to the server was lost."""
736 self.file_discovery_bubble.connection_lost()
737- self.progress_bar.show_warning_emblem()
738
739 def connection_made(self):
740 """The connection to the server was made."""
741 self.file_discovery_bubble.connection_made()
742- self.progress_bar.hide_emblem()
743
744
745 class StatusFrontend(object):
746@@ -780,6 +785,7 @@
747 self.notification = self.aggregator.get_notification()
748 self.messaging = Messaging()
749 self.udf_message = None
750+ self.quota_timer = None
751
752 def file_published(self, public_url):
753 """A file was published."""
754@@ -845,6 +851,29 @@
755 logger.debug("server connection made")
756 self.aggregator.connection_made()
757
758+ def udf_quota_exceeded(self, volume_dict):
759+ """Quota exceeded in UDF."""
760+ logger.debug("UDF quota exceeded for volume %r." % volume_dict)
761+ alert_user()
762+
763+ def share_quota_exceeded(self, volume_dict):
764+ """Sharing user's quota exceeded in share."""
765+ logger.debug("Share quota exceeded for volume %r." % volume_dict)
766+ if self.quota_timer is not None:
767+ if self.quota_timer.active:
768+ return
769+ else:
770+ self.quota_timer = Timer(ONE_DAY, clock=self.aggregator.clock)
771+ self.notification.send_notification(
772+ UBUNTUONE_TITLE, SHARE_QUOTA_EXCEEDED % (
773+ volume_dict['path'], volume_dict['other_visible_name']))
774+ alert_user()
775+
776+ def root_quota_exceeded(self, volume_dict):
777+ """Quota exceeded in root."""
778+ logger.debug("Root quota exceeded for volume %r." % volume_dict)
779+ alert_user()
780+
781 def set_show_all_notifications(self, value):
782 """Set the flag to show all notifications."""
783 if value:
784
785=== modified file 'ubuntuone/syncdaemon/status_listener.py'
786--- ubuntuone/syncdaemon/status_listener.py 2011-03-23 13:58:14 +0000
787+++ ubuntuone/syncdaemon/status_listener.py 2011-04-06 12:54:32 +0000
788@@ -19,12 +19,17 @@
789
790 from ubuntuone.status.aggregator import StatusFrontend
791 from ubuntuone.syncdaemon import action_queue, config
792+from ubuntuone.syncdaemon.interaction_interfaces import (
793+ get_share_dict, get_udf_dict)
794+from ubuntuone.syncdaemon.volume_manager import UDF, Root
795+
796
797 def should_start_listener():
798 """Check if the status listener should be started."""
799 # TODO: look this up in some configuration object
800 return True
801
802+
803 def get_listener(fsm, vm):
804 """Return an instance of the status listener, or None if turned off."""
805 if should_start_listener():
806@@ -33,6 +38,7 @@
807 else:
808 return None
809
810+
811 #TODO: hookup the shutdown of the listener to the cleanup in the aggregator
812 class StatusListener(object):
813 """SD listener for EQ events that turns them into status updates."""
814@@ -59,6 +65,7 @@
815 show_all_notifications = property(get_show_all_notifications,
816 set_show_all_notifications)
817
818+ # pylint: disable=W0613
819 def handle_AQ_CHANGE_PUBLIC_ACCESS_OK(self, share_id, node_id, is_public,
820 public_url):
821 """The status of a published resource changed."""
822@@ -66,6 +73,7 @@
823 self.status_frontend.file_published(public_url)
824 else:
825 self.status_frontend.file_unpublished(public_url)
826+ # pylint: enable=W0613
827
828 def handle_SYS_QUEUE_ADDED(self, command):
829 """A command has been added to the queue."""
830@@ -101,3 +109,19 @@
831 def handle_SYS_CONNECTION_MADE(self):
832 """The client connected to the server."""
833 self.status_frontend.server_connection_made()
834+
835+ def handle_SYS_QUOTA_EXCEEDED(self, volume_id, free_bytes):
836+ """Handle the SYS_QUOTA_EXCEEDED event."""
837+ volume = self.vm.get_volume(str(volume_id))
838+ volume_dict = {}
839+ if isinstance(volume, UDF):
840+ volume_dict = get_udf_dict(volume)
841+ to_call = self.status_frontend.udf_quota_exceeded
842+ elif isinstance(volume, Root):
843+ volume_dict = get_share_dict(volume)
844+ to_call = self.status_frontend.root_quota_exceeded
845+ else:
846+ volume_dict = get_share_dict(volume)
847+ to_call = self.status_frontend.share_quota_exceeded
848+ volume_dict['free_bytes'] = str(free_bytes)
849+ to_call(volume_dict)

Subscribers

People subscribed via source and target branches