Merge lp:~nataliabidart/magicicada-gui/tweaks-and-shares-to-others into lp:magicicada-gui

Proposed by Natalia Bidart
Status: Merged
Approved by: Facundo Batista
Approved revision: 39
Merged at revision: 36
Proposed branch: lp:~nataliabidart/magicicada-gui/tweaks-and-shares-to-others
Merge into: lp:magicicada-gui
Diff against target: 622 lines (+352/-67)
4 files modified
data/ui/gui.glade (+218/-29)
magicicada/__init__.py (+42/-25)
magicicada/helpers.py (+41/-0)
magicicada/tests/test_magicicada.py (+51/-13)
To merge this branch: bzr merge lp:~nataliabidart/magicicada-gui/tweaks-and-shares-to-others
Reviewer Review Type Date Requested Status
Facundo Batista Approve
Review via email: mp+26456@code.launchpad.net

Description of the change

Shares to others. Free bytes humanization.

To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote :

Go for it, baby

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/ui/gui.glade'
2--- data/ui/gui.glade 2010-05-31 19:41:08 +0000
3+++ data/ui/gui.glade 2010-06-01 00:35:33 +0000
4@@ -62,6 +62,28 @@
5 <column type="gchararray"/>
6 </columns>
7 </object>
8+ <object class="GtkListStore" id="shares_to_others_store">
9+ <columns>
10+ <!-- column-name accepted -->
11+ <column type="gboolean"/>
12+ <!-- column-name access_level -->
13+ <column type="gchararray"/>
14+ <!-- column-name free_bytes -->
15+ <column type="gchararray"/>
16+ <!-- column-name name -->
17+ <column type="gchararray"/>
18+ <!-- column-name node_id -->
19+ <column type="gchararray"/>
20+ <!-- column-name other_username -->
21+ <column type="gchararray"/>
22+ <!-- column-name other_visible_name -->
23+ <column type="gchararray"/>
24+ <!-- column-name path -->
25+ <column type="gchararray"/>
26+ <!-- column-name volume_id -->
27+ <column type="gchararray"/>
28+ </columns>
29+ </object>
30 <object class="GtkWindow" id="main_window">
31 <property name="width_request">800</property>
32 <property name="height_request">600</property>
33@@ -598,19 +620,6 @@
34 <property name="enable_grid_lines">both</property>
35 <property name="enable_tree_lines">True</property>
36 <child>
37- <object class="GtkTreeViewColumn" id="folders_node">
38- <property name="resizable">True</property>
39- <property name="title">Node</property>
40- <property name="expand">True</property>
41- <child>
42- <object class="GtkCellRendererText" id="cellrenderertext9"/>
43- <attributes>
44- <attribute name="text">0</attribute>
45- </attributes>
46- </child>
47- </object>
48- </child>
49- <child>
50 <object class="GtkTreeViewColumn" id="folders_path">
51 <property name="resizable">True</property>
52 <property name="title">Path</property>
53@@ -650,6 +659,19 @@
54 </object>
55 </child>
56 <child>
57+ <object class="GtkTreeViewColumn" id="folders_node">
58+ <property name="resizable">True</property>
59+ <property name="title">Node</property>
60+ <property name="expand">True</property>
61+ <child>
62+ <object class="GtkCellRendererText" id="cellrenderertext9"/>
63+ <attributes>
64+ <attribute name="text">0</attribute>
65+ </attributes>
66+ </child>
67+ </object>
68+ </child>
69+ <child>
70 <object class="GtkTreeViewColumn" id="folders_volume">
71 <property name="resizable">True</property>
72 <property name="title">Volume</property>
73@@ -681,6 +703,7 @@
74 <property name="receives_default">False</property>
75 <property name="use_stock">True</property>
76 <signal name="clicked" handler="on_folders_close_clicked"/>
77+ <signal name="activate" handler="on_folders_close_clicked"/>
78 </object>
79 <packing>
80 <property name="expand">False</property>
81@@ -742,7 +765,7 @@
82 </child>
83 <child>
84 <object class="GtkTreeViewColumn" id="shares_to_me_other">
85- <property name="title">Other</property>
86+ <property name="title">Shared from</property>
87 <property name="expand">True</property>
88 <child>
89 <object class="GtkCellRendererText" id="cellrenderertext19"/>
90@@ -759,14 +782,14 @@
91 <child>
92 <object class="GtkCellRendererToggle" id="cellrenderertoggle2"/>
93 <attributes>
94- <attribute name="activatable">0</attribute>
95+ <attribute name="active">0</attribute>
96 </attributes>
97 </child>
98 </object>
99 </child>
100 <child>
101 <object class="GtkTreeViewColumn" id="shares_to_me_access_level">
102- <property name="title">Access Level</property>
103+ <property name="title">Access level</property>
104 <property name="expand">True</property>
105 <child>
106 <object class="GtkCellRendererText" id="cellrenderertext13"/>
107@@ -777,7 +800,7 @@
108 </object>
109 </child>
110 <child>
111- <object class="GtkTreeViewColumn" id="shares-to_me_free_bytes">
112+ <object class="GtkTreeViewColumn" id="shares_to_me_free_bytes">
113 <property name="title">Free bytes</property>
114 <property name="expand">True</property>
115 <child>
116@@ -789,6 +812,18 @@
117 </object>
118 </child>
119 <child>
120+ <object class="GtkTreeViewColumn" id="shares_to_me_path">
121+ <property name="title">Path</property>
122+ <property name="expand">True</property>
123+ <child>
124+ <object class="GtkCellRendererText" id="cellrenderertext18"/>
125+ <attributes>
126+ <attribute name="text">7</attribute>
127+ </attributes>
128+ </child>
129+ </object>
130+ </child>
131+ <child>
132 <object class="GtkTreeViewColumn" id="shares_to_me_node">
133 <property name="title">Node</property>
134 <property name="expand">True</property>
135@@ -801,18 +836,6 @@
136 </object>
137 </child>
138 <child>
139- <object class="GtkTreeViewColumn" id="shares_to_me_path">
140- <property name="title">Path</property>
141- <property name="expand">True</property>
142- <child>
143- <object class="GtkCellRendererText" id="cellrenderertext18"/>
144- <attributes>
145- <attribute name="text">7</attribute>
146- </attributes>
147- </child>
148- </object>
149- </child>
150- <child>
151 <object class="GtkTreeViewColumn" id="shares_to_me_volume">
152 <property name="title">Volume</property>
153 <property name="expand">True</property>
154@@ -843,6 +866,7 @@
155 <property name="receives_default">True</property>
156 <property name="use_stock">True</property>
157 <signal name="clicked" handler="on_shares_to_me_close_clicked"/>
158+ <signal name="activate" handler="on_shares_to_me_close_clicked"/>
159 </object>
160 <packing>
161 <property name="expand">False</property>
162@@ -863,4 +887,169 @@
163 <action-widget response="0">shares_to_me_close</action-widget>
164 </action-widgets>
165 </object>
166+ <object class="GtkDialog" id="shares_to_others_dialog">
167+ <property name="width_request">600</property>
168+ <property name="height_request">300</property>
169+ <property name="border_width">5</property>
170+ <property name="title" translatable="yes">Shares to others</property>
171+ <property name="modal">True</property>
172+ <property name="window_position">center</property>
173+ <property name="type_hint">normal</property>
174+ <property name="has_separator">False</property>
175+ <child internal-child="vbox">
176+ <object class="GtkVBox" id="dialog-vbox5">
177+ <property name="visible">True</property>
178+ <property name="spacing">2</property>
179+ <child>
180+ <object class="GtkScrolledWindow" id="scrolledwindow5">
181+ <property name="visible">True</property>
182+ <property name="can_focus">True</property>
183+ <property name="hscrollbar_policy">automatic</property>
184+ <property name="vscrollbar_policy">automatic</property>
185+ <child>
186+ <object class="GtkTreeView" id="shares_to_others_view">
187+ <property name="visible">True</property>
188+ <property name="can_focus">True</property>
189+ <property name="model">shares_to_others_store</property>
190+ <property name="headers_clickable">False</property>
191+ <property name="rules_hint">True</property>
192+ <property name="search_column">0</property>
193+ <property name="enable_grid_lines">both</property>
194+ <property name="enable_tree_lines">True</property>
195+ <child>
196+ <object class="GtkTreeViewColumn" id="shares_to_others_name">
197+ <property name="title">Name</property>
198+ <property name="expand">True</property>
199+ <child>
200+ <object class="GtkCellRendererText" id="cellrenderertext20"/>
201+ <attributes>
202+ <attribute name="text">3</attribute>
203+ </attributes>
204+ </child>
205+ </object>
206+ </child>
207+ <child>
208+ <object class="GtkTreeViewColumn" id="shares_to_others_other">
209+ <property name="title">Shared to</property>
210+ <property name="expand">True</property>
211+ <child>
212+ <object class="GtkCellRendererText" id="cellrenderertext21"/>
213+ <attributes>
214+ <attribute name="text">6</attribute>
215+ </attributes>
216+ </child>
217+ </object>
218+ </child>
219+ <child>
220+ <object class="GtkTreeViewColumn" id="shates_to_others_accepted">
221+ <property name="title">Accepted</property>
222+ <property name="expand">True</property>
223+ <child>
224+ <object class="GtkCellRendererToggle" id="cellrenderertoggle3"/>
225+ <attributes>
226+ <attribute name="active">0</attribute>
227+ </attributes>
228+ </child>
229+ </object>
230+ </child>
231+ <child>
232+ <object class="GtkTreeViewColumn" id="shares_to_others_access_level">
233+ <property name="title">Access level</property>
234+ <property name="expand">True</property>
235+ <child>
236+ <object class="GtkCellRendererText" id="cellrenderertext22"/>
237+ <attributes>
238+ <attribute name="text">1</attribute>
239+ </attributes>
240+ </child>
241+ </object>
242+ </child>
243+ <child>
244+ <object class="GtkTreeViewColumn" id="shares_to_others_free_bytes">
245+ <property name="title">Free bytes</property>
246+ <property name="expand">True</property>
247+ <child>
248+ <object class="GtkCellRendererText" id="cellrenderertext23"/>
249+ <attributes>
250+ <attribute name="text">2</attribute>
251+ </attributes>
252+ </child>
253+ </object>
254+ </child>
255+ <child>
256+ <object class="GtkTreeViewColumn" id="shares_to_others_path">
257+ <property name="title">Path</property>
258+ <property name="expand">True</property>
259+ <child>
260+ <object class="GtkCellRendererText" id="cellrenderertext24"/>
261+ <attributes>
262+ <attribute name="text">7</attribute>
263+ </attributes>
264+ </child>
265+ </object>
266+ </child>
267+ <child>
268+ <object class="GtkTreeViewColumn" id="shares_to_others_node">
269+ <property name="title">Node</property>
270+ <property name="expand">True</property>
271+ <child>
272+ <object class="GtkCellRendererText" id="cellrenderertext25"/>
273+ <attributes>
274+ <attribute name="text">4</attribute>
275+ </attributes>
276+ </child>
277+ </object>
278+ </child>
279+ <child>
280+ <object class="GtkTreeViewColumn" id="shares_to_others_volume">
281+ <property name="title">Volume</property>
282+ <property name="expand">True</property>
283+ <child>
284+ <object class="GtkCellRendererText" id="cellrenderertext26"/>
285+ <attributes>
286+ <attribute name="text">8</attribute>
287+ </attributes>
288+ </child>
289+ </object>
290+ </child>
291+ </object>
292+ </child>
293+ </object>
294+ <packing>
295+ <property name="position">1</property>
296+ </packing>
297+ </child>
298+ <child internal-child="action_area">
299+ <object class="GtkHButtonBox" id="dialog-action_area5">
300+ <property name="visible">True</property>
301+ <property name="layout_style">end</property>
302+ <child>
303+ <object class="GtkButton" id="shares_to_others_close">
304+ <property name="label">gtk-close</property>
305+ <property name="visible">True</property>
306+ <property name="can_focus">True</property>
307+ <property name="receives_default">True</property>
308+ <property name="use_stock">True</property>
309+ <signal name="clicked" handler="on_shares_to_others_close_clicked"/>
310+ <signal name="activate" handler="on_shares_to_others_close_clicked"/>
311+ </object>
312+ <packing>
313+ <property name="expand">False</property>
314+ <property name="fill">False</property>
315+ <property name="position">0</property>
316+ </packing>
317+ </child>
318+ </object>
319+ <packing>
320+ <property name="expand">False</property>
321+ <property name="pack_type">end</property>
322+ <property name="position">0</property>
323+ </packing>
324+ </child>
325+ </object>
326+ </child>
327+ <action-widgets>
328+ <action-widget response="0">shares_to_others_close</action-widget>
329+ </action-widgets>
330+ </object>
331 </interface>
332
333=== modified file 'magicicada/__init__.py'
334--- magicicada/__init__.py 2010-05-31 19:41:08 +0000
335+++ magicicada/__init__.py 2010-06-01 00:35:33 +0000
336@@ -29,7 +29,7 @@
337 gtk2reactor.install()
338
339 from magicicada import syncdaemon
340-from magicicada.helpers import get_data_file, get_builder, NO_OP
341+from magicicada.helpers import humanize_bytes, get_data_file, get_builder, NO_OP
342
343 CONTENT_QUEUE = 'content'
344 META_QUEUE = 'meta'
345@@ -64,16 +64,17 @@
346
347 widgets = (
348 'start', 'stop', 'connect', 'disconnect', # toolbar buttons
349- 'folders', 'folders_dialog',
350- 'folders_store', 'folders_close', # folders
351- 'shares_to_me', 'shares_to_me_dialog',
352- 'shares_to_me_store', 'shares_to_me_close', # shares_to_me
353- 'shares_to_others', # toolbar buttons
354- 'raw_metadata', # more toolbar buttons
355+ 'folders', 'folders_dialog', # folders
356+ 'folders_store', 'folders_close',
357+ 'shares_to_me', 'shares_to_me_dialog', # shares_to_me
358+ 'shares_to_me_store', 'shares_to_me_close',
359+ 'shares_to_others', 'shares_to_others_dialog', # shares_to_others
360+ 'shares_to_others_store', 'shares_to_others_close',
361 'is_started', 'is_connected', 'is_online', # status bar images
362 'status_label', 'status_icon', # status label and systray icon
363 'metaq_view', 'contentq_view', # queues tree views
364 'metaq_store', 'contentq_store', # queues list stores
365+ 'raw_metadata', # raw metadata
366 'about_dialog', # dialogs
367 'main_window'
368 )
369@@ -137,6 +138,9 @@
370
371 def on_stop_clicked(self, widget, data=None):
372 """Stop syncdaemon."""
373+ for v in self.volumes:
374+ v.set_sensitive(False)
375+
376 if self.widget_enabled(self.disconnect):
377 self.on_disconnect_clicked(self.disconnect)
378 self.connect.set_sensitive(False)
379@@ -178,25 +182,39 @@
380 """Close the shares_to_me dialog."""
381 self.shares_to_me_dialog.response(gtk.RESPONSE_CLOSE)
382
383+ def _on_shares_clicked(self, items, store, dialog):
384+ """List shares to the user or to others."""
385+ if items is None:
386+ items = []
387+
388+ store.clear()
389+ for item in items:
390+ free_bytes = item.free_bytes
391+ if isinstance(free_bytes, int):
392+ free_bytes = humanize_bytes(free_bytes, precision=2)
393+ row = (item.accepted, item.access_level, free_bytes, item.name,
394+ item.node_id, item.other_username, item.other_visible_name,
395+ item.path, item.volume_id)
396+ store.append(row)
397+
398+ res = dialog.run()
399+ dialog.hide()
400+
401 def on_shares_to_me_clicked(self, widget, data=None):
402 """List shares to the user."""
403- items = self.sd.shares_to_me
404- if items is None:
405- items = []
406-
407- self.shares_to_me_store.clear()
408- for item in items:
409- #free_bytes = 0 if item.free_bytes is None else item.free_bytes
410- row = (item.accepted, item.access_level, item.free_bytes, item.name,
411- item.node_id, item.other_username, item.other_visible_name,
412- item.path, item.volume_id)
413- self.shares_to_me_store.append(row)
414-
415- res = self.shares_to_me_dialog.run()
416- self.shares_to_me_dialog.hide()
417+ self._on_shares_clicked(self.sd.shares_to_me,
418+ self.shares_to_me_store,
419+ self.shares_to_me_dialog)
420+
421+ def on_shares_to_others_close_clicked(self, widget, data=None):
422+ """Close the shares_to_others dialog."""
423+ self.shares_to_others_dialog.response(gtk.RESPONSE_CLOSE)
424
425 def on_shares_to_others_clicked(self, widget, data=None):
426 """List user shares to others."""
427+ self._on_shares_clicked(self.sd.shares_to_others,
428+ self.shares_to_others_store,
429+ self.shares_to_others_dialog)
430
431 def on_raw_metadata_clicked(self, widget, data=None):
432 """Show raw metadata for a path choosen by the user."""
433@@ -218,6 +236,9 @@
434 self._activate_indicator(self.is_started)
435 self.connect.set_sensitive(True)
436
437+ for v in self.volumes:
438+ v.set_sensitive(True)
439+
440 def on_stopped(self, *args, **kwargs):
441 """Callback'ed when syncadaemon is stopped."""
442 self.stop.hide()
443@@ -257,13 +278,9 @@
444 """Callback'ed when syncadaemon is online."""
445 self.is_online.set_sensitive(True)
446 self._activate_indicator(self.is_online)
447- for v in self.volumes:
448- v.set_sensitive(True)
449
450 def on_offline(self, *args, **kwargs):
451 """Callback'ed when syncadaemon is offline."""
452- for v in self.volumes:
453- v.set_sensitive(False)
454 self._activate_indicator(self.is_online, sensitive=False)
455
456 def on_status_changed(self, name=None, description=None,
457
458=== modified file 'magicicada/helpers.py'
459--- magicicada/helpers.py 2010-05-15 20:32:43 +0000
460+++ magicicada/helpers.py 2010-06-01 00:35:33 +0000
461@@ -5,6 +5,8 @@
462
463 """Helpers for an Ubuntu application."""
464
465+from __future__ import division
466+
467 __all__ = [
468 'make_window',
469 ]
470@@ -50,3 +52,42 @@
471 return result
472
473 return inner
474+
475+# from
476+# http://code.activestate.com/recipes/577081-humanized-representation-of-a-number-of-bytes/
477+def humanize_bytes(bytes, precision=1):
478+ """Return a humanized string representation of a number of bytes.
479+
480+ Assumes `from __future__ import division`.
481+
482+ >>> humanize_bytes(1)
483+ '1 byte'
484+ >>> humanize_bytes(1024)
485+ '1.0 kB'
486+ >>> humanize_bytes(1024*123)
487+ '123.0 kB'
488+ >>> humanize_bytes(1024*12342)
489+ '12.1 MB'
490+ >>> humanize_bytes(1024*12342,2)
491+ '12.05 MB'
492+ >>> humanize_bytes(1024*1234,2)
493+ '1.21 MB'
494+ >>> humanize_bytes(1024*1234*1111,2)
495+ '1.31 GB'
496+ >>> humanize_bytes(1024*1234*1111,1)
497+ '1.3 GB'
498+ """
499+ abbrevs = (
500+ (1<<50L, 'PB'),
501+ (1<<40L, 'TB'),
502+ (1<<30L, 'GB'),
503+ (1<<20L, 'MB'),
504+ (1<<10L, 'kB'),
505+ (1, 'bytes')
506+ )
507+ if bytes == 1:
508+ return '1 byte'
509+ for factor, suffix in abbrevs:
510+ if bytes >= factor:
511+ break
512+ return '%.*f %s' % (precision, bytes / factor, suffix)
513
514=== modified file 'magicicada/tests/test_magicicada.py'
515--- magicicada/tests/test_magicicada.py 2010-05-31 22:46:26 +0000
516+++ magicicada/tests/test_magicicada.py 2010-06-01 00:35:33 +0000
517@@ -28,7 +28,7 @@
518
519 from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, syncdaemon
520 from magicicada.dbusiface import QueueData, FolderData, ShareData
521-from magicicada.helpers import NO_OP
522+from magicicada.helpers import NO_OP, humanize_bytes
523
524 def process_gtk_pendings():
525 while gtk.events_pending(): gtk.main_iteration()
526@@ -658,27 +658,35 @@
527 msg % (self.name, '' if enabled else 'not '))
528
529 @skip_abstract_class
530- def test_volume_are_disabled_until_online(self):
531+ def test_volume_are_disabled_until_started(self):
532 """Folders and shares are disabled until online."""
533 # disabled at startup
534 self.assert_volume_availability(enabled=False)
535
536- # disabled even if connected
537- self.do_connect()
538- self.assert_volume_availability(enabled=False)
539-
540- # enabled when online
541- self.ui.on_online()
542+ # enabled when started
543+ self.do_start()
544 self.assert_volume_availability(enabled=True)
545
546 @skip_abstract_class
547- def test_volume_are_enabled_until_offline(self):
548+ def test_volume_are_enabled_until_stopped(self):
549 """Folders and shares are enabled until offline."""
550 self.do_connect()
551+ self.assert_volume_availability(enabled=True)
552+
553 self.ui.on_online()
554+ self.assert_volume_availability(enabled=True)
555
556- # disabled when offline
557 self.ui.on_offline()
558+ self.assert_volume_availability(enabled=True)
559+
560+ self.ui.on_disconnect_clicked(self.ui.disconnect)
561+ self.ui.on_disconnected()
562+ self.assert_volume_availability(enabled=True)
563+
564+ # disabled when stopped
565+ self.ui.on_stop_clicked(self.ui.stop)
566+ self.assert_volume_availability(enabled=False)
567+ self.ui.on_stopped()
568 self.assert_volume_availability(enabled=False)
569
570 @skip_abstract_class
571@@ -766,7 +774,6 @@
572 self.assertEqual(expected, actual,
573 msg % (self.volume_dialog_name, expected, actual))
574
575-
576 class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase):
577 """UI test cases for folders."""
578
579@@ -774,10 +781,41 @@
580 data_type = FolderData # node path suggested_path subscribed volume
581
582
583-class MagicicadaUISharesToMeTestCase(_MagicicadaUIVolumeTestCase):
584+class _MagicicadaUISharesTestCase(_MagicicadaUIVolumeTestCase):
585 """UI test cases for shares_to_me."""
586
587- name = 'shares_to_me'
588 data_type = ShareData # accepted access_level free_bytes name node_id
589 # other_username other_visible_name path volume_id
590
591+ @skip_abstract_class
592+ def test_bytes_are_humanized(self):
593+ """Free bytes are shown humanized."""
594+ attrs = self.data_type._fields
595+ kwargs = dict([(attr, str(attr)) for attr in attrs])
596+ kwargs['free_bytes'] = 10000
597+ item = self.data_type(**kwargs)
598+ setattr(self.ui.sd, self.name, [item])
599+
600+ def test():
601+ """Perform the test per se before closing the dialog."""
602+ kwargs['free_bytes'] = humanize_bytes(kwargs['free_bytes'],
603+ precision=2)
604+ item = self.data_type(**kwargs)
605+ self.assert_store_correct(self.volume_store, [item])
606+
607+ gobject.timeout_add(100, close_dialog,
608+ (self.volume_dialog, test))
609+ self.on_volume_clicked(self.volume)
610+
611+
612+class MagicicadaUISharesToMeTestCase(_MagicicadaUISharesTestCase):
613+ """UI test cases for shares_to_me."""
614+
615+ name = 'shares_to_me'
616+
617+
618+class MagicicadaUISharesToOthersTestCase(_MagicicadaUISharesTestCase):
619+ """UI test cases for shares_to_others."""
620+
621+ name = 'shares_to_others'
622+

Subscribers

People subscribed via source and target branches

to all changes: