Merge lp:~statik/ubuntu/maverick/magicicada/zero-one-two into lp:ubuntu/maverick/magicicada

Proposed by Elliot Murphy
Status: Merged
Merged at revision: 3
Proposed branch: lp:~statik/ubuntu/maverick/magicicada/zero-one-two
Merge into: lp:ubuntu/maverick/magicicada
Diff against target: 3256 lines (+1233/-512)
17 files modified
PKG-INFO (+3/-3)
README.txt (+13/-0)
bin/magicicada (+17/-13)
data/ui/gui.glade (+70/-8)
debian/changelog (+6/-0)
magicicada/__init__.py (+129/-33)
magicicada/cmd_pof.py (+0/-190)
magicicada/dbusiface.py (+58/-10)
magicicada/helpers.py (+38/-24)
magicicada/logger.py (+0/-1)
magicicada/magicicadaconfig.py (+5/-6)
magicicada/syncdaemon.py (+48/-26)
magicicada/tests/helpers.py (+7/-3)
magicicada/tests/test_dbusiface.py (+160/-49)
magicicada/tests/test_magicicada.py (+502/-87)
magicicada/tests/test_syncdaemon.py (+152/-41)
setup.py (+25/-18)
To merge this branch: bzr merge lp:~statik/ubuntu/maverick/magicicada/zero-one-two
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+29596@code.launchpad.net

Description of the change

New upstream release of Magicicada. I've testbuilt and installed, and it upgrades and runs just fine.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2010-06-08 10:53:02 +0000
3+++ PKG-INFO 2010-07-09 18:33:43 +0000
4@@ -1,7 +1,7 @@
5 Metadata-Version: 1.1
6 Name: magicicada
7-Version: 0.1.1
8-Summary: A GTK+ frontend for the "Chicharra" part of Ubuntu One client.
9+Version: 0.1.2
10+Summary: A GTK+ frontend for the "Chicharra" part of Ubuntu One.
11 Home-page: https://launchpad.net/magicicada
12 Author: Natalia Bidart
13 Author-email: natalia.bidart@ubuntu.com
14@@ -14,6 +14,6 @@
15 Requires: pango
16 Requires: twisted.internet
17 Requires: twisted.trial.unittest
18-Requires: ubuntuone.syncdaemon
19+Requires: ubuntuone.syncdaemon.tools
20 Requires: xdg.BaseDirectory
21 Provides: magicicada
22
23=== modified file 'README.txt'
24--- README.txt 2010-06-08 10:53:02 +0000
25+++ README.txt 2010-07-09 18:33:43 +0000
26@@ -1,3 +1,16 @@
27 This is Magicicada!
28
29 A GTK+ frontend for the "Chicharra" part of Ubuntu One client.
30+
31+-----------
32+HOWTO do a source release:
33+
34+ * edit setup.py and increment the version number.
35+ * 'python setup.py sdist'
36+ * look at the contents of the tarball created in dist/ to be sure they are ok
37+ * sign the tarball by a command like:
38+ gpg -a --detach-sign magicicada-0.2.tar.gz
39+ this should create a file like magicicada-0.2.tar.gz.asc
40+ * Upload the new release to launchpad with a command like:
41+ lp-project-upload magicicada 0.2 magicicada-0.2.tar.gz
42+ * Announce the release, ping someone to build updated packages for the PPA and Ubuntu.
43
44=== modified file 'bin/magicicada'
45--- bin/magicicada 2010-06-08 10:53:02 +0000
46+++ bin/magicicada 2010-07-09 18:33:43 +0000
47@@ -1,8 +1,21 @@
48 #!/usr/bin/python
49 # -*- coding: utf-8 -*-
50-### BEGIN LICENSE
51-# This file is in the public domain
52-### END LICENSE
53+#
54+# Copyright 2010 Chicharreros
55+#
56+# This program is free software: you can redistribute it and/or modify it
57+# under the terms of the GNU General Public License version 3, as published
58+# by the Free Software Foundation.
59+#
60+# This program is distributed in the hope that it will be useful, but
61+# WITHOUT ANY WARRANTY; without even the implied warranties of
62+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
63+# PURPOSE. See the GNU General Public License for more details.
64+#
65+# You should have received a copy of the GNU General Public License along
66+# with this program. If not, see <http://www.gnu.org/licenses/>.
67+
68+"""Script to run Magicicada."""
69
70 import sys
71 import os
72@@ -11,14 +24,6 @@
73 from gettext import gettext as _
74 gettext.textdomain('magicicada')
75
76-# optional Launchpad integration
77-# this shouldn't crash if not found as it is simply used for bug reporting
78-try:
79- import LaunchpadIntegration
80- launchpad_available = True
81-except:
82- launchpad_available = False
83-
84 # Add project root directory (enable symlink, and trunk execution).
85 PROJECT_ROOT_DIRECTORY = os.path.abspath(
86 os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
87@@ -48,6 +53,5 @@
88 from twisted.internet import reactor
89
90 # Run the application.
91- window = MagicicadaUI(launchpad_available=launchpad_available,
92- on_destroy=lambda *a, **kw: reactor.stop())
93+ window = MagicicadaUI(on_destroy=lambda *a, **kw: reactor.stop())
94 reactor.run()
95
96=== added symlink 'data/media/icon.png'
97=== target is u'logo-032.png'
98=== added directory 'data/tests'
99=== added file 'data/tests/metadata-test.txt'
100=== modified file 'data/ui/gui.glade'
101--- data/ui/gui.glade 2010-06-08 10:53:02 +0000
102+++ data/ui/gui.glade 2010-07-09 18:33:43 +0000
103@@ -257,6 +257,7 @@
104 </child>
105 <child>
106 <object class="GtkToolButton" id="raw_metadata">
107+ <property name="visible">True</property>
108 <property name="sensitive">False</property>
109 <property name="label" translatable="yes">Metadata</property>
110 <property name="use_underline">True</property>
111@@ -384,7 +385,7 @@
112 <child>
113 <object class="GtkCellRendererText" id="cellrenderertext1"/>
114 <attributes>
115- <attribute name="text">0</attribute>
116+ <attribute name="markup">0</attribute>
117 </attributes>
118 </child>
119 </object>
120@@ -396,7 +397,7 @@
121 <child>
122 <object class="GtkCellRendererText" id="cellrenderertext2"/>
123 <attributes>
124- <attribute name="text">1</attribute>
125+ <attribute name="markup">1</attribute>
126 </attributes>
127 </child>
128 </object>
129@@ -408,7 +409,7 @@
130 <child>
131 <object class="GtkCellRendererText" id="cellrenderertext4"/>
132 <attributes>
133- <attribute name="text">2</attribute>
134+ <attribute name="markup">2</attribute>
135 </attributes>
136 </child>
137 </object>
138@@ -420,7 +421,7 @@
139 <child>
140 <object class="GtkCellRendererText" id="cellrenderertext3"/>
141 <attributes>
142- <attribute name="text">3</attribute>
143+ <attribute name="markup">3</attribute>
144 </attributes>
145 </child>
146 </object>
147@@ -476,7 +477,7 @@
148 <child>
149 <object class="GtkCellRendererText" id="cellrenderertext5"/>
150 <attributes>
151- <attribute name="text">0</attribute>
152+ <attribute name="markup">0</attribute>
153 </attributes>
154 </child>
155 </object>
156@@ -488,7 +489,7 @@
157 <child>
158 <object class="GtkCellRendererText" id="cellrenderertext6"/>
159 <attributes>
160- <attribute name="text">1</attribute>
161+ <attribute name="markup">1</attribute>
162 </attributes>
163 </child>
164 </object>
165@@ -500,7 +501,7 @@
166 <child>
167 <object class="GtkCellRendererText" id="cellrenderertext8"/>
168 <attributes>
169- <attribute name="text">2</attribute>
170+ <attribute name="markup">2</attribute>
171 </attributes>
172 </child>
173 </object>
174@@ -512,7 +513,7 @@
175 <child>
176 <object class="GtkCellRendererText" id="cellrenderertext7"/>
177 <attributes>
178- <attribute name="text">3</attribute>
179+ <attribute name="markup">3</attribute>
180 </attributes>
181 </child>
182 </object>
183@@ -1051,4 +1052,65 @@
184 <action-widget response="0">shares_to_others_close</action-widget>
185 </action-widgets>
186 </object>
187+ <object class="GtkFileChooserDialog" id="file_chooser">
188+ <property name="border_width">5</property>
189+ <property name="type_hint">normal</property>
190+ <property name="has_separator">False</property>
191+ <property name="create_folders">False</property>
192+ <signal name="file_activated" handler="on_file_chooser_open_clicked"/>
193+ <child internal-child="vbox">
194+ <object class="GtkVBox" id="dialog-vbox7">
195+ <property name="visible">True</property>
196+ <property name="spacing">2</property>
197+ <child>
198+ <placeholder/>
199+ </child>
200+ <child internal-child="action_area">
201+ <object class="GtkHButtonBox" id="dialog-action_area7">
202+ <property name="visible">True</property>
203+ <property name="layout_style">end</property>
204+ <child>
205+ <object class="GtkButton" id="file_chooser_cancel">
206+ <property name="label">gtk-cancel</property>
207+ <property name="visible">True</property>
208+ <property name="can_focus">True</property>
209+ <property name="receives_default">True</property>
210+ <property name="use_stock">True</property>
211+ </object>
212+ <packing>
213+ <property name="expand">False</property>
214+ <property name="fill">False</property>
215+ <property name="position">0</property>
216+ </packing>
217+ </child>
218+ <child>
219+ <object class="GtkButton" id="file_chooser_open">
220+ <property name="label">gtk-open</property>
221+ <property name="visible">True</property>
222+ <property name="can_focus">True</property>
223+ <property name="receives_default">True</property>
224+ <property name="use_stock">True</property>
225+ <signal name="clicked" handler="on_file_chooser_open_clicked"/>
226+ <signal name="activate" handler="on_file_chooser_open_clicked"/>
227+ </object>
228+ <packing>
229+ <property name="expand">False</property>
230+ <property name="fill">False</property>
231+ <property name="position">1</property>
232+ </packing>
233+ </child>
234+ </object>
235+ <packing>
236+ <property name="expand">False</property>
237+ <property name="pack_type">end</property>
238+ <property name="position">0</property>
239+ </packing>
240+ </child>
241+ </object>
242+ </child>
243+ <action-widgets>
244+ <action-widget response="-6">file_chooser_cancel</action-widget>
245+ <action-widget response="0">file_chooser_open</action-widget>
246+ </action-widgets>
247+ </object>
248 </interface>
249
250=== modified file 'debian/changelog'
251--- debian/changelog 2010-06-08 10:53:02 +0000
252+++ debian/changelog 2010-07-09 18:33:43 +0000
253@@ -1,3 +1,9 @@
254+magicicada (0.1.2-0ubuntu1) maverick; urgency=low
255+
256+ * New upstream release.
257+
258+ -- Elliot Murphy <elliot@ubuntu.com> Fri, 09 Jul 2010 14:14:53 -0400
259+
260 magicicada (0.1.1-0ubuntu1) maverick; urgency=low
261
262 [ Elliot Murphy ]
263
264=== modified file 'magicicada/__init__.py'
265--- magicicada/__init__.py 2010-06-08 10:53:02 +0000
266+++ magicicada/__init__.py 2010-07-09 18:33:43 +0000
267@@ -18,33 +18,52 @@
268
269 """Magicicada."""
270
271-import gtk
272-import sys
273+import logging
274+import os
275
276 import gettext
277-from gettext import gettext as _
278+_ = gettext.gettext
279 gettext.textdomain('magicicada')
280
281-from twisted.internet import gtk2reactor # for gtk-2.0
282+import gtk
283+
284+# optional Launchpad integration
285+# this shouldn't crash if not found as it is simply used for bug reporting
286+try:
287+ import LaunchpadIntegration
288+ launchpad_available = True
289+except ImportError:
290+ launchpad_available = False
291+
292+from twisted.internet import gtk2reactor # for gtk-2.0
293 gtk2reactor.install()
294
295-from magicicada import syncdaemon, logger
296-from magicicada.helpers import humanize_bytes, get_data_file, get_builder, NO_OP
297+from magicicada import syncdaemon, logger as logger_helper
298+from magicicada.helpers import humanize_bytes, get_data_file, get_builder, \
299+ log, NO_OP
300
301 CONTENT_QUEUE = 'content'
302 META_QUEUE = 'meta'
303+UBUNTU_ONE_ROOT = os.path.expanduser('~/Ubuntu One')
304
305 # set up the logging for all the project
306-logger.set_up()
307+logger_helper.set_up()
308+logger = logging.getLogger('magicicada.ui')
309+console = logging.StreamHandler()
310+console.setLevel(logging.DEBUG)
311+#logger.addHandler(console)
312+
313
314 class MagicicadaUI(object):
315+ """Magicicada GUI main class."""
316
317- STATUS_JOINER = " - "
318+ CURRENT_ROW = '<b><span foreground="#000099">%s</span></b>'
319+ STATUS_JOINER = ' - '
320 STATUS = {
321 'initial': _('Service is not started, click Start to continue.'),
322 }
323
324- def __init__(self, launchpad_available=False, on_destroy=NO_OP,
325+ def __init__(self, on_destroy=NO_OP,
326 syncdaemon_class=syncdaemon.SyncDaemon):
327 """Init."""
328 self.builder = get_builder('gui.glade')
329@@ -66,27 +85,28 @@
330 self.active_indicator = gtk.gdk.pixbuf_new_from_file(active_filename)
331
332 widgets = (
333- 'start', 'stop', 'connect', 'disconnect', # toolbar buttons
334- 'folders', 'folders_dialog', # folders
335+ 'start', 'stop', 'connect', 'disconnect', # toolbar buttons
336+ 'folders', 'folders_dialog', # folders
337 'folders_store', 'folders_close',
338- 'shares_to_me', 'shares_to_me_dialog', # shares_to_me
339+ 'shares_to_me', 'shares_to_me_dialog', # shares_to_me
340 'shares_to_me_store', 'shares_to_me_close',
341- 'shares_to_others', 'shares_to_others_dialog', # shares_to_others
342+ 'shares_to_others', 'shares_to_others_dialog', # shares_to_others
343 'shares_to_others_store', 'shares_to_others_close',
344- 'is_started', 'is_connected', 'is_online', # status bar images
345- 'status_label', 'status_icon', # status label and systray icon
346- 'metaq_view', 'contentq_view', # queues tree views
347- 'metaq_store', 'contentq_store', # queues list stores
348- 'metaq_label', 'contentq_label', # queues labels
349- 'raw_metadata', # raw metadata
350- 'about_dialog', # dialogs
351- 'main_window'
352- )
353+ 'raw_metadata', # raw metadata
354+ 'is_started', 'is_connected', 'is_online', # status bar images
355+ 'status_label', 'status_icon', # status label and systray icon
356+ 'metaq_view', 'contentq_view', # queues tree views
357+ 'metaq_store', 'contentq_store', # queues list stores
358+ 'metaq_label', 'contentq_label', # queues labels
359+ 'file_chooser', 'file_chooser_open', 'file_chooser_cancel',
360+ 'about_dialog', 'main_window')
361+
362 for widget in widgets:
363 obj = self.builder.get_object(widget)
364 setattr(self, widget, obj)
365 assert obj is not None, '%s must not be None' % widget
366
367+ self.raw_metadata_dialog = self._new_metadata_dialog()
368 self.volumes = (self.folders, self.shares_to_me, self.shares_to_others)
369 self.windows = (self.main_window, self.about_dialog,
370 self.folders_dialog)
371@@ -97,8 +117,8 @@
372 for w in self.windows:
373 w.set_icon(self._icon)
374
375- about_filename = get_data_file('media', 'logo-128.png')
376- self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(about_filename))
377+ about_fname = get_data_file('media', 'logo-128.png')
378+ self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(about_fname))
379
380 self.sd = syncdaemon_class()
381 self.sd.on_started_callback = self.on_started
382@@ -110,13 +130,44 @@
383 self.sd.status_changed_callback = self.on_status_changed
384 self.sd.content_queue_changed_callback = self.on_content_queue_changed
385 self.sd.meta_queue_changed_callback = self.on_meta_queue_changed
386+ self.sd.on_metadata_ready_callback = self.on_metadata_ready
387
388 self.widget_is_visible = lambda w: w.get_property('visible')
389 self.widget_enabled = lambda w: self.widget_is_visible(w) and \
390 w.is_sensitive()
391-
392+ self.file_chooser.set_current_folder(UBUNTU_ONE_ROOT)
393+ self.last_metadata_path = None
394 self.update()
395
396+ def _new_metadata_dialog(self):
397+ """Return a new metadata dialog."""
398+ dialog = gtk.Dialog(title='Raw metadata', parent=self.main_window,
399+ flags=gtk.DIALOG_NO_SEPARATOR,
400+ buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
401+ dialog.set_size_request(600, 300)
402+ dialog.set_position(gtk.WIN_POS_CENTER)
403+ setattr(self, 'raw_metadata_dialog', dialog)
404+
405+ close_button = dialog.action_area.get_children()[-1]
406+ close_button.connect('clicked', self.on_raw_metadata_close_clicked)
407+ close_button.connect('activate', self.on_raw_metadata_close_clicked)
408+ setattr(self, 'raw_metadata_close', close_button)
409+
410+ image = gtk.Image()
411+ image.set_from_animation(self.loading_animation)
412+ setattr(self, 'raw_metadata_image', image)
413+
414+ dialog.get_child().add(image)
415+
416+ text_view = gtk.TextView()
417+ text_view.set_editable(False)
418+ text_view.set_wrap_mode(gtk.WRAP_WORD)
419+ dialog.get_child().add(text_view)
420+ setattr(self, 'raw_metadata_view', text_view)
421+
422+ dialog.hide() # XXX to be fixed later
423+ return dialog
424+
425 # GTK callbacks
426
427 def on_main_window_destroy(self, widget, data=None):
428@@ -131,7 +182,7 @@
429
430 def on_about_activate(self, widget, data=None):
431 """Display the about box."""
432- response = self.about_dialog.run()
433+ self.about_dialog.run()
434 self.about_dialog.hide()
435
436 def on_start_clicked(self, widget, data=None):
437@@ -144,6 +195,7 @@
438 """Stop syncdaemon."""
439 for v in self.volumes:
440 v.set_sensitive(False)
441+ self.raw_metadata.set_sensitive(False)
442
443 if self.widget_enabled(self.disconnect):
444 self.on_disconnect_clicked(self.disconnect)
445@@ -179,13 +231,14 @@
446 item.subscribed, item.volume)
447 self.folders_store.append(row)
448
449- res = self.folders_dialog.run()
450+ self.folders_dialog.run()
451 self.folders_dialog.hide()
452
453 def on_shares_to_me_close_clicked(self, widget, data=None):
454 """Close the shares_to_me dialog."""
455 self.shares_to_me_dialog.response(gtk.RESPONSE_CLOSE)
456
457+ @log(logger)
458 def _on_shares_clicked(self, items, store, dialog):
459 """List shares to the user or to others."""
460 if items is None:
461@@ -201,7 +254,7 @@
462 item.path, item.volume_id)
463 store.append(row)
464
465- res = dialog.run()
466+ dialog.run()
467 dialog.hide()
468
469 def on_shares_to_me_clicked(self, widget, data=None):
470@@ -220,8 +273,27 @@
471 self.shares_to_others_store,
472 self.shares_to_others_dialog)
473
474+ def on_file_chooser_open_clicked(self, widget, data=None):
475+ """Close the file_chooser dialog."""
476+ self.file_chooser.response(gtk.FILE_CHOOSER_ACTION_OPEN)
477+
478+ def on_raw_metadata_close_clicked(self, widget, data=None):
479+ """Close the raw_metadata dialog."""
480+ self.raw_metadata_dialog.hide()
481+
482 def on_raw_metadata_clicked(self, widget, data=None):
483 """Show raw metadata for a path choosen by the user."""
484+ res = self.file_chooser.run()
485+ self.file_chooser.hide()
486+ if res != gtk.FILE_CHOOSER_ACTION_OPEN:
487+ return
488+
489+ self.last_metadata_path = self.file_chooser.get_filename()
490+ self.sd.get_metadata(path=self.last_metadata_path)
491+ self.raw_metadata_view.hide()
492+ self.raw_metadata_image.show()
493+ self._start_loading(self.raw_metadata_image)
494+ self.raw_metadata_dialog.show()
495
496 def on_status_icon_activate(self, widget, data=None):
497 """Systray icon was clicked."""
498@@ -242,6 +314,7 @@
499
500 for v in self.volumes:
501 v.set_sensitive(True)
502+ self.raw_metadata.set_sensitive(True)
503
504 self._update_queues_and_status(self.sd.current_state)
505
506@@ -285,6 +358,7 @@
507 """Callback'ed when syncadaemon is offline."""
508 self._activate_indicator(self.is_online, sensitive=False)
509
510+ @log(logger)
511 def on_status_changed(self, name=None, description=None,
512 is_error=False, is_connected=True, is_online=True,
513 queues=None, connection=None):
514@@ -295,17 +369,22 @@
515 text = self.STATUS['initial']
516 self.status_label.set_text(text)
517
518- def _on_queue_changed(self, queue_name, items, *args, **kwargs):
519+ @log(logger)
520+ def _on_queue_changed(self, queue_name, items, must_highlight):
521 """Callback'ed when a queue changed."""
522 if items is None:
523 items = []
524
525+ markup = lambda value: self.CURRENT_ROW % value \
526+ if must_highlight and value is not None else value
527 queue_label = getattr(self, '%sq_label' % queue_name)
528 queue_view = getattr(self, '%sq_view' % queue_name)
529 queue_store = getattr(self, '%sq_store' % queue_name)
530 queue_store.clear()
531- for item in items:
532+ for i, item in enumerate(items):
533 row = (item.operation, item.path, item.share, item.node)
534+ if i == 0:
535+ row = map(markup, row)
536 queue_store.append(row)
537
538 items_len = len(items)
539@@ -317,11 +396,27 @@
540
541 def on_content_queue_changed(self, items, *args, **kwargs):
542 """Callback'ed when syncadaemon's content queue changed."""
543- self._on_queue_changed(CONTENT_QUEUE, items, args, kwargs)
544+ state = self.sd.current_state
545+ highlight = state.processing_content and state.is_online
546+ self._on_queue_changed(CONTENT_QUEUE, items, highlight)
547
548 def on_meta_queue_changed(self, items, *args, **kwargs):
549 """Callback'ed when syncadaemon's meta queue changed."""
550- self._on_queue_changed(META_QUEUE, items, args, kwargs)
551+ state = self.sd.current_state
552+ highlight = state.processing_meta and state.is_online
553+ self._on_queue_changed(META_QUEUE, items, highlight)
554+
555+ @log(logger)
556+ def on_metadata_ready(self, path, metadata):
557+ """Lower layer has the requested metadata for 'path'."""
558+ logger.debug('on_metadata_ready: path: %r, last_metadata_path: %r',
559+ path, self.last_metadata_path)
560+ if path != self.last_metadata_path:
561+ return
562+ self.raw_metadata_image.hide()
563+ self.raw_metadata_view.show()
564+ text = '\n'.join('%s: %s' % i for i in metadata.iteritems())
565+ self.raw_metadata_view.get_buffer().set_text(text)
566
567 # custom
568
569@@ -340,7 +435,8 @@
570 self.on_meta_queue_changed(self.sd.meta_queue)
571 self.on_content_queue_changed(self.sd.content_queue)
572 self.on_status_changed(name=state.name, description=state.description,
573- queues=state.queues, connection=state.connection)
574+ queues=state.queues,
575+ connection=state.connection)
576
577 def update(self):
578 """Update UI based on SD current state."""
579
580=== removed file 'magicicada/cmd_pof.py'
581--- magicicada/cmd_pof.py 2010-06-08 10:53:02 +0000
582+++ magicicada/cmd_pof.py 1970-01-01 00:00:00 +0000
583@@ -1,190 +0,0 @@
584-# DISCLAIMER: this is a proof of concept, we need tests for this!
585-# Author: Facundo Batista
586-
587-# this always first
588-from twisted.internet import glib2reactor
589-glib2reactor.install()
590-
591-import functools
592-import re
593-import time
594-
595-import dbus
596-from dbus.mainloop.glib import DBusGMainLoop
597-from twisted.internet import reactor
598-
599-from ubuntuone.syncdaemon import tools
600-
601-# main connected stuff
602-loop = DBusGMainLoop(set_as_default=True)
603-bus = dbus.SessionBus(mainloop=loop)
604-sync_daemon_tool = tools.SyncDaemonTool(bus)
605-
606-DATE_FMT = "%Y-%m-%d"
607-TIME_FMT = "%H:%M:%S"
608-
609-RE_OP_LISTDIR = re.compile("ListDir\(share_id=(.*?), node_id=(.*?), .*")
610-RE_OP_UNLINK = re.compile("Unlink\(share_id=(.*?), node_id=(.*?), .*")
611-RE_OP_MAKEFILE = re.compile(
612- "MakeFile\(share_id=(.*?), parent_id=(.*?), name=(.*?), .*")
613-
614-class DBusHandler(object):
615- def __init__(self):
616- # status changed
617- bus.add_signal_receiver(self.on_status_changed,
618- dbus_interface='com.ubuntuone.SyncDaemon.Status',
619- signal_name='StatusChanged')
620- # content queue changed
621- bus.add_signal_receiver(self.on_CQ_changed,
622- dbus_interface='com.ubuntuone.SyncDaemon.Status',
623- signal_name='ContentQueueChanged')
624-
625- # upload or download
626- for s in ('DownloadStarted', 'DownloadFinished',
627- 'UploadStarted', 'UploadFinished'):
628- bus.add_signal_receiver(functools.partial(self.on_updown_activ, s),
629- signal_name=s)
630-
631- # get the first one
632- d = sync_daemon_tool.get_status()
633- d.addCallback(self.on_status_changed)
634-
635- self._date = None
636- self._last_CQ_data = None
637- self._last_MQ_data = None
638- self._state = None
639- self._mqcaller = None
640-
641- def on_updown_activ(self, signal, *data):
642- """Upload or download activity."""
643- path = data[0]
644- if signal in ('UploadStarted', 'DownloadStarted'):
645- self._show("{0}: {1}".format(signal, path))
646- else:
647- self._show("{0}: {1}".format(signal, path))
648-
649- def on_CQ_changed(self, data):
650- """Content Queue changed, update it."""
651- def show_cq(data):
652- """Show the CONTENT queue."""
653- if data == self._last_CQ_data:
654- return
655-
656- # it changed!
657- self._last_CQ_data = data
658- self._show("Content Queue:")
659- if data:
660- for d in data:
661- print " ", self._format_CQ_data(d)
662- else:
663- print " (empty)"
664-
665- d = sync_daemon_tool.waiting_content()
666- d.addCallback(show_cq)
667-
668- def _show(self, message):
669- date = time.strftime(DATE_FMT)
670- if date != self._date:
671- self._date = date
672- print "-- {0} --".format(self._date)
673-
674- now = time.strftime(TIME_FMT)
675- print "{0} {1}".format(now, message)
676-
677- def _format_MQ_data(self, data):
678- """Format the meta queue information."""
679- if data.startswith("ListDir"):
680- m = RE_OP_LISTDIR.match(data)
681- if not m:
682- m = "Got a ListDir, but failed to match: %r" % (data,)
683- raise ValueError(m)
684- share_id, node_id = m.groups()
685- name = self._find_out_path(share_id, node_id)
686- return "ListDir: {0} ({1}:{2})".format(name, share_id, node_id)
687-
688- if data.startswith("Unlink"):
689- m = RE_OP_UNLINK.match(data)
690- if not m:
691- m = "Got an Unlink, but failed to match: %r" % (data,)
692- raise ValueError(m)
693- share_id, node_id = m.groups()
694- name = self._find_out_path(share_id, node_id)
695- return "Unlink: {0} ({1}:{2})".format(name, share_id, node_id)
696-
697- if data.startswith("MakeFile"):
698- m = RE_OP_MAKEFILE.match(data)
699- if not m:
700- m = "Got a Makefile, but failed to match: %r" % (data,)
701- raise ValueError(m)
702- share_id, parent_id, name = m.groups()
703- parent = self._find_out_path(share_id, parent_id)
704- return "MakeFile: {0} (in {1})".format(name, parent)
705-
706- # these operations are very simple
707- if data in ('GetPublicFiles', 'AccountInquiry', 'FreeSpaceInquiry',
708- 'ListVolumes', 'ListShares'):
709- return data
710-
711- return "Op? {0}".format(data)
712-
713- def _format_CQ_data(self, data):
714- """Format the content queue information."""
715- return "{operation}: {path} ({share}:{node})".format(**data)
716-
717- def _find_out_path(self, share_id, node_id):
718- """Curse the destiny!"""
719- return "?"
720-
721- def on_status_changed(self, state):
722- """Show the state nicely."""
723- self._state = state
724- if state["is_error"]:
725- print "State: Error!"
726- return
727-
728- # normal
729- self._show("State: {name} Q: {queues} C: {connection}".format(
730- **state))
731- self._check_MQ()
732-
733- def _check_MQ(self):
734- """Check MQ if we should."""
735- # check if we have something to show in MQ!
736- if self._state['name'] != 'QUEUE_MANAGER' or self._state['queues'] \
737- not in ('WORKING_ON_METADATA', 'WORKING_ON_BOTH'):
738- return
739-
740- # we have a previous call later running?
741- if self._mqcaller is not None and self._mqcaller.active():
742- self._mqcaller.cancel()
743-
744- def show_mq(data):
745- """Show the META queue."""
746- if data == self._last_MQ_data:
747- return
748-
749- # it changed!
750- self._last_MQ_data = data
751- self._show("Meta Queue:")
752- if data:
753- for d in data:
754- print " ", self._format_MQ_data(d)
755- else:
756- print " (empty)"
757-
758- d = sync_daemon_tool.waiting_metadata()
759- d.addCallback(show_mq)
760-
761- self._mqcaller = reactor.callLater(1, self._check_MQ)
762-
763-def main():
764- print "Check if it's running..."
765- if not tools.is_running():
766- print "ERROR: SD is not running!"
767- reactor.stop()
768-
769- sh = DBusHandler()
770-
771-if __name__ == "__main__":
772- reactor.callWhenRunning(main)
773- reactor.run()
774
775=== modified file 'magicicada/dbusiface.py'
776--- magicicada/dbusiface.py 2010-06-08 10:53:02 +0000
777+++ magicicada/dbusiface.py 2010-07-09 18:33:43 +0000
778@@ -32,6 +32,8 @@
779 # log!
780 logger = logging.getLogger('magicicada.dbusiface')
781
782+# we use here camel case names, because this variables are used later as
783+# classes, so pylint: disable-msg=C0103
784 QueueData = collections.namedtuple('QueueData', 'operation path share node')
785 FolderData = collections.namedtuple('FolderData',
786 'node path suggested_path subscribed volume')
787@@ -50,14 +52,23 @@
788 "(Move)\(share_id=(.*?), node_id=(.*?), old_parent_id=(.*?), "
789 "new_parent_id=(.*?), new_name=(.*?)\)")
790
791+# DBus exceptions store the type inside, as a string :|
792+DBUSERR_NOREPLY = 'org.freedesktop.DBus.Error.NoReply'
793+DBUSERR_NAMENOOWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
794+DBUSERR_PYKEYERROR = 'org.freedesktop.DBus.Python.KeyError'
795+
796+# some constants
797+NOT_SYNCHED_PATH = "Not a valid path!"
798+
799
800 def _is_retry_exception(err):
801 """Check if the exception is a retry one."""
802 if isinstance(err, dbus.exceptions.DBusException):
803- if err.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
804+ if err.get_dbus_name() == DBUSERR_NOREPLY:
805 return True
806 return False
807
808+
809 def retryable(func):
810 """Call the function until its deferred succeed (max 5 times)."""
811
812@@ -68,7 +79,7 @@
813 while opportunities:
814 try:
815 res = yield func(*a, **k)
816- except Exception, err:
817+ except Exception, err: # pylint: disable-msg=W0703
818 opportunities -= 1
819 if opportunities == 0 or not _is_retry_exception(err):
820 raise
821@@ -99,6 +110,8 @@
822 (self._on_name_owner_changed, None, 'NameOwnerChanged'),
823 (self._on_folder_created, 'Folders', 'FolderCreated'),
824 (self._on_folder_deleted, 'Folders', 'FolderDeleted'),
825+ (self._on_folder_subscribed, 'Folders', 'FolderSubscribed'),
826+ (self._on_folder_unsubscribed, 'Folders', 'FolderUnSubscribed'),
827 (self._on_share_created, 'Shares', 'ShareCreated'),
828 (self._on_share_deleted, 'Shares', 'ShareDeleted'),
829 (self._on_share_changed, 'Shares', 'ShareChanged'),
830@@ -114,7 +127,6 @@
831 signal_name=signal_name)
832 self._dbus_matches.append((match, dbus_interface, signal_name))
833
834-
835 def shutdown(self):
836 """Shut down the SyncDaemon."""
837 logger.info("DBus interface going down")
838@@ -180,6 +192,16 @@
839 logger.info("Received Folder deleted")
840 self.msd.on_sd_folders_changed()
841
842+ def _on_folder_subscribed(self, _):
843+ """Call the SD callback."""
844+ logger.info("Received Folder subscribed")
845+ self.msd.on_sd_folders_changed()
846+
847+ def _on_folder_unsubscribed(self, _):
848+ """Call the SD callback."""
849+ logger.info("Received Folder unsubscribed")
850+ self.msd.on_sd_folders_changed()
851+
852 def _on_share_created(self, _):
853 """Call the SD callback."""
854 logger.info("Received Share created")
855@@ -198,6 +220,7 @@
856 @retryable
857 def get_content_queue(self):
858 """Get the content queue from SDT."""
859+
860 def process(data):
861 """Enhance data format."""
862 logger.info("Processing Content Queue items (%d)", len(data))
863@@ -217,31 +240,32 @@
864 def _parse_mq(self, data):
865 """Parse MetaQueue string to extract its data."""
866 if data in ('AccountInquiry', 'FreeSpaceInquiry', 'GetPublicFiles',
867- 'ListShares', 'ListVolumes', 'Query'):
868+ 'ListShares', 'ListVolumes', 'Query',
869+ 'ChangePublicAccess', 'AnswerShare'):
870 return QueueData(operation=data, path=None, node=None, share=None)
871
872 m = RE_OP_LISTDIR.match(data)
873 if m:
874 op, share, node = m.groups()
875- path = '?' # we should get the real path, no API now
876+ path = '?' # we should get the real path, no API now
877 return QueueData(operation=op, path=path, node=node, share=share)
878
879 m = RE_OP_MAKEFILE.match(data)
880 if m:
881 op, share, parent, name = m.groups()
882- path = '/?.../' + name # we should get the real path, no API now
883+ path = '/?.../' + name # we should get the real path, no API now
884 return QueueData(operation=op, path=path, node=None, share=share)
885
886 m = RE_OP_MAKEDIR.match(data)
887 if m:
888 op, share, parent, name = m.groups()
889- path = '/?.../' + name # we should get the real path, no API now
890+ path = '/?.../' + name # we should get the real path, no API now
891 return QueueData(operation=op, path=path, node=None, share=share)
892
893 m = RE_OP_UNLINK.match(data)
894 if m:
895 op, share, node, = m.groups()
896- path = '?' # we should get the real path, no API now
897+ path = '?' # we should get the real path, no API now
898 return QueueData(operation=op, path=path, node=node, share=share)
899
900 m = RE_OP_MOVE.match(data)
901@@ -262,6 +286,7 @@
902 @retryable
903 def get_meta_queue(self):
904 """Get the meta queue from SDT."""
905+
906 def process(data):
907 """Enhance data format."""
908 logger.info("Processing Meta Queue items (%d)", len(data))
909@@ -280,6 +305,7 @@
910 @retryable
911 def get_folders(self):
912 """Get the folders info from SDT."""
913+
914 def process(data):
915 """Enhance data format."""
916 logger.info("Processing Folders items (%d)", len(data))
917@@ -323,8 +349,7 @@
918 try:
919 self._bus.get_name_owner('com.ubuntuone.SyncDaemon')
920 except dbus.exceptions.DBusException, err:
921- if err.get_dbus_name() != \
922- 'org.freedesktop.DBus.Error.NameHasNoOwner':
923+ if err.get_dbus_name() != DBUSERR_NAMENOOWNER:
924 raise
925 started = False
926 else:
927@@ -359,6 +384,7 @@
928 @retryable
929 def get_shares_to_me(self):
930 """Get the shares to me ('shares') info from SDT."""
931+
932 def process(data):
933 """Enhance data format."""
934 logger.info("Processing Shares To Me items (%d)", len(data))
935@@ -372,6 +398,7 @@
936 @retryable
937 def get_shares_to_others(self):
938 """Get the shares to others ('shared') info from SDT."""
939+
940 def process(data):
941 """Enhance data format."""
942 logger.info("Processing Shares To Others items (%d)", len(data))
943@@ -381,3 +408,24 @@
944 d = self.sync_daemon_tool.list_shared()
945 d.addCallback(process)
946 return d
947+
948+ @retryable
949+ def get_metadata(self, path):
950+ """Return the raw metadata."""
951+ logger.info("Getting metadata for %r", path)
952+
953+ def fix_failure(failure):
954+ """Get the failure and return a nice message."""
955+ if failure.check(dbus.exceptions.DBusException):
956+ if failure.value.get_dbus_name() == DBUSERR_PYKEYERROR:
957+ return NOT_SYNCHED_PATH
958+ return failure
959+
960+ def process(metadata):
961+ """Process the metadata."""
962+ logger.debug("Got metadata for path %r: %r", path, metadata)
963+ return dict(metadata)
964+
965+ d = self.sync_daemon_tool.get_metadata(path)
966+ d.addCallbacks(process, fix_failure)
967+ return d
968
969=== modified file 'magicicada/helpers.py'
970--- magicicada/helpers.py 2010-06-08 10:53:02 +0000
971+++ magicicada/helpers.py 2010-07-09 18:33:43 +0000
972@@ -13,18 +13,15 @@
973
974 import gtk
975 import os
976-import sys
977
978 from functools import wraps
979
980 from magicicada.magicicadaconfig import get_data_file
981
982-import gettext
983-from gettext import gettext as _
984-gettext.textdomain('magicicada')
985
986 NO_OP = lambda *a, **kw: None
987
988+
989 def get_builder(builder_file_name):
990 """Return a fully-instantiated gtk.Builder instance from specified ui file.
991
992@@ -41,21 +38,37 @@
993 builder.add_from_file(ui_filename)
994 return builder
995
996-def print_debug(f):
997- """Print debug info for 'f'."""
998-
999- @wraps(f)
1000- def inner(*args, **kwargs):
1001- """Wrap f."""
1002- sys.stderr.write('Calling %s %s %s\n' % (f.__name__, args, kwargs))
1003- result = f(*args, **kwargs)
1004- return result
1005-
1006- return inner
1007-
1008-# from
1009-# http://code.activestate.com/recipes/577081-humanized-representation-of-a-number-of-bytes/
1010-def humanize_bytes(bytes, precision=1):
1011+
1012+def log(logger):
1013+ """Log input/ouput info for 'f' using 'logger'."""
1014+
1015+ def decorator(f):
1016+ """The decorator per se."""
1017+
1018+ @wraps(f)
1019+ def inner(*args, **kwargs):
1020+ """Wrap 'f', log input args and result using 'logger'."""
1021+ name = f.__name__
1022+ result = None
1023+ logger.debug("Calling '%s' with args '%s' and kwargs '%s'.",
1024+ name, args, kwargs)
1025+ try:
1026+ result = f(*args, **kwargs)
1027+ except Exception: # pylint: disable-msg=W0703
1028+ logger.exception('%s failed with exception:', name)
1029+ logger.debug("Returning from '%s' with result '%s'.", name, result)
1030+ return result
1031+
1032+ return inner
1033+
1034+ return decorator
1035+
1036+
1037+# from http://code.activestate.com/recipes/
1038+# 577081-humanized-representation-of-a-number-of-bytes/
1039+
1040+
1041+def humanize_bytes(numbytes, precision=1):
1042 """Return a humanized string representation of a number of bytes.
1043
1044 Assumes `from __future__ import division`.
1045@@ -83,11 +96,12 @@
1046 (1<<30L, 'GB'),
1047 (1<<20L, 'MB'),
1048 (1<<10L, 'kB'),
1049- (1, 'bytes')
1050- )
1051- if bytes == 1:
1052+ (1, 'bytes'))
1053+
1054+ if numbytes == 1:
1055 return '1 byte'
1056 for factor, suffix in abbrevs:
1057- if bytes >= factor:
1058+ if numbytes >= factor:
1059 break
1060- return '%.*f %s' % (precision, bytes / factor, suffix)
1061+ # pylint: disable-msg=W0631
1062+ return '%.*f %s' % (precision, numbytes / factor, suffix)
1063
1064=== modified file 'magicicada/logger.py'
1065--- magicicada/logger.py 2010-06-08 10:53:02 +0000
1066+++ magicicada/logger.py 2010-07-09 18:33:43 +0000
1067@@ -53,4 +53,3 @@
1068 '%Y-%m-%d %H:%M:%S')
1069 handler.setFormatter(formatter)
1070 logger.setLevel(logging.DEBUG)
1071-
1072
1073=== modified file 'magicicada/magicicadaconfig.py'
1074--- magicicada/magicicadaconfig.py 2010-06-08 10:53:02 +0000
1075+++ magicicada/magicicadaconfig.py 2010-07-09 18:33:43 +0000
1076@@ -3,13 +3,15 @@
1077 # This file is in the public domain
1078 ### END LICENSE
1079
1080+"""Magicicada configuration file."""
1081+
1082 # THIS IS Magicicada CONFIGURATION FILE
1083 # YOU CAN PUT THERE SOME GLOBAL VALUE
1084 # Do not touch unless you know what you're doing.
1085 # you're warned :)
1086
1087 __all__ = [
1088- 'project_path_not_found',
1089+ 'ProjectPathNotFound',
1090 'get_data_file',
1091 'get_data_path',
1092 ]
1093@@ -21,11 +23,8 @@
1094
1095 import os
1096
1097-import gettext
1098-from gettext import gettext as _
1099-gettext.textdomain('magicicada')
1100
1101-class project_path_not_found(Exception):
1102+class ProjectPathNotFound(Exception):
1103 """Raised when we can't find the project directory."""
1104
1105
1106@@ -53,6 +52,6 @@
1107
1108 abs_data_path = os.path.abspath(path)
1109 if not os.path.exists(abs_data_path):
1110- raise project_path_not_found
1111+ raise ProjectPathNotFound
1112
1113 return abs_data_path
1114
1115=== modified file 'magicicada/syncdaemon.py'
1116--- magicicada/syncdaemon.py 2010-06-08 10:53:02 +0000
1117+++ magicicada/syncdaemon.py 2010-07-09 18:33:43 +0000
1118@@ -25,14 +25,19 @@
1119 from magicicada.dbusiface import DBusInterface
1120 from magicicada.helpers import NO_OP
1121
1122+
1123 # log!
1124 logger = logging.getLogger('magicicada.syncdaemon')
1125
1126
1127 class State(object):
1128 """Hold the state of SD."""
1129- _attrs = ['name', 'description', 'is_error', 'is_connected',
1130- 'is_online', 'queues', 'connection', 'is_started']
1131+
1132+ _attrs = ['name', 'description', 'is_error', 'is_connected',
1133+ 'is_online', 'queues', 'connection', 'is_started']
1134+
1135+ _meta = ('WORKING_ON_METADATA', 'WORKING_ON_BOTH')
1136+ _content = ('WORKING_ON_CONTENT', 'WORKING_ON_BOTH')
1137
1138 def __init__(self):
1139 # starting defaults
1140@@ -47,12 +52,16 @@
1141
1142 def __getattribute__(self, name):
1143 """Return the value if there."""
1144- if name[0] == "_":
1145+ if name[0] == "_" or name == 'set':
1146 return object.__getattribute__(self, name)
1147+ elif name == 'processing_meta':
1148+ return self.__dict__['queues'] in self._meta
1149+ elif name == 'processing_content':
1150+ return self.__dict__['queues'] in self._content
1151 else:
1152 return self.__dict__[name]
1153
1154- def _set(self, **data):
1155+ def set(self, **data):
1156 """Set the attributes from data, if allowed."""
1157 for name, value in data.iteritems():
1158 if name not in self._attrs:
1159@@ -97,6 +106,7 @@
1160 self.on_folders_changed_callback = NO_OP
1161 self.on_shares_to_me_changed_callback = NO_OP
1162 self.on_shares_to_others_changed_callback = NO_OP
1163+ self.on_metadata_ready_callback = None # mandatory
1164
1165 # mq needs to be polled to know progress
1166 self._mqcaller = None
1167@@ -104,11 +114,10 @@
1168
1169 # load initial data if ubuntuone-client already started
1170 if self.dbus.is_sd_started():
1171- self.current_state._set(is_started=True)
1172+ self.current_state.set(is_started=True)
1173 self._get_initial_data()
1174 else:
1175- self.current_state._set(is_started=False)
1176-
1177+ self.current_state.set(is_started=False)
1178
1179 def shutdown(self):
1180 """Shut down the SyncDaemon."""
1181@@ -165,14 +174,14 @@
1182 def on_sd_name_owner_changed(self, now_active):
1183 """SyncDaemon name owner changed."""
1184 logger.info("SD Name Owner changed: %s", now_active)
1185- self.current_state._set(is_started=now_active)
1186+ self.current_state.set(is_started=now_active)
1187
1188 def set_status(name, description):
1189 """Set status after the name owner change."""
1190 d = dict(name=name, description=description, is_error=False,
1191 is_connected=False, is_online=False, queues='',
1192 connection='')
1193- self.current_state._set(**d)
1194+ self.current_state.set(**d)
1195
1196 if now_active:
1197 set_status('STARTED', 'ubuntuone-client just started')
1198@@ -189,6 +198,7 @@
1199
1200 def _send_status_changed(self, name, description, is_error, is_connected,
1201 is_online, queues, connection):
1202+ """Send status changed signal."""
1203 logger.debug(" new status: name=%r, description=%r, is_error=%s, "
1204 "is_connected=%s, is_online=%s, queues=%r, connection=%r",
1205 name, description, is_error, is_connected, is_online,
1206@@ -205,7 +215,7 @@
1207 self.on_offline_callback()
1208
1209 # set current state to new values and call status changed cb
1210- self.current_state._set(name=name, description=description,
1211+ self.current_state.set(name=name, description=description,
1212 is_error=is_error, is_connected=is_connected,
1213 is_online=is_online, queues=queues,
1214 connection=connection)
1215@@ -213,7 +223,8 @@
1216 is_online, queues, connection)
1217
1218 # if corresponds, supervise MQ
1219- self._check_mq()
1220+ if self._mqcaller is None:
1221+ self._check_mq()
1222
1223 @defer.inlineCallbacks
1224 def on_sd_content_queue_changed(self):
1225@@ -226,27 +237,30 @@
1226 self.content_queue_changed_callback(new_cq)
1227
1228 @defer.inlineCallbacks
1229+ def _get_mq_data(self):
1230+ """Get MQ info and call back if needed."""
1231+ new_mq = yield self.dbus.get_meta_queue()
1232+ if new_mq != self.meta_queue:
1233+ logger.info("SD Meta Queue changed: %d items", len(new_mq))
1234+ self.meta_queue = new_mq
1235+ self.meta_queue_changed_callback(new_mq)
1236+
1237 def _check_mq(self):
1238 """Check MQ if we should."""
1239- state = self.current_state
1240- if state.queues not in ('WORKING_ON_METADATA', 'WORKING_ON_BOTH'):
1241- logger.info("Check MQ called but States not in MQ")
1242+ # cancel previous (if any) and check again later
1243+ if self._mqcaller is not None and self._mqcaller.active():
1244+ self._mqcaller.cancel()
1245+
1246+ if not self.current_state.processing_meta:
1247+ logger.info("Check MQ called, States not in MQ, call a last time")
1248+ self._mqcaller = None
1249+ self._get_mq_data()
1250 else:
1251 logger.info("Asking for MQ information")
1252
1253- # have we a previous call later still running?
1254- if self._mqcaller is not None and self._mqcaller.active():
1255- self._mqcaller.cancel()
1256-
1257 # get the info
1258- new_mq = yield self.dbus.get_meta_queue()
1259-
1260- if new_mq != self.meta_queue:
1261- logger.info("SD Meta Queue changed: %d items", len(new_mq))
1262- self.meta_queue = new_mq
1263- self.meta_queue_changed_callback(new_mq)
1264-
1265- # check again later
1266+ self._get_mq_data()
1267+
1268 self._mqcaller = reactor.callLater(self._mq_poll_time,
1269 self._check_mq)
1270
1271@@ -270,3 +284,11 @@
1272 """Tell the SyncDaemon that the user wants it to disconnect."""
1273 logger.info("Telling u1.SD to disconnect")
1274 self.dbus.disconnect()
1275+
1276+ def get_metadata(self, path):
1277+ """Get the metadata for given path."""
1278+ if self.on_metadata_ready_callback is None:
1279+ raise ValueError("Missing the mandatory cback for get_metadata.")
1280+
1281+ d = self.dbus.get_metadata(path)
1282+ d.addCallback(lambda resp: self.on_metadata_ready_callback(path, resp))
1283
1284=== modified file 'magicicada/tests/helpers.py'
1285--- magicicada/tests/helpers.py 2010-06-08 10:53:02 +0000
1286+++ magicicada/tests/helpers.py 2010-07-09 18:33:43 +0000
1287@@ -36,14 +36,18 @@
1288 def check(self, level, msg):
1289 """Check that something is logged."""
1290 for rec in self.records:
1291- if rec.levelname == level and rec.message == msg:
1292+ if rec.levelname == level and str(msg) in rec.message:
1293 return True
1294 return False
1295
1296- def check_inf(self, msg):
1297+ def check_error(self, msg):
1298+ """Shortcut for ERROR check."""
1299+ return self.check('ERROR', msg)
1300+
1301+ def check_info(self, msg):
1302 """Shortcut for INFO check."""
1303 return self.check('INFO', msg)
1304
1305- def check_dbg(self, msg):
1306+ def check_debug(self, msg):
1307 """Shortcut for DEBUG check."""
1308 return self.check('DEBUG', msg)
1309
1310=== modified file 'magicicada/tests/test_dbusiface.py'
1311--- magicicada/tests/test_dbusiface.py 2010-06-08 10:53:02 +0000
1312+++ magicicada/tests/test_dbusiface.py 2010-07-09 18:33:43 +0000
1313@@ -28,8 +28,13 @@
1314 from magicicada.tests.helpers import MementoHandler
1315
1316
1317+# It's ok to access private data in the test suite
1318+# pylint: disable-msg=W0212
1319+
1320+
1321 class FakeSessionBus(object):
1322 """Fake Session Bus."""
1323+
1324 def __init__(self, **kwargs):
1325 self._callbacks = {}
1326 self.fake_name_owner = "foo"
1327@@ -43,8 +48,12 @@
1328 del self._callbacks[(dbus_interface, signal_name)]
1329
1330 def get_name_owner(self, name):
1331- """Fakes the response of the method."""
1332+ """Fake the response of the method."""
1333 assert name == 'com.ubuntuone.SyncDaemon'
1334+
1335+ # will return a string, or raise an exception instance, never
1336+ # raise a string
1337+ # pylint: disable-msg=W0701
1338 if isinstance(self.fake_name_owner, str):
1339 return self.fake_name_owner
1340 else:
1341@@ -53,6 +62,7 @@
1342
1343 class CallLoguer(object):
1344 """Class that logs the methods called."""
1345+
1346 def __init__(self):
1347 self._called_method = None, ()
1348 self._fake_response = None
1349@@ -62,19 +72,25 @@
1350 if name[0] == "_":
1351 return object.__getattribute__(self, name)
1352 else:
1353+
1354 def f(*args):
1355+ """Fake function."""
1356 setattr(self, "_called_method", (name, args))
1357 if self._fake_response is None:
1358 # no hurt in returning a deferred, it may be needed
1359 return defer.Deferred()
1360 methname, response = self._fake_response
1361 assert methname == name
1362- return response
1363+ if isinstance(response, Exception):
1364+ return defer.fail(response)
1365+ else:
1366+ return defer.succeed(response)
1367 return f
1368
1369
1370 class FakeSDTool(CallLoguer):
1371 """Fake real SyncDaemonTool."""
1372+
1373 def __init__(self, _):
1374 CallLoguer.__init__(self)
1375
1376@@ -104,9 +120,9 @@
1377 return called_args
1378
1379 def fake_sdt_response(self, method_name, response):
1380- """Fakes SDT answer in deferred mode."""
1381- self.dbus.sync_daemon_tool._fake_response = (method_name,
1382- defer.succeed(response))
1383+ """Fake SDT answer in deferred mode."""
1384+ self.dbus.sync_daemon_tool._fake_response = (method_name, response)
1385+
1386
1387 class TestSignalHooking(SafeTests):
1388 """Signal hooking tests.
1389@@ -114,6 +130,7 @@
1390 We can not check if the methods are really called, because DBus holds the
1391 method object itself, so no chance in monkeypatching.
1392 """
1393+
1394 def _get_hooked(self, iface, signal):
1395 """Return the hooked method if any."""
1396 if iface is None:
1397@@ -152,6 +169,16 @@
1398 self.assertEqual(self._get_hooked('Folders', 'FolderDeleted'),
1399 self.dbus._on_folder_deleted)
1400
1401+ def test_folder_subscribed_changed(self):
1402+ """Test folder subscribed changed callback."""
1403+ self.assertEqual(self._get_hooked('Folders', 'FolderSubscribed'),
1404+ self.dbus._on_folder_subscribed)
1405+
1406+ def test_folder_unsubscribed_changed(self):
1407+ """Test folder unsubscribed changed callback."""
1408+ self.assertEqual(self._get_hooked('Folders', 'FolderUnSubscribed'),
1409+ self.dbus._on_folder_unsubscribed)
1410+
1411 def test_share_created(self):
1412 """Test share created callback."""
1413 self.assertEqual(self._get_hooked('Shares', 'ShareCreated'),
1414@@ -188,7 +215,7 @@
1415
1416
1417 class TestDataProcessingStatus(SafeTests):
1418- """Processes Status before sending it to SyncDaemon."""
1419+ """Process Status before sending it to SyncDaemon."""
1420
1421 @defer.inlineCallbacks
1422 def test_get_status(self):
1423@@ -224,7 +251,7 @@
1424
1425
1426 class TestDataProcessingNameOwner(SafeTests):
1427- """Processes Name Owner data before sending it to SyncDaemon."""
1428+ """Process Name Owner data before sending it to SyncDaemon."""
1429
1430 def test_name_owner_changed_no_syncdaemon(self):
1431 """Test name owner changed callback."""
1432@@ -245,7 +272,7 @@
1433
1434
1435 class TestDataProcessingCQ(SafeTests):
1436- """Processes CQ data before sending it to SyncDaemon."""
1437+ """Process CQ data before sending it to SyncDaemon."""
1438
1439 @defer.inlineCallbacks
1440 def test_nodata(self):
1441@@ -288,7 +315,7 @@
1442
1443
1444 class TestDataProcessingMQ(SafeTests):
1445- """Processes MQ data before sending it to SyncDaemon."""
1446+ """Process MQ data before sending it to SyncDaemon."""
1447
1448 @defer.inlineCallbacks
1449 def test_nodata(self):
1450@@ -461,9 +488,33 @@
1451 self.assertEqual(data.share, 'a')
1452 self.assertEqual(data.node, 'b')
1453
1454+ @defer.inlineCallbacks
1455+ def test_ChangePublicAccess(self):
1456+ """Test meta with ChangePublicAccess."""
1457+ cmd = 'ChangePublicAccess'
1458+ self.fake_sdt_response('waiting_metadata', [cmd])
1459+ rcv = yield self.dbus.get_meta_queue()
1460+ data = rcv[0]
1461+ self.assertEqual(data.operation, 'ChangePublicAccess')
1462+ self.assertEqual(data.path, None)
1463+ self.assertEqual(data.share, None)
1464+ self.assertEqual(data.node, None)
1465+
1466+ @defer.inlineCallbacks
1467+ def test_AnswerShare(self):
1468+ """Test meta with AnswerShare."""
1469+ cmd = 'AnswerShare'
1470+ self.fake_sdt_response('waiting_metadata', [cmd])
1471+ rcv = yield self.dbus.get_meta_queue()
1472+ data = rcv[0]
1473+ self.assertEqual(data.operation, 'AnswerShare')
1474+ self.assertEqual(data.path, None)
1475+ self.assertEqual(data.share, None)
1476+ self.assertEqual(data.node, None)
1477+
1478
1479 class TestDataProcessingFolders(SafeTests):
1480- """Processes Folders data before sending it to SyncDaemon."""
1481+ """Process Folders data before sending it to SyncDaemon."""
1482
1483 @defer.inlineCallbacks
1484 def test_nodata(self):
1485@@ -520,10 +571,40 @@
1486 self.dbus._on_folder_deleted(None)
1487 self.get_msd_called("on_sd_folders_changed")
1488
1489+ def test_folders_changed_from_subscribed(self):
1490+ """Test folders changed callback from subscribed."""
1491+ self.dbus._on_folder_subscribed(None)
1492+ self.get_msd_called("on_sd_folders_changed")
1493+
1494+ def test_folders_changed_from_unsubscribed(self):
1495+ """Test folders changed callback from unsubscribed."""
1496+ self.dbus._on_folder_unsubscribed(None)
1497+ self.get_msd_called("on_sd_folders_changed")
1498+
1499+
1500+class TestDataProcessingMetadata(SafeTests):
1501+ """Process Metadata data before sending it to SyncDaemon."""
1502+
1503+ @defer.inlineCallbacks
1504+ def test_info_ok(self):
1505+ """Test get metadata and see response."""
1506+ md = dbus.Dictionary({'a': 3, 'c': 4}, signature=dbus.Signature('ss'))
1507+ self.fake_sdt_response('get_metadata', md)
1508+ rcv = yield self.dbus.get_metadata('path')
1509+ self.assertEqual(rcv, dict(a=3, c=4))
1510+
1511+ @defer.inlineCallbacks
1512+ def test_info_bad(self):
1513+ """Test get metadata and get the error."""
1514+ exc = dbus.exceptions.DBusException(
1515+ name='org.freedesktop.DBus.Python.KeyError')
1516+ self.fake_sdt_response('get_metadata', exc)
1517+ rcv = yield self.dbus.get_metadata('not a real path')
1518+ self.assertEqual(rcv, dbusiface.NOT_SYNCHED_PATH)
1519
1520
1521 class TestDataProcessingShares(SafeTests):
1522- """Processes Shares data before sending it to SyncDaemon."""
1523+ """Process Shares data before sending it to SyncDaemon."""
1524
1525 @defer.inlineCallbacks
1526 def test_sharestome_nodata(self):
1527@@ -693,68 +774,74 @@
1528
1529 def test_instancing(self):
1530 """Just logged SD instancing."""
1531- self.assertTrue(self.handler.check_inf("DBus interface starting"))
1532+ self.assertTrue(self.handler.check_info("DBus interface starting"))
1533
1534 def test_shutdown(self):
1535 """Log when SD shutdowns."""
1536 self.dbus.shutdown()
1537- self.assertTrue(self.handler.check_inf("DBus interface going down"))
1538+ self.assertTrue(self.handler.check_info("DBus interface going down"))
1539
1540 def test_waiting_content(self):
1541 """Test call to waiting content."""
1542 self.dbus.get_content_queue()
1543- self.assertTrue(self.handler.check_inf("Getting content queue"))
1544+ self.assertTrue(self.handler.check_info("Getting content queue"))
1545
1546 def test_waiting_meta(self):
1547 """Test call to waiting meta."""
1548 self.dbus.get_meta_queue()
1549- self.assertTrue(self.handler.check_inf("Getting meta queue"))
1550+ self.assertTrue(self.handler.check_info("Getting meta queue"))
1551
1552 def test_get_status(self):
1553 """Test call to status."""
1554 self.dbus.get_status()
1555- self.assertTrue(self.handler.check_inf("Getting status"))
1556+ self.assertTrue(self.handler.check_info("Getting status"))
1557
1558 def test_get_folders(self):
1559 """Test call to folders."""
1560 self.dbus.get_folders()
1561- self.assertTrue(self.handler.check_inf("Getting folders"))
1562+ self.assertTrue(self.handler.check_info("Getting folders"))
1563+
1564+ def test_get_metadata(self):
1565+ """Test call to metadata."""
1566+ self.dbus.get_metadata('path')
1567+ msg = "Getting metadata for u'path'"
1568+ self.assertTrue(self.handler.check_info(msg))
1569
1570 def test_get_shares_to_me(self):
1571 """Test call to shares to me."""
1572 self.dbus.get_shares_to_me()
1573- self.assertTrue(self.handler.check_inf("Getting shares to me"))
1574+ self.assertTrue(self.handler.check_info("Getting shares to me"))
1575
1576 def test_get_shares_to_other(self):
1577 """Test call to shares to others."""
1578 self.dbus.get_shares_to_others()
1579- self.assertTrue(self.handler.check_inf("Getting shares to others"))
1580+ self.assertTrue(self.handler.check_info("Getting shares to others"))
1581
1582 def test_is_sd_started(self):
1583 """Test call to is_sd_started."""
1584 self.dbus.is_sd_started()
1585- self.assertTrue(self.handler.check_inf(
1586+ self.assertTrue(self.handler.check_info(
1587 "Checking if SD is started: True"))
1588
1589 def test_start(self):
1590 """Test call to start."""
1591 self.dbus.start()
1592- self.assertTrue(self.handler.check_inf("Calling start"))
1593+ self.assertTrue(self.handler.check_info("Calling start"))
1594
1595 def test_quit(self):
1596 """Test call to quit."""
1597 self.dbus.quit()
1598- self.assertTrue(self.handler.check_inf("Calling quit"))
1599+ self.assertTrue(self.handler.check_info("Calling quit"))
1600
1601 def test_connect(self):
1602 """Test call to connect."""
1603 self.dbus.connect()
1604- self.assertTrue(self.handler.check_inf("Calling connect"))
1605+ self.assertTrue(self.handler.check_info("Calling connect"))
1606
1607 def test_disconnect(self):
1608 """Test call to disconnect."""
1609 self.dbus.disconnect()
1610- self.assertTrue(self.handler.check_inf("Calling disconnect"))
1611+ self.assertTrue(self.handler.check_info("Calling disconnect"))
1612
1613 def test_status_changed(self):
1614 """Test status changed callback."""
1615@@ -762,58 +849,71 @@
1616 is_connected='True', is_online='', queues='queues',
1617 connection='connection')
1618 self.dbus._on_status_changed(d)
1619- self.assertTrue(self.handler.check_inf("Received Status changed"))
1620- self.assertTrue(self.handler.check_dbg("Status changed data: %r" % d))
1621+ self.assertTrue(self.handler.check_info("Received Status changed"))
1622+ msg = "Status changed data: %r" % d
1623+ self.assertTrue(self.handler.check_debug(msg))
1624
1625 def test_content_queue_changed(self):
1626 """Test content queue changed callback."""
1627 self.dbus._on_content_queue_changed("foo")
1628- self.assertTrue(self.handler.check_inf(
1629+ self.assertTrue(self.handler.check_info(
1630 "Received Content Queue changed"))
1631
1632 def test_name_owner_changed_other(self):
1633 """Test name owner changed callback, no SD."""
1634 self.dbus._on_name_owner_changed("other", "", "T")
1635- self.assertFalse(self.handler.check_inf("Received Name Owner changed"))
1636+ msg = "Received Name Owner changed"
1637+ self.assertFalse(self.handler.check_info(msg))
1638
1639 def test_name_owner_changed_syncdaemon(self):
1640 """Test name owner changed callback, SD value ok."""
1641 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "", "T")
1642- self.assertTrue(self.handler.check_inf("Received Name Owner changed"))
1643- self.assertTrue(self.handler.check_dbg("Name Owner data: u'' u'T'"))
1644+ self.assertTrue(self.handler.check_info("Received Name Owner changed"))
1645+ self.assertTrue(self.handler.check_debug("Name Owner data: u'' u'T'"))
1646
1647 def test_name_owner_changed_yes_syncdaemon_TF(self):
1648 """Test name owner changed callback, SD value bad."""
1649 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "F", "T")
1650- self.assertTrue(self.handler.check_inf("Received Name Owner changed"))
1651- self.assertTrue(self.handler.check_dbg("Name Owner data: u'F' u'T'"))
1652+ self.assertTrue(self.handler.check_info("Received Name Owner changed"))
1653+ self.assertTrue(self.handler.check_debug("Name Owner data: u'F' u'T'"))
1654 self.assertTrue(self.handler.check("ERROR",
1655 "Name Owner invalid data: Same bool in old and new!"))
1656
1657 def test_folder_created_changed(self):
1658 """Test folder created changed callback."""
1659 self.dbus._on_folder_created("foo")
1660- self.assertTrue(self.handler.check_inf("Received Folder created"))
1661+ self.assertTrue(self.handler.check_info("Received Folder created"))
1662
1663 def test_folder_deleted_changed(self):
1664 """Test folder deleted changed callback."""
1665 self.dbus._on_folder_deleted("foo")
1666- self.assertTrue(self.handler.check_inf("Received Folder deleted"))
1667+ self.assertTrue(self.handler.check_info("Received Folder deleted"))
1668+
1669+ def test_folder_subscribed_changed(self):
1670+ """Test folder subscribed changed callback."""
1671+ self.dbus._on_folder_subscribed("foo")
1672+ self.assertTrue(self.handler.check_info("Received Folder subscribed"))
1673+
1674+ def test_folder_unsubscribed_changed(self):
1675+ """Test folder unsubscribed changed callback."""
1676+ self.dbus._on_folder_unsubscribed("foo")
1677+ self.assertTrue(self.handler.check_info(
1678+ "Received Folder unsubscribed"))
1679
1680 def test_share_created(self):
1681 """Test share created callback."""
1682 self.dbus._on_share_created("foo")
1683- self.assertTrue(self.handler.check_inf("Received Share created"))
1684+ self.assertTrue(self.handler.check_info("Received Share created"))
1685
1686 def test_share_deleted(self):
1687 """Test share deleted callback."""
1688 self.dbus._on_share_deleted("foo")
1689- self.assertTrue(self.handler.check_inf("Received Share deleted"))
1690+ self.assertTrue(self.handler.check_info("Received Share deleted"))
1691
1692 def test_share__changed(self):
1693 """Test share changed callback."""
1694 self.dbus._on_share_changed("foo")
1695- self.assertTrue(self.handler.check_inf("Received Share changed"))
1696+ self.assertTrue(self.handler.check_info("Received Share changed"))
1697
1698 @defer.inlineCallbacks
1699 def test_content_queue_processing(self):
1700@@ -821,9 +921,9 @@
1701 c = dict(operation='oper', path='path', share='share', node='node')
1702 self.fake_sdt_response('waiting_content', [c])
1703 yield self.dbus.get_content_queue()
1704- self.assertTrue(self.handler.check_inf(
1705+ self.assertTrue(self.handler.check_info(
1706 "Processing Content Queue items (1)"))
1707- self.assertTrue(self.handler.check_dbg(
1708+ self.assertTrue(self.handler.check_debug(
1709 " Content Queue data: %s" % c))
1710
1711 @defer.inlineCallbacks
1712@@ -831,9 +931,9 @@
1713 """Test with one item in the queue."""
1714 self.fake_sdt_response('waiting_metadata', ['ListShares'])
1715 yield self.dbus.get_meta_queue()
1716- self.assertTrue(self.handler.check_inf(
1717+ self.assertTrue(self.handler.check_info(
1718 "Processing Meta Queue items (1)"))
1719- self.assertTrue(self.handler.check_dbg(
1720+ self.assertTrue(self.handler.check_debug(
1721 " Meta Queue data: u'ListShares'"))
1722
1723 @defer.inlineCallbacks
1724@@ -843,8 +943,18 @@
1725 suggested_path=u'sgp', type='UDF', volume_id='vid')
1726 self.fake_sdt_response('get_folders', [d])
1727 yield self.dbus.get_folders()
1728- self.assertTrue(self.handler.check_inf("Processing Folders items (1)"))
1729- self.assertTrue(self.handler.check_dbg(" Folders data: %r" % d))
1730+ msg = "Processing Folders items (1)"
1731+ self.assertTrue(self.handler.check_info(msg))
1732+ self.assertTrue(self.handler.check_debug(" Folders data: %r" % d))
1733+
1734+ @defer.inlineCallbacks
1735+ def test_metadata_processing(self):
1736+ """Test get metadata."""
1737+ d = dict(lot_of_data="I don't care")
1738+ self.fake_sdt_response('get_metadata', d)
1739+ yield self.dbus.get_metadata('path')
1740+ self.assertTrue(self.handler.check_debug(
1741+ "Got metadata for path u'path': %r" % d))
1742
1743 @defer.inlineCallbacks
1744 def test_sharestome_processing(self):
1745@@ -855,9 +965,9 @@
1746 volume_id=u'vol', type=u'Share')
1747 self.fake_sdt_response('get_shares', [d])
1748 yield self.dbus.get_shares_to_me()
1749- self.assertTrue(self.handler.check_inf(
1750+ self.assertTrue(self.handler.check_info(
1751 "Processing Shares To Me items (1)"))
1752- self.assertTrue(self.handler.check_dbg(" Share data: %r" % d))
1753+ self.assertTrue(self.handler.check_debug(" Share data: %r" % d))
1754
1755 @defer.inlineCallbacks
1756 def test_sharestoothers_processing(self):
1757@@ -868,16 +978,17 @@
1758 volume_id=u'vol', type=u'Shared')
1759 self.fake_sdt_response('list_shared', [d])
1760 yield self.dbus.get_shares_to_others()
1761- self.assertTrue(self.handler.check_inf(
1762+ self.assertTrue(self.handler.check_info(
1763 "Processing Shares To Others items (1)"))
1764- self.assertTrue(self.handler.check_dbg(" Share data: %r" % d))
1765+ self.assertTrue(self.handler.check_debug(" Share data: %r" % d))
1766
1767
1768 class RetryDecoratorTests(TwistedTestCase):
1769 """Test the retry decorator."""
1770
1771 class Helper(object):
1772- """Fails some times, finally succeeds."""
1773+ """Fail some times, finally succeed."""
1774+
1775 def __init__(self, limit, excep=None):
1776 self.cant = 0
1777 self.limit = limit
1778@@ -915,7 +1026,7 @@
1779 self.assertTrue(dbusiface._is_retry_exception(err))
1780
1781 def get_decorated_func(self, func):
1782- """Executes the test calling the received function."""
1783+ """Execute the test calling the received function."""
1784
1785 @dbusiface.retryable
1786 def f():
1787
1788=== modified file 'magicicada/tests/test_magicicada.py'
1789--- magicicada/tests/test_magicicada.py 2010-06-08 10:53:02 +0000
1790+++ magicicada/tests/test_magicicada.py 2010-07-09 18:33:43 +0000
1791@@ -18,6 +18,9 @@
1792
1793 """Tests for magicicada."""
1794
1795+import logging
1796+import sys
1797+
1798 from functools import wraps
1799
1800 import gobject
1801@@ -26,33 +29,57 @@
1802
1803 from twisted.trial.unittest import TestCase
1804
1805-from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, syncdaemon
1806+from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, \
1807+ UBUNTU_ONE_ROOT, syncdaemon
1808 from magicicada.dbusiface import QueueData, FolderData, ShareData
1809-from magicicada.helpers import NO_OP, humanize_bytes
1810+from magicicada.helpers import NO_OP, humanize_bytes, get_data_file
1811+from magicicada.tests.helpers import MementoHandler
1812+
1813+
1814+# It's ok to access private data in the test suite
1815+# pylint: disable-msg=W0212
1816+# Arguments number differs from overridden method
1817+# pylint: disable-msg=W0221
1818+
1819
1820 def process_gtk_pendings():
1821- while gtk.events_pending(): gtk.main_iteration()
1822+ """Process all gtk pending events."""
1823+ while gtk.events_pending():
1824+ gtk.main_iteration()
1825+
1826
1827 def close_dialog((dialog, test)):
1828 """Call the 'test', close 'dialog'."""
1829- try:
1830- process_gtk_pendings()
1831- test()
1832- process_gtk_pendings()
1833- finally:
1834- dialog.response(gtk.RESPONSE_CLOSE)
1835- process_gtk_pendings()
1836- return False # do not be called again
1837+ response = gtk.RESPONSE_CLOSE
1838+ try:
1839+ process_gtk_pendings()
1840+ test()
1841+ finally:
1842+ dialog.response(response)
1843+ process_gtk_pendings()
1844+ return False # do not be called again
1845+
1846+
1847+def test_and_click((button, test)):
1848+ """Call the 'test', and click 'button'."""
1849+ try:
1850+ test()
1851+ finally:
1852+ button.clicked()
1853+ return False # do not be called again
1854
1855
1856 class FakedSyncdaemon(object):
1857 """A faked syncdaemon."""
1858
1859 def __init__(self):
1860+ self._meta_path = None
1861+
1862 self.current_state = syncdaemon.State()
1863 self.meta_queue = []
1864 self.content_queue = []
1865 self.folders = []
1866+ self.processing_meta = False
1867
1868 self.on_started_callback = NO_OP
1869 self.on_stopped_callback = NO_OP
1870@@ -63,13 +90,16 @@
1871 self.status_changed_callback = NO_OP
1872 self.content_queue_changed_callback = NO_OP
1873 self.meta_queue_changed_callback = NO_OP
1874+ self.on_metadata_ready_callback = None # mandatory
1875 self.shutdown = NO_OP
1876
1877 self.start = lambda: setattr(self.current_state, 'is_started', True)
1878 self.quit = lambda: setattr(self.current_state, 'is_started', False)
1879- self.connect = lambda: setattr(self.current_state, 'is_connected', True)
1880- self.disconnect = \
1881- lambda: setattr(self.current_state, 'is_connected', False)
1882+ self.connect = lambda: setattr(self.current_state,
1883+ 'is_connected', True)
1884+ self.disconnect = lambda: \
1885+ setattr(self.current_state, 'is_connected', False)
1886+ self.get_metadata = lambda path: setattr(self, '_meta_path', path)
1887
1888
1889 class MagicicadaUITestCase(TestCase):
1890@@ -78,13 +108,14 @@
1891 def setUp(self):
1892 """Init."""
1893 self.ui = MagicicadaUI(syncdaemon_class=FakedSyncdaemon)
1894- self._called = False
1895- self.set_called = lambda *args, **kwargs: setattr(self, '_called', True)
1896+ self.called = False
1897+ self.response = None
1898+ self.set_called = lambda *args, **kwargs: setattr(self, 'called', True)
1899
1900 def tearDown(self):
1901 """Cleanup."""
1902 self.ui.on_main_window_destroy(self.ui.main_window)
1903- self._called = False
1904+ self.called = False
1905
1906 def do_start(self):
1907 """Simulate that start fully happened."""
1908@@ -106,14 +137,16 @@
1909 result.append(data_type(**kwargs))
1910 return result
1911
1912- def assert_store_correct(self, store, items):
1913+ def assert_store_correct(self, store, items, markup=None):
1914 """Test that 'store' has 'items' as content."""
1915 msg = 'amount of rows for %s must be %s (got %s).'
1916 self.assertEqual(len(store), len(items),
1917 msg % (store, len(items), len(store)))
1918+
1919 # assert rows content equal to items content
1920 tree_iter = store.get_iter_root()
1921 tmp = list(reversed(items))
1922+ do_markup = markup is not None
1923 msg = "column %i ('%s') must be '%s' (got '%s' instead)"
1924 while tree_iter is not None:
1925 head = tmp.pop()
1926@@ -122,27 +155,68 @@
1927 expected = getattr(head, field)
1928 if store.get_column_type(i).name == 'gboolean':
1929 expected = bool(expected)
1930- self.assertEqual(expected, actual, msg % (i, field, expected, actual))
1931+ elif do_markup:
1932+ expected = markup(expected)
1933+ self.assertEqual(expected, actual,
1934+ msg % (i, field, expected, actual))
1935
1936 tree_iter = store.iter_next(tree_iter)
1937+ do_markup = False # only for first row
1938
1939 def assert_indicator_disabled(self, indicator):
1940 """Test that 'indicator' is not sensitive."""
1941- self.assertFalse(indicator.is_sensitive(), 'indicator is not sensitive')
1942+ self.assertFalse(indicator.is_sensitive(),
1943+ 'indicator must not be sensitive.')
1944
1945 def assert_indicator_ready(self, indicator):
1946 """Test that 'indicator' is sensitive and green."""
1947- self.assertTrue(indicator.is_sensitive(), 'indicator is sensitive')
1948- expected = indicator.get_pixbuf() # a test on its own
1949+ self.assertTrue(indicator.is_sensitive(),
1950+ 'indicator must be sensitive.')
1951+ expected = indicator.get_pixbuf() # a test on its own
1952 self.assertEqual(self.ui.active_indicator, expected,
1953- 'indicator is the correct pixbuf')
1954+ 'indicator must have the correct pixbuf.')
1955
1956 def assert_indicator_loading(self, indicator):
1957 """Test that 'indicator' is sensitive and loading."""
1958- self.assertTrue(indicator.is_sensitive(), 'indicator is sensitive')
1959- expected = indicator.get_animation() # a test on its own
1960+ self.assertTrue(indicator.is_sensitive(),
1961+ 'indicator must be sensitive.')
1962+ expected = indicator.get_animation() # a test on its own
1963 self.assertEqual(self.ui.loading_animation, expected,
1964- 'indicator is the correct animation')
1965+ 'indicator must have the correct animation.')
1966+
1967+ def assert_widget_availability(self, widget_name, enabled=True):
1968+ """Check button availability according to 'enabled'."""
1969+ widget = getattr(self.ui, widget_name)
1970+ self.assertTrue(self.ui.widget_is_visible(widget),
1971+ '%s should be visible' % widget_name)
1972+ sensitive = widget.is_sensitive()
1973+ msg = '%s should %sbe sensitive'
1974+ self.assertTrue(sensitive if enabled else not sensitive,
1975+ msg % (widget_name, '' if enabled else 'not '))
1976+
1977+ def assert_dialog_properties(self, dialog_name, title, size=(600, 300),
1978+ modal=True):
1979+ """The dialog 'dialog_name' has correct properties."""
1980+ dialog = getattr(self.ui, dialog_name)
1981+ actual = dialog.size_request()
1982+ msg = 'size must be %s (got %s instead).'
1983+ self.assertEquals(size, actual, msg % (size, actual))
1984+
1985+ msg = '%s must %sbe modal.'
1986+ self.assertEqual(modal, dialog.get_modal(),
1987+ msg % (dialog_name, '' if modal else 'not '))
1988+
1989+ position = dialog.get_property('window-position')
1990+ self.assertEqual(gtk.WIN_POS_CENTER, position,
1991+ '%s must be centered.' % dialog_name)
1992+
1993+ actual = dialog.get_title()
1994+ msg = '%s title must be %s (got %s instead)'
1995+ self.assertEqual(title, actual, msg % (dialog_name, title, actual))
1996+
1997+ msg = '%s must have main_window as parent.'
1998+ #self.assertTrue(dialog.parent is self.ui.main_window,
1999+ # msg % dialog_name)
2000
2001
2002 class MagicicadaUIBasicTestCase(MagicicadaUITestCase):
2003@@ -157,7 +231,7 @@
2004 """SyncDaemon instance is shutdown at destroy time."""
2005 self.patch(self.ui.sd, 'shutdown', self.set_called)
2006 self.ui.on_main_window_destroy(self.ui.main_window)
2007- self.assertTrue(self._called,
2008+ self.assertTrue(self.called,
2009 'syncdaemon.shutdown must be called at destroy time.')
2010
2011 def test_main_window_is_visible(self):
2012@@ -192,7 +266,7 @@
2013 """Update is called at startup."""
2014 self.patch(MagicicadaUI, 'update', self.set_called)
2015 self.ui = MagicicadaUI(syncdaemon_class=FakedSyncdaemon)
2016- self.assertTrue(self._called,
2017+ self.assertTrue(self.called,
2018 'update was called at startup.')
2019
2020
2021@@ -215,11 +289,11 @@
2022 """Test on_start_clicked."""
2023 self.patch(self.ui.sd, 'start', self.set_called)
2024 self.ui.on_start_clicked(self.ui.start)
2025- self.assertTrue(self._called, 'syncdaemon.start was called.')
2026+ self.assertTrue(self.called, 'syncdaemon.start was called.')
2027
2028 def test_on_connect_clicked(self):
2029 """Test on_connect_clicked."""
2030- self.do_start() # need to be started
2031+ self.do_start() # need to be started
2032 self.ui.on_connect_clicked(self.ui.connect)
2033
2034 self.assertTrue(self.ui.widget_is_visible(self.ui.connect))
2035@@ -234,7 +308,7 @@
2036 """Test on_connect_clicked."""
2037 self.patch(self.ui.sd, 'connect', self.set_called)
2038 self.ui.on_connect_clicked(self.ui.connect)
2039- self.assertTrue(self._called, 'syncdaemon.connect was called.')
2040+ self.assertTrue(self.called, 'syncdaemon.connect was called.')
2041
2042 def test_on_stop_clicked(self):
2043 """Test on_stop_clicked."""
2044@@ -243,7 +317,7 @@
2045 self.patch(self.ui, 'on_disconnect_clicked', self.set_called)
2046 self.ui.on_stop_clicked(self.ui.stop)
2047
2048- self.assertFalse(self._called, 'on_disconnect_clicked was not called.')
2049+ self.assertFalse(self.called, 'on_disconnect_clicked was not called.')
2050
2051 self.assertFalse(self.ui.widget_is_visible(self.ui.start))
2052 self.assertTrue(self.ui.widget_is_visible(self.ui.stop))
2053@@ -259,13 +333,13 @@
2054 self.patch(self.ui, 'on_disconnect_clicked', self.set_called)
2055 self.ui.on_stop_clicked(self.ui.stop)
2056
2057- self.assertTrue(self._called, 'on_disconnect_clicked was called.')
2058+ self.assertTrue(self.called, 'on_disconnect_clicked was called.')
2059
2060 def test_on_stop_clicked_stops_syncdaemon(self):
2061 """Test on_stop_clicked."""
2062 self.patch(self.ui.sd, 'quit', self.set_called)
2063 self.ui.on_stop_clicked(self.ui.stop)
2064- self.assertTrue(self._called, 'syncdaemon.quit was called.')
2065+ self.assertTrue(self.called, 'syncdaemon.quit was called.')
2066
2067 def test_on_disconnect_clicked(self):
2068 """Test on_disconnect_clicked."""
2069@@ -280,7 +354,7 @@
2070 """Test on_disconnect_clicked."""
2071 self.patch(self.ui.sd, 'disconnect', self.set_called)
2072 self.ui.on_disconnect_clicked(self.ui.disconnect)
2073- self.assertTrue(self._called, 'syncdaemon.disconnect was called.')
2074+ self.assertTrue(self.called, 'syncdaemon.disconnect was called.')
2075
2076
2077 class MagicicadaUISystrayIconTestCase(MagicicadaUITestCase):
2078@@ -294,14 +368,15 @@
2079
2080 def test_main_window_is_shown_when_clicked_after_hidden(self):
2081 """Main window is shown when the icon is clicked after hidden."""
2082- self.ui.on_status_icon_activate(self.ui.status_icon) # hide
2083- self.ui.on_status_icon_activate(self.ui.status_icon) # show
2084+ self.ui.on_status_icon_activate(self.ui.status_icon) # hide
2085+ self.ui.on_status_icon_activate(self.ui.status_icon) # show
2086 msg = 'main_window should be visible when icon clicked after hidden.'
2087 self.assertTrue(self.ui.widget_is_visible(self.ui.main_window), msg)
2088
2089
2090 def skip_abstract_class(test):
2091 """If 'test' belongs to an abstract class, don't run it."""
2092+
2093 @wraps(test)
2094 def inner(klass):
2095 """Execute 'test' only if not in an abstract class."""
2096@@ -336,6 +411,24 @@
2097 # operation path share node
2098 return res
2099
2100+ def expected_markup(self, value):
2101+ """Return the markup for row at index 'i'."""
2102+ processing_meta = self.name == META_QUEUE and \
2103+ self.ui.sd.current_state.processing_meta
2104+ processing_content = self.name == CONTENT_QUEUE and \
2105+ self.ui.sd.current_state.processing_content
2106+ assert not (processing_meta and processing_content)
2107+ must_highlight = self.ui.sd.current_state.is_online and \
2108+ (processing_meta or processing_content)
2109+ result = self.ui.CURRENT_ROW % value \
2110+ if must_highlight and value is not None else value
2111+ return result
2112+
2113+ def assert_store_correct(self, store, items):
2114+ """Test that 'store' has 'items' as content."""
2115+ args = (store, items, self.expected_markup)
2116+ super(_MagicicadaUIQueueTestCase, self).assert_store_correct(*args)
2117+
2118 @skip_abstract_class
2119 def test_callback_is_connected(self):
2120 """Queue changed callback is connected."""
2121@@ -364,6 +457,13 @@
2122 self.assert_store_correct(self.queue_store, [])
2123
2124 @skip_abstract_class
2125+ def test_on_queue_changed_handles_an_item_none(self):
2126+ """On queue changed handles None as items."""
2127+ items = [QueueData(operation='Test', path='', share=None, node=None)]
2128+ self.sd_changed(items)
2129+ self.assert_store_correct(self.queue_store, items)
2130+
2131+ @skip_abstract_class
2132 def test_model_is_cleared_before_updating(self):
2133 """The model is cleared before upadting with a new set of data."""
2134 items = self.build_some_data()
2135@@ -400,7 +500,7 @@
2136 cb = 'on_%s_queue_changed' % self.name
2137 self.patch(self.ui, cb, self.set_called)
2138 self.ui.on_stopped()
2139- self.assertTrue(self._called,
2140+ self.assertTrue(self.called,
2141 '%s was called on_stopped.' % cb)
2142
2143 @skip_abstract_class
2144@@ -425,12 +525,64 @@
2145 self.do_start()
2146 self.assert_store_correct(self.queue_store, items)
2147
2148- items = items[:len(items)/2]
2149+ items = items[:len(items) / 2]
2150 self.set_sd_queue(items)
2151 self.ui.on_stop_clicked(self.ui.stop)
2152 self.ui.on_stopped()
2153 self.assert_store_correct(self.queue_store, items)
2154
2155+ def assert_current_processing_row_is_different(self):
2156+ """Row being processed is highlighted."""
2157+ items = self.build_some_data()
2158+ self.sd_changed(items)
2159+
2160+ item = items[0]
2161+ attrs = type(item)._fields
2162+
2163+ markup = self.expected_markup
2164+ expected = tuple(markup(getattr(item, attr)) for attr in attrs)
2165+
2166+ iter_root = self.queue_store.get_iter_root()
2167+ actual = self.queue_store.get(iter_root, *xrange(len(attrs)))
2168+
2169+ msg = 'first row for %s queue must be %s (got %s instead)' % \
2170+ (self.name, expected, actual)
2171+ self.assertEqual(expected, actual, msg)
2172+
2173+ @skip_abstract_class
2174+ def test_current_processing_row_is_different_if_online(self):
2175+ """Row being processed is highlighted."""
2176+ self.ui.sd.current_state.set(is_online=True)
2177+
2178+ self.ui.sd.current_state.set(queues='')
2179+ self.assert_current_processing_row_is_different()
2180+
2181+ self.ui.sd.current_state.set(queues='WORKING_ON_METADATA')
2182+ self.assert_current_processing_row_is_different()
2183+
2184+ self.ui.sd.current_state.set(queues='WORKING_ON_CONTENT')
2185+ self.assert_current_processing_row_is_different()
2186+
2187+ self.ui.sd.current_state.set(queues='WORKING_ON_BOTH')
2188+ self.assert_current_processing_row_is_different()
2189+
2190+ @skip_abstract_class
2191+ def test_current_processing_row_is_not_different_if_offline(self):
2192+ """Row being processed is highlighted."""
2193+ self.ui.sd.current_state.set(is_online=False)
2194+
2195+ self.ui.sd.current_state.set(queues='')
2196+ self.assert_current_processing_row_is_different()
2197+
2198+ self.ui.sd.current_state.set(queues='WORKING_ON_METADATA')
2199+ self.assert_current_processing_row_is_different()
2200+
2201+ self.ui.sd.current_state.set(queues='WORKING_ON_CONTENT')
2202+ self.assert_current_processing_row_is_different()
2203+
2204+ self.ui.sd.current_state.set(queues='WORKING_ON_BOTH')
2205+ self.assert_current_processing_row_is_different()
2206+
2207
2208 class MagicicadaUIContentQueueTestCase(_MagicicadaUIQueueTestCase):
2209 """UI test cases for content queue view."""
2210@@ -477,13 +629,14 @@
2211 """Status callback is connected."""
2212 self.assertEqual(self.ui.sd.status_changed_callback,
2213 self.ui.on_status_changed,
2214- 'status_changed callback must be set')
2215+ 'status_changed callback must be set.')
2216
2217 def test_status_label_ellipsizes(self):
2218 """The status label ellipsizes."""
2219 expected = pango.ELLIPSIZE_END
2220 actual = self.ui.status_label.get_ellipsize()
2221- self.assertEqual(expected, actual, 'label ellipsizes is ELLIPSIZE_END.')
2222+ self.assertEqual(expected, actual,
2223+ 'label ellipsizes is ELLIPSIZE_END.')
2224
2225 def test_on_status_changed_updates_status_label(self):
2226 """On status changed, the status label is updated."""
2227@@ -493,7 +646,7 @@
2228
2229 def test_on_status_changed_updates_status_label_even_on_weird_cases(self):
2230 """On status changed, the status label is updated."""
2231- keywords = ('name', 'description', 'queues', 'connection') # need order
2232+ keywords = ('name', 'description', 'queues', 'connection') # order
2233 for attr in keywords:
2234 old_value = self.kwargs[attr]
2235
2236@@ -509,7 +662,7 @@
2237
2238 def test_update_is_correct_for_status_label(self):
2239 """Correctly updates the status label."""
2240- self.ui.sd.current_state._set(**self.kwargs)
2241+ self.ui.sd.current_state.set(**self.kwargs)
2242 self.ui.update()
2243 self.assert_status_label_correct(**self.kwargs)
2244
2245@@ -517,7 +670,7 @@
2246 """On SD stoppped, the UI updates the status label."""
2247 self.patch(self.ui, 'on_status_changed', self.set_called)
2248 self.ui.on_stopped()
2249- self.assertTrue(self._called,
2250+ self.assertTrue(self.called,
2251 'on_status_changed was called on_stopped.')
2252
2253 def test_status_label_default_if_not_started(self):
2254@@ -532,12 +685,12 @@
2255
2256 def test_status_label_is_updated_on_started_and_on_stopped(self):
2257 """Status label is updated on_started."""
2258- self.ui.sd.current_state._set(**self.kwargs)
2259+ self.ui.sd.current_state.set(**self.kwargs)
2260 self.do_start()
2261 self.assert_status_label_correct(**self.kwargs)
2262
2263 self.kwargs['name'] = 'CHANGED'
2264- self.ui.sd.current_state._set(**self.kwargs)
2265+ self.ui.sd.current_state.set(**self.kwargs)
2266
2267 self.ui.on_stop_clicked(self.ui.stop)
2268 self.ui.on_stopped()
2269@@ -551,7 +704,7 @@
2270 """Test that correctly updates the 'indicator'."""
2271 cs = self.ui.sd.current_state
2272 for expected in (True, False):
2273- cs._set(**{indicator: expected})
2274+ cs.set(**{indicator: expected})
2275
2276 self.ui.update()
2277
2278@@ -684,67 +837,64 @@
2279 self.volume_dialog_name = '%s_dialog' % self.name
2280 self.volume_dialog = getattr(self.ui, self.volume_dialog_name)
2281 self.on_volume_clicked = getattr(self.ui, 'on_%s_clicked' % self.name)
2282+ self.volume_close = getattr(self.ui, '%s_close' % self.name)
2283
2284 def build_some_data(self, limit=5):
2285 """Build some data to act as volume."""
2286 kwargs = dict(data_type=self.data_type, limit=limit)
2287- res = super(_MagicicadaUIVolumeTestCase, self).build_some_data(**kwargs)
2288- return res
2289+ r = super(_MagicicadaUIVolumeTestCase, self).build_some_data(**kwargs)
2290+ return r
2291
2292- def assert_volume_availability(self, enabled):
2293+ def assert_widget_availability(self, enabled=True):
2294 """Check volume availability according to 'enabled'."""
2295- self.assertTrue(self.ui.widget_is_visible(self.volume),
2296- '%s should be visible' % self.name)
2297- sensitive = self.volume.is_sensitive()
2298- msg = '%s should %sbe sensitive'
2299- self.assertTrue(sensitive if enabled else not sensitive,
2300- msg % (self.name, '' if enabled else 'not '))
2301+ s = super(_MagicicadaUIVolumeTestCase, self)
2302+ s.assert_widget_availability(self.name, enabled)
2303
2304 @skip_abstract_class
2305 def test_volume_are_disabled_until_started(self):
2306 """Folders and shares are disabled until online."""
2307 # disabled at startup
2308- self.assert_volume_availability(enabled=False)
2309+ self.assert_widget_availability(enabled=False)
2310
2311 # enabled when started
2312 self.do_start()
2313- self.assert_volume_availability(enabled=True)
2314+ self.assert_widget_availability(enabled=True)
2315
2316 @skip_abstract_class
2317 def test_volume_are_enabled_until_stopped(self):
2318 """Folders and shares are enabled until offline."""
2319 self.do_connect()
2320- self.assert_volume_availability(enabled=True)
2321+ self.assert_widget_availability(enabled=True)
2322
2323 self.ui.on_online()
2324- self.assert_volume_availability(enabled=True)
2325+ self.assert_widget_availability(enabled=True)
2326
2327 self.ui.on_offline()
2328- self.assert_volume_availability(enabled=True)
2329+ self.assert_widget_availability(enabled=True)
2330
2331 self.ui.on_disconnect_clicked(self.ui.disconnect)
2332 self.ui.on_disconnected()
2333- self.assert_volume_availability(enabled=True)
2334+ self.assert_widget_availability(enabled=True)
2335
2336 # disabled when stopped
2337 self.ui.on_stop_clicked(self.ui.stop)
2338- self.assert_volume_availability(enabled=False)
2339+ self.assert_widget_availability(enabled=False)
2340 self.ui.on_stopped()
2341- self.assert_volume_availability(enabled=False)
2342+ self.assert_widget_availability(enabled=False)
2343
2344 @skip_abstract_class
2345 def test_volume_close_emits_response_close(self):
2346 """Test volume close button emits RESPONSE_CLOSE when clicked."""
2347- self.response = None
2348+
2349 def record_response(value):
2350 """Record the response received."""
2351 self.response = value
2352+
2353 self.patch(self.volume_dialog, 'response', record_response)
2354
2355- volume_close = '%s_close' % self.name
2356- getattr(self.ui, volume_close).clicked()
2357+ self.volume_close.clicked()
2358 self.assertEqual(gtk.RESPONSE_CLOSE, self.response,
2359- '%s should emit RESPONSE_CLOSE.' % volume_close)
2360+ 'volume close button should emit RESPONSE_CLOSE.')
2361
2362 @skip_abstract_class
2363 def test_on_volume_clicked(self):
2364@@ -799,36 +949,25 @@
2365 self.on_volume_clicked(self.volume)
2366
2367 @skip_abstract_class
2368- def test_volume_dialog_props(self):
2369+ def test_volume_dialog_properties(self):
2370 """The volume dialog has correct properties."""
2371- size = self.volume_dialog.size_request()
2372- self.assertEquals((600, 300), size)
2373-
2374- self.assertTrue(self.volume_dialog.get_modal(),
2375- '%s must be modal.' % self.volume_dialog_name)
2376-
2377- position = self.volume_dialog.get_property('window-position')
2378- self.assertEqual(gtk.WIN_POS_CENTER, position,
2379- '%s must be centered.' % self.volume_dialog_name)
2380-
2381- actual = self.volume_dialog.get_title()
2382- expected = self.name.replace('_', ' ').capitalize()
2383- msg = '%s title must be %s (got %s instead)'
2384- self.assertEqual(expected, actual,
2385- msg % (self.volume_dialog_name, expected, actual))
2386+ title = self.name.replace('_', ' ').capitalize()
2387+ self.assert_dialog_properties(dialog_name=self.volume_dialog_name,
2388+ title=title)
2389+
2390
2391 class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase):
2392 """UI test cases for folders."""
2393
2394 name = 'folders'
2395- data_type = FolderData # node path suggested_path subscribed volume
2396+ data_type = FolderData # node path suggested_path subscribed volume
2397
2398
2399 class _MagicicadaUISharesTestCase(_MagicicadaUIVolumeTestCase):
2400 """UI test cases for shares_to_me."""
2401
2402- data_type = ShareData # accepted access_level free_bytes name node_id
2403- # other_username other_visible_name path volume_id
2404+ data_type = ShareData # accepted access_level free_bytes name node_id
2405+ # other_username other_visible_name path volume_id
2406
2407 @skip_abstract_class
2408 def test_bytes_are_humanized(self):
2409@@ -862,3 +1001,279 @@
2410
2411 name = 'shares_to_others'
2412
2413+
2414+class MagicicadaUIMetadataTestCase(MagicicadaUITestCase):
2415+ """UI test cases for metadata display."""
2416+
2417+ name = 'raw_metadata'
2418+
2419+ def setUp(self):
2420+ """Init."""
2421+ super(MagicicadaUIMetadataTestCase, self).setUp()
2422+ self.path = get_data_file('tests', 'metadata-test.txt')
2423+ self.metadata = dict(bla='ble', foo='bar')
2424+
2425+ def assert_widget_availability(self, enabled=True):
2426+ """Check button availability according to 'enabled'."""
2427+ s = super(MagicicadaUIMetadataTestCase, self)
2428+ s.assert_widget_availability(self.name, enabled)
2429+
2430+ def assert_dialog_visibility(self, dialog, text_view, image):
2431+ """Check the visibility for dialog, text_view and image."""
2432+ msg = '%s visibility should be %s (got %s instead).'
2433+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_dialog)
2434+ self.assertEqual(dialog, visible,
2435+ msg % ('raw_metadata_dialog', dialog, visible))
2436+
2437+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_view)
2438+ self.assertEqual(text_view, visible,
2439+ msg % ('raw_metadata_view', text_view, visible))
2440+
2441+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_image)
2442+ self.assertEqual(image, visible,
2443+ msg % ('raw_metadata_image', image, visible))
2444+
2445+ def test_raw_metadata_are_disabled_until_started(self):
2446+ """Raw metadata button is disabled until online."""
2447+ # disabled at startup
2448+ self.assert_widget_availability(enabled=False)
2449+
2450+ # enabled when started
2451+ self.do_start()
2452+ self.assert_widget_availability(enabled=True)
2453+
2454+ def test_raw_metadata_are_enabled_until_stopped(self):
2455+ """Raw metadata button is enabled until offline."""
2456+ self.do_connect()
2457+ self.assert_widget_availability(enabled=True)
2458+
2459+ self.ui.on_online()
2460+ self.assert_widget_availability(enabled=True)
2461+
2462+ self.ui.on_offline()
2463+ self.assert_widget_availability(enabled=True)
2464+
2465+ self.ui.on_disconnect_clicked(self.ui.disconnect)
2466+ self.ui.on_disconnected()
2467+ self.assert_widget_availability(enabled=True)
2468+
2469+ # disabled when stopped
2470+ self.ui.on_stop_clicked(self.ui.stop)
2471+ self.assert_widget_availability(enabled=False)
2472+ self.ui.on_stopped()
2473+ self.assert_widget_availability(enabled=False)
2474+
2475+ def test_raw_metadata_close_hides_the_dialog(self):
2476+ """Test raw_metadata close button emits RESPONSE_CLOSE when clicked."""
2477+ self.ui.raw_metadata_close.clicked()
2478+ self.assertFalse(self.ui.widget_is_visible(
2479+ self.ui.raw_metadata_dialog),
2480+ 'raw_metadata_dialog should not be visible.')
2481+
2482+ def test_file_chooser_open_emits_response_ok(self):
2483+ """Test volume close button emits RESPONSE_CLOSE when clicked."""
2484+
2485+ def record_response(value):
2486+ """Record the response received."""
2487+ self.response = value
2488+
2489+ self.patch(self.ui.file_chooser, 'response', record_response)
2490+
2491+ self.ui.file_chooser_open.clicked()
2492+ self.assertEqual(gtk.FILE_CHOOSER_ACTION_OPEN, self.response,
2493+ 'open button should emit FILE_CHOOSER_ACTION_OPEN.')
2494+
2495+ def test_on_raw_metadata_clicked(self):
2496+ """Test on_raw_metadata_clicked."""
2497+ self.assertFalse(self.ui.widget_is_visible(
2498+ self.ui.raw_metadata_dialog),
2499+ 'raw_metadata_dialog should not be visible.')
2500+
2501+ self.ui.file_chooser.set_filename(self.path)
2502+
2503+ def test_file_chooser():
2504+ """Auxiliar to assert over the file_chooser."""
2505+ self.assertTrue(self.ui.widget_is_visible(self.ui.file_chooser),
2506+ 'file_chooser must be visible on metadata clicked.')
2507+
2508+ gobject.timeout_add(100, test_and_click,
2509+ (self.ui.file_chooser_open, test_file_chooser))
2510+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
2511+
2512+ self.assertFalse(self.ui.widget_is_visible(self.ui.file_chooser),
2513+ 'file_chooser must be visible after metadata clicked.')
2514+ self.assertEqual(self.path, self.ui.file_chooser.get_filename(),
2515+ 'filename returned by file chooser must be correct.')
2516+
2517+ # raw_metadata_dialog is enabled and shows the loading animation
2518+ self.assert_dialog_visibility(dialog=True, text_view=False, image=True)
2519+ expected = self.ui.raw_metadata_image.get_animation()
2520+ self.assertEqual(self.ui.loading_animation, expected,
2521+ 'raw_metadata_image must have the correct animation.')
2522+
2523+ # Check that the metadata was asked to the SD
2524+ self.assertEqual(self.ui.sd._meta_path, self.path)
2525+ # SD will eventually callback us with the metadata
2526+ self.ui.on_metadata_ready(self.path, self.metadata)
2527+
2528+ # raw_metadata_dialog is enabled and shows the metadata
2529+ self.assert_dialog_visibility(dialog=True, text_view=True, image=False)
2530+
2531+ # user closes the dialog
2532+ self.ui.raw_metadata_close.clicked()
2533+
2534+ # dialog was closed already
2535+ self.assertFalse(self.ui.widget_is_visible(
2536+ self.ui.raw_metadata_dialog),
2537+ 'raw_metadata_dialog should not be visible.')
2538+
2539+ def test_raw_metadata_dialog_properties(self):
2540+ """The raw_metadata dialog has correct properties."""
2541+ title = self.name.replace('_', ' ').capitalize()
2542+ self.assert_dialog_properties(dialog_name='raw_metadata_dialog',
2543+ title=title, modal=False)
2544+
2545+ actual = self.ui.raw_metadata_view.get_wrap_mode()
2546+ msg = 'wrap mode for view must be gtk.WRAP_WORD (got %s instead).'
2547+ self.assertEqual(gtk.WRAP_WORD, actual, msg % actual)
2548+
2549+ def test_callback_is_connected(self):
2550+ """Metadata ready callback is connected."""
2551+ self.assertEqual(self.ui.sd.on_metadata_ready_callback,
2552+ self.ui.on_metadata_ready,
2553+ 'on_metadata_ready_callback callback must be set.')
2554+
2555+ def test_file_chooser_is_hidden_at_startup(self):
2556+ """File chooser exists but is not visible."""
2557+ self.assertFalse(self.ui.widget_is_visible(self.ui.file_chooser),
2558+ 'file_chooser must be hidden by default.')
2559+
2560+ def test_file_chooser_current_folder_is_ubuntu_one_root(self):
2561+ """File chooser default folder is ~/Ubuntu One."""
2562+ process_gtk_pendings() # WOW! Needed to get proper value below
2563+ actual = self.ui.file_chooser.get_current_folder()
2564+ msg = 'file_chooser default folder must be %s (got %s instead).'
2565+ self.assertEqual(actual, UBUNTU_ONE_ROOT,
2566+ msg % (UBUNTU_ONE_ROOT, actual))
2567+
2568+ def test_filename_is_used_only_if_open_clicked(self):
2569+ """Filename is used only if user clicked open."""
2570+ self.patch(self.ui.sd, 'get_metadata', self.set_called)
2571+ gobject.timeout_add(100, test_and_click,
2572+ (self.ui.file_chooser_cancel, NO_OP))
2573+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
2574+
2575+ self.assertFalse(self.called,
2576+ 'get_metadata should not be called if no file chosen.')
2577+
2578+ def test_filename_is_stored_if_open_clicked(self):
2579+ """Filename is stored as 'last_metadata_path' if user clicked open."""
2580+ self.assertTrue(self.ui.last_metadata_path is None,
2581+ 'last_metadata_path must be None.')
2582+ self.ui.file_chooser.set_filename(self.path)
2583+ gobject.timeout_add(100, test_and_click,
2584+ (self.ui.file_chooser_open, NO_OP))
2585+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
2586+
2587+ self.assertEqual(self.path, self.ui.last_metadata_path,
2588+ 'last_metadata_path should be what the user choose.')
2589+
2590+ def test_on_metadata_ready(self):
2591+ """Callback on_metadata_ready updates the raw_metadata_view."""
2592+ path = 'bla'
2593+ self.ui.last_metadata_path = path
2594+ self.ui.on_metadata_ready(path, self.metadata)
2595+
2596+ buff = self.ui.raw_metadata_view.get_buffer()
2597+ self.assertTrue(buff is not None,
2598+ 'buffer for raw_metadata_view must not be None.')
2599+
2600+ expected = '\n'.join('%s: %s' % i for i in self.metadata.iteritems())
2601+ actual = buff.get_text(*buff.get_bounds())
2602+ msg = 'buffer content must be %s (got %s instead).'
2603+ self.assertEqual(actual, expected,
2604+ msg % (expected, actual))
2605+
2606+ def test_on_metadata_ready_doesnt_update_if_last_path_doesnt_match(self):
2607+ """Callback on_metadata_ready updates the raw_metadata_view."""
2608+ self.patch(self.ui.raw_metadata_view.get_buffer(),
2609+ 'set_text', self.set_called)
2610+ path = 'bla'
2611+ self.ui.last_metadata_path = path + path
2612+ self.ui.on_metadata_ready(path, self.metadata)
2613+
2614+ self.assertFalse(self.called,
2615+ 'view should not be updated if key is not last one.')
2616+
2617+
2618+def override_input_output(input_args, output_args):
2619+ """Call 'f' but receive fixed input and return fixed output."""
2620+
2621+ def decorator(f):
2622+ """The decorator per se."""
2623+
2624+ @wraps(f)
2625+ def inner(*args, **kwargs):
2626+ """Feed 'f' with 'input_args' and return 'output_args'."""
2627+ f(input_args)
2628+ return output_args
2629+
2630+ return inner
2631+
2632+ return decorator
2633+
2634+
2635+class MagicicadaLoggingTestCase(MagicicadaUITestCase):
2636+ """UI test cases for logging."""
2637+
2638+ def setUp(self):
2639+ """Init."""
2640+ super(MagicicadaLoggingTestCase, self).setUp()
2641+
2642+ self.memento = MementoHandler()
2643+ self.memento.setLevel(logging.DEBUG)
2644+ logger = logging.getLogger('magicicada.ui')
2645+ logger.addHandler(self.memento)
2646+
2647+ def assert_function_logs(self, func, *args, **kwargs):
2648+ """Check 'funcion' logs its inputs as DEBUG."""
2649+ name = func.__name__
2650+ msg = '%s must be logged as DEBUG'
2651+ try:
2652+ func(*args, **kwargs)
2653+ except Exception: # pylint: disable-msg=E0501, W0703
2654+ exc = sys.exc_info()
2655+ self.assertTrue(self.memento.check_error(name),
2656+ 'function (%s) must be logged as ERROR' % name)
2657+ self.assertTrue(self.memento.check_error(exc),
2658+ 'sys.exc_info (%s) must be logged as ERROR' % exc)
2659+ self.assertTrue(self.memento.check_debug(name), msg % name)
2660+ for arg in args:
2661+ self.assertTrue(self.memento.check_debug(str(arg)), msg % arg)
2662+ for key, val in kwargs.iteritems():
2663+ arg = "'%s': %r" % (key, val)
2664+ self.assertTrue(self.memento.check_debug(arg), msg % arg)
2665+
2666+ def test_on_shares_clicked_logs(self):
2667+ """Check _on_shares_clicked logs properly."""
2668+ args = ([0, object(), 'test', {}], object())
2669+ kwargs = dict(dialog=object())
2670+ self.assert_function_logs(self.ui._on_shares_clicked, *args, **kwargs)
2671+
2672+ def test_on_status_changed_logs(self):
2673+ """Check _on_status_changed logs properly."""
2674+ args = ('test status', 'status description', True, False, True)
2675+ kwargs = dict(queues='bla', connection=None)
2676+ self.assert_function_logs(self.ui.on_status_changed, *args, **kwargs)
2677+
2678+ def test_on_queue_changed_logs(self):
2679+ """Check _on_queue_changed logs properly."""
2680+ args = ('meta',)
2681+ kwargs = dict(items=[0, object(), 'test', {}], must_highlight=True)
2682+ self.assert_function_logs(self.ui._on_queue_changed, *args, **kwargs)
2683+
2684+ def test_on_metadata_ready_logs(self):
2685+ """Check on_metadata_ready logs properly."""
2686+ args = ()
2687+ kwargs = dict(path='test', metadata=True)
2688+ self.assert_function_logs(self.ui.on_metadata_ready, *args, **kwargs)
2689
2690=== modified file 'magicicada/tests/test_syncdaemon.py'
2691--- magicicada/tests/test_syncdaemon.py 2010-06-08 10:53:02 +0000
2692+++ magicicada/tests/test_syncdaemon.py 2010-07-09 18:33:43 +0000
2693@@ -28,6 +28,10 @@
2694 from twisted.internet import defer, reactor
2695
2696
2697+# It's ok to access private data in the test suite
2698+# pylint: disable-msg=W0212
2699+
2700+
2701 class FakeDBusInterface(object):
2702 """Fake DBus Interface, for SD to not use dbus at all during tests."""
2703
2704@@ -37,15 +41,18 @@
2705 pass
2706
2707 def shutdown(self):
2708+ """Fake shutdown."""
2709 pass
2710
2711 def get_status(self):
2712 """Fake status."""
2713 return defer.succeed(('fakename', 'fakedescrip', False, True,
2714 False, 'fakequeues', 'fakeconnection'))
2715+
2716 def get_folders(self):
2717 """Fake folders."""
2718 return defer.succeed('fakedata')
2719+
2720 get_content_queue = get_meta_queue = get_folders
2721 start = quit = connect = disconnect = get_folders
2722 get_shares_to_me = get_shares_to_others = get_folders
2723@@ -133,8 +140,9 @@
2724
2725 @defer.inlineCallbacks
2726 def test_initial_value(self):
2727- """Fills the status info initially."""
2728+ """Fill the status info initially."""
2729 called = []
2730+
2731 def fake():
2732 """Fake method."""
2733 called.append(True)
2734@@ -167,7 +175,7 @@
2735 return deferred
2736
2737 def test_status_changed_affects_cuurent_status(self):
2738- """Makes changes to see how status are reflected."""
2739+ """Make changes to see how status are reflected."""
2740 # one set of values
2741 self.sd.on_sd_status_changed('name1', 'description1', False, True,
2742 False, 'queues1', 'connection1')
2743@@ -229,13 +237,42 @@
2744 self.assertEqual(self.sd.current_state.queues, '')
2745 self.assertEqual(self.sd.current_state.connection, '')
2746
2747+ def test_processing_meta_if_working_on_meta_or_both(self):
2748+ """Status.processing_meta is True when WORKING_ON_{METADATA,BOTH}."""
2749+
2750+ msg = 'processing_meta must be False when %s.'
2751+ for state in ('WORKING_ON_CONTENT', ''):
2752+ self.sd.current_state.set(queues=state)
2753+ self.assertFalse(self.sd.current_state.processing_meta,
2754+ msg % state)
2755+
2756+ msg = 'processing_meta must be True when %s.'
2757+ for state in ('WORKING_ON_METADATA', 'WORKING_ON_BOTH'):
2758+ self.sd.current_state.set(queues=state)
2759+ self.assertTrue(self.sd.current_state.processing_meta, msg % state)
2760+
2761+ def test_processing_content_if_working_on_content_or_both(self):
2762+ """Status.processing_content is True when WORKING_ON_{CONTENT,BOTH}."""
2763+
2764+ msg = 'processing_content must be False when %s.'
2765+ for state in ('WORKING_ON_METADATA', ''):
2766+ self.sd.current_state.set(queues=state)
2767+ self.assertFalse(self.sd.current_state.processing_content,
2768+ msg % state)
2769+
2770+ msg = 'processing_content must be True when %s.'
2771+ for state in ('WORKING_ON_CONTENT', 'WORKING_ON_BOTH'):
2772+ self.sd.current_state.set(queues=state)
2773+ self.assertTrue(self.sd.current_state.processing_content,
2774+ msg % state)
2775+
2776
2777 class ContentQueueChangedTests(BaseTest):
2778 """Check the ContenQueueChanged handling."""
2779
2780 @defer.inlineCallbacks
2781 def test_initial_value(self):
2782- """Fills the content queue info initially."""
2783+ """Fill the content queue info initially."""
2784 called = []
2785 self.sd.dbus.get_content_queue = lambda: called.append(True)
2786 yield self.sd._get_initial_data()
2787@@ -311,13 +348,19 @@
2788 def setUp(self):
2789 """Set up."""
2790 BaseTest.setUp(self)
2791- self.sd.current_state._set(queues='WORKING_ON_METADATA')
2792+ self.sd.current_state.set(queues='WORKING_ON_METADATA')
2793
2794 @defer.inlineCallbacks
2795 def test_initial_value(self):
2796- """Fills the meta queue info initially."""
2797+ """Fill the meta queue info initially."""
2798 called = []
2799- self.sd.dbus.get_meta_queue = lambda: called.append(True)
2800+
2801+ def f():
2802+ """Helper function."""
2803+ called.append(True)
2804+ return []
2805+
2806+ self.sd.dbus.get_meta_queue = f
2807 yield self.sd._get_initial_data()
2808 self.assertTrue(called)
2809
2810@@ -373,10 +416,12 @@
2811 """Check that it polls mq while working in metadata not being in QM."""
2812 # set the callback
2813 deferred = defer.Deferred()
2814+
2815 def fake():
2816 """Fake."""
2817 deferred.callback(True)
2818 return defer.succeed("foo")
2819+
2820 self.sd.dbus.get_meta_queue = fake
2821
2822 # send status changed to working in metadata
2823@@ -389,10 +434,12 @@
2824 """Check that it polls mq while working in metadata being in QM."""
2825 # set the callback
2826 deferred = defer.Deferred()
2827+
2828 def fake():
2829 """Fake."""
2830 deferred.callback(True)
2831 return defer.succeed("foo")
2832+
2833 self.sd.dbus.get_meta_queue = fake
2834
2835 # send status changed to working in metadata
2836@@ -405,10 +452,12 @@
2837 """Check that it polls mq while working in both."""
2838 # set the callback
2839 deferred = defer.Deferred()
2840+
2841 def fake():
2842 """Fake."""
2843 deferred.callback(True)
2844 return defer.succeed("foo")
2845+
2846 self.sd.dbus.get_meta_queue = fake
2847
2848 # send status changed to working in metadata
2849@@ -417,14 +466,47 @@
2850 'connection')
2851 return deferred
2852
2853+ def test_mq_polls_last_time(self):
2854+ """Was polling, state changed, it needs to poll a last time."""
2855+ # set the callback
2856+ deferred = defer.Deferred()
2857+ changed = self.sd.on_sd_status_changed
2858+ called = []
2859+
2860+ def fake():
2861+ """Fake."""
2862+ called.append(None)
2863+ if len(called) == 1:
2864+ changed('QUEUE_MANAGER', 'description', False, True, False,
2865+ 'WORKING_ON_CONTENT', 'connection')
2866+ elif len(called) == 2:
2867+ # check that the caller is set back to None
2868+ self.assertTrue(self.sd._mqcaller is None)
2869+ deferred.callback(True)
2870+ return defer.succeed("foo")
2871+
2872+ self.sd.dbus.get_meta_queue = fake
2873+
2874+ # send status changed to working in metadata
2875+ changed('QUEUE_MANAGER', 'description', False, True, False,
2876+ 'WORKING_ON_BOTH', 'connection')
2877+ return deferred
2878+
2879+ def test_mq_caller_is_reset_last_time(self):
2880+ """When MQ is polled last time, the caller should be back to None."""
2881+ self.sd._mqcaller = reactor.callLater(100, lambda: None)
2882+ self.sd.current_state.set(name='QUEUE_MANAGER',
2883+ queues='WORKING_ON_CONTENT')
2884+
2885+ # call the method and check
2886+ self.sd._check_mq()
2887+ self.assertTrue(self.sd._mqcaller is None)
2888+
2889 def test_mq_polling_untilfinish(self):
2890 """Check that it polls mq until no more is needed."""
2891- d2 = dict(name='QUEUE_MANAGER', queues='WORKING_ON_CONTENT',
2892- description='description', is_error='', is_connected='True',
2893- is_online='', connection='conn')
2894-
2895 # set the callback, and adjust the polling time to faster
2896 calls = []
2897+
2898 def fake_get(*a):
2899 """Fake get."""
2900 calls.append(None)
2901@@ -437,6 +519,8 @@
2902
2903 # allow time to see if a mistaken call happens
2904 reactor.callLater(.5, deferred.callback, True)
2905+ elif len(calls) == 4:
2906+ pass # last call after state changed
2907 else:
2908 deferred.errback(ValueError("Too many calls"))
2909 return defer.succeed("foo")
2910@@ -480,7 +564,7 @@
2911 def test_set_one_value(self):
2912 """Set one value."""
2913 st = State()
2914- st._set(name=55)
2915+ st.set(name=55)
2916
2917 # check the one is set, the rest not
2918 self.assertEqual(st.name, 55)
2919@@ -489,7 +573,7 @@
2920 def test_set_two_values(self):
2921 """Set two values."""
2922 st = State()
2923- st._set(name=55, description=77)
2924+ st.set(name=55, description=77)
2925
2926 # check those two are set, the rest not
2927 self.assertEqual(st.name, 55)
2928@@ -499,7 +583,7 @@
2929 def test_bad_value(self):
2930 """Set a value that should not."""
2931 st = State()
2932- self.assertRaises(AttributeError, st._set, not_really_allowed=44)
2933+ self.assertRaises(AttributeError, st.set, not_really_allowed=44)
2934
2935
2936 class APITests(unittest.TestCase):
2937@@ -617,7 +701,6 @@
2938 self.assertTrue(self.called)
2939
2940
2941-
2942 class TestLogs(unittest.TestCase):
2943 """Test logging."""
2944
2945@@ -634,100 +717,128 @@
2946
2947 def test_instancing(self):
2948 """Just logged SD instancing."""
2949- self.assertTrue(self.hdlr.check_inf("SyncDaemon interface started!"))
2950+ self.assertTrue(self.hdlr.check_info("SyncDaemon interface started!"))
2951
2952 def test_shutdown(self):
2953 """Log when SD shutdowns."""
2954 self.sd.shutdown()
2955- self.assertTrue(self.hdlr.check_inf("SyncDaemon interface going down"))
2956+ msg = "SyncDaemon interface going down"
2957+ self.assertTrue(self.hdlr.check_info(msg))
2958
2959 @defer.inlineCallbacks
2960 def test_initial_value(self):
2961 """Log the initial filling."""
2962 yield self.sd._get_initial_data()
2963- self.assertTrue(self.hdlr.check_inf("Getting initial data"))
2964+ self.assertTrue(self.hdlr.check_info("Getting initial data"))
2965
2966 def test_start(self):
2967 """Log the call to start."""
2968 self.sd.start()
2969- self.assertTrue(self.hdlr.check_inf("Starting u1.SD"))
2970+ self.assertTrue(self.hdlr.check_info("Starting u1.SD"))
2971
2972 def test_quit(self):
2973 """Log the call to quit."""
2974 self.sd.quit()
2975- self.assertTrue(self.hdlr.check_inf("Stopping u1.SD"))
2976+ self.assertTrue(self.hdlr.check_info("Stopping u1.SD"))
2977
2978 def test_connect(self):
2979 """Log the call to connect."""
2980 self.sd.connect()
2981- self.assertTrue(self.hdlr.check_inf("Telling u1.SD to connect"))
2982+ self.assertTrue(self.hdlr.check_info("Telling u1.SD to connect"))
2983
2984 def test_disconnect(self):
2985 """Log the call to disconnect."""
2986 self.sd.disconnect()
2987- self.assertTrue(self.hdlr.check_inf("Telling u1.SD to disconnect"))
2988+ self.assertTrue(self.hdlr.check_info("Telling u1.SD to disconnect"))
2989
2990 def test_check_mq_true(self):
2991 """Log the MQ check when it asks for info."""
2992- self.sd.current_state._set(name='QUEUE_MANAGER',
2993+ self.sd.current_state.set(name='QUEUE_MANAGER',
2994 queues='WORKING_ON_METADATA')
2995 self.sd._check_mq()
2996- self.assertTrue(self.hdlr.check_inf("Asking for MQ information"))
2997+ self.assertTrue(self.hdlr.check_info("Asking for MQ information"))
2998
2999 def test_check_mq_noreally(self):
3000 """Log the MQ check when it should not work."""
3001- self.sd.current_state._set(name='QUEUE_MANAGER',
3002+ self.sd.current_state.set(name='QUEUE_MANAGER',
3003 queues='WORKING_ON_CONTENT')
3004 self.sd._check_mq()
3005- self.assertTrue(self.hdlr.check_inf(
3006- "Check MQ called but States not in MQ"))
3007+ self.assertTrue(self.hdlr.check_info(
3008+ "Check MQ called, States not in MQ, call a last time"))
3009
3010 def test_meta_queue_changed(self):
3011 """Log that MQ has new data."""
3012 self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo'])
3013- self.sd.current_state._set(name='QUEUE_MANAGER',
3014+ self.sd.current_state.set(name='QUEUE_MANAGER',
3015 queues='WORKING_ON_METADATA')
3016 self.sd._check_mq()
3017- self.assertTrue(self.hdlr.check_inf("SD Meta Queue changed: 1 items"))
3018+ self.assertTrue(self.hdlr.check_info("SD Meta Queue changed: 1 items"))
3019
3020 def test_content_queue_changed(self):
3021 """Log that process_cq has new data."""
3022 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo'])
3023 self.sd.on_sd_content_queue_changed()
3024- self.assertTrue(self.hdlr.check_inf("SD Content Queue changed"))
3025- self.assertTrue(self.hdlr.check_inf(
3026+ self.assertTrue(self.hdlr.check_info("SD Content Queue changed"))
3027+ self.assertTrue(self.hdlr.check_info(
3028 "Content Queue info is new! 1 items"))
3029
3030 def test_on_status_changed(self):
3031 """Log status changed."""
3032 self.sd.on_sd_status_changed('name', 'description', False, True,
3033 False, 'queues', 'connection')
3034- self.assertTrue(self.hdlr.check_inf("SD Status changed"))
3035- self.assertTrue(self.hdlr.check_dbg(" new status: name=u'name', "
3036+ self.assertTrue(self.hdlr.check_info("SD Status changed"))
3037+ self.assertTrue(self.hdlr.check_debug(" new status: name=u'name', "
3038 "description=u'description', is_error=False, is_connected=True, "
3039 "is_online=False, queues=u'queues', connection=u'connection'"))
3040
3041 def test_folders_changed(self):
3042 """Log when folders changed."""
3043 self.sd.on_sd_folders_changed()
3044- self.assertTrue(self.hdlr.check_inf("SD Folders changed"))
3045+ self.assertTrue(self.hdlr.check_info("SD Folders changed"))
3046
3047 def test_shares_changed(self):
3048 """Log when shares changed."""
3049 self.sd.on_sd_shares_changed()
3050- self.assertTrue(self.hdlr.check_inf("SD Shares changed"))
3051+ self.assertTrue(self.hdlr.check_info("SD Shares changed"))
3052
3053 def test_on_name_owner_changed(self):
3054 """Log name owner changed."""
3055 self.sd.on_sd_name_owner_changed(True)
3056- self.assertTrue(self.hdlr.check_inf("SD Name Owner changed: True"))
3057+ self.assertTrue(self.hdlr.check_info("SD Name Owner changed: True"))
3058+
3059+
3060+class MetadataTests(BaseTest):
3061+ """Get Metadata info."""
3062+
3063+ def test_get_metadata_no_callback_set(self):
3064+ """It's mandatory to set the callback for this response."""
3065+ self.assertRaises(ValueError, self.sd.get_metadata, 'path')
3066+
3067+ def test_get_metadata_ok(self):
3068+ """Get the metadata for given path."""
3069+ called = []
3070+ self.sd.dbus.get_metadata = lambda p: defer.succeed('foo')
3071+ self.sd.on_metadata_ready_callback = lambda *a: called.extend(a)
3072+ self.sd.get_metadata('path')
3073+ self.assertEqual(called, ['path', 'foo'])
3074+
3075+ def test_get_metadata_double(self):
3076+ """Get the metadata twice."""
3077+ called = []
3078+ fake_md = {'path1': 'foo', 'path2': 'bar'}
3079+ self.sd.dbus.get_metadata = lambda p: defer.succeed(fake_md[p])
3080+ self.sd.on_metadata_ready_callback = lambda *a: called.append(a)
3081+ self.sd.get_metadata('path1')
3082+ self.sd.get_metadata('path2')
3083+ self.assertEqual(called[0], ('path1', 'foo'))
3084+ self.assertEqual(called[1], ('path2', 'bar'))
3085
3086
3087 class FoldersTests(BaseTest):
3088 """Folders checking."""
3089
3090 def test_foldercreated_callback(self):
3091- """Gets the new data after the folders changed."""
3092+ """Get the new data after the folders changed."""
3093 # set the callback
3094 called = []
3095 self.sd.dbus.get_folders = lambda: called.append(True)
3096@@ -740,7 +851,7 @@
3097
3098 @defer.inlineCallbacks
3099 def test_initial_value(self):
3100- """Fills the folder info initially."""
3101+ """Fill the folder info initially."""
3102 called = []
3103 self.sd.dbus.get_folders = lambda: called.append(True)
3104 yield self.sd._get_initial_data()
3105@@ -763,7 +874,7 @@
3106 """Shares checking."""
3107
3108 def test_shares_changed_callback(self):
3109- """Gets the new data after the shares changed."""
3110+ """Get the new data after the shares changed."""
3111 # set the callback
3112 called = []
3113 self.sd.dbus.get_shares_to_me = lambda: called.append(True)
3114@@ -777,7 +888,7 @@
3115
3116 @defer.inlineCallbacks
3117 def test_initial_value(self):
3118- """Fills the folder info initially."""
3119+ """Fill the folder info initially."""
3120 called = []
3121 self.sd.dbus.get_shares_to_me = lambda: called.append(True)
3122 self.sd.dbus.get_shares_to_others = lambda: called.append(True)
3123@@ -793,7 +904,7 @@
3124
3125 # they changed!
3126 self.sd.shares_to_me = 'foo'
3127- self.sd.shares_to_others = 'fakedata' # what fake dbus will return
3128+ self.sd.shares_to_others = 'fakedata' # what fake dbus will return
3129 self.sd.on_sd_shares_changed()
3130
3131 # test
3132@@ -808,7 +919,7 @@
3133
3134 # they changed!
3135 self.sd.shares_to_others = 'foo'
3136- self.sd.shares_to_me = 'fakedata' # what fake dbus will return
3137+ self.sd.shares_to_me = 'fakedata' # what fake dbus will return
3138 self.sd.on_sd_shares_changed()
3139
3140 # test
3141
3142=== modified file 'setup.py'
3143--- setup.py 2010-06-08 10:53:02 +0000
3144+++ setup.py 2010-07-09 18:33:43 +0000
3145@@ -4,7 +4,9 @@
3146 # This file is in the public domain
3147 ### END LICENSE
3148
3149-###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ######################
3150+"""Build tar.gz and related for magicicada."""
3151+
3152+################# DO NOT TOUCH THIS (HEAD TO THE SECOND PART) #################
3153
3154 import os
3155 import sys
3156@@ -12,24 +14,28 @@
3157 try:
3158 import DistUtilsExtra.auto
3159 except ImportError:
3160- print >> sys.stderr, 'To build magicicada you need https://launchpad.net/python-distutils-extra'
3161+ url = 'https://launchpad.net/python-distutils-extra'
3162+ print >> sys.stderr, 'To build magicicada you need', url
3163 sys.exit(1)
3164-assert DistUtilsExtra.auto.__version__ >= '2.18', 'needs DistUtilsExtra.auto >= 2.18'
3165+assert DistUtilsExtra.auto.__version__ >= '2.18', \
3166+ 'needs DistUtilsExtra.auto >= 2.18'
3167+
3168
3169 def update_data_path(prefix, oldvalue=None):
3170+ """Update data path."""
3171
3172 try:
3173 fin = file('magicicada/magicicadaconfig.py', 'r')
3174 fout = file(fin.name + '.new', 'w')
3175
3176- for line in fin:
3177- fields = line.split(' = ') # Separate variable from value
3178+ for line in fin:
3179+ fields = line.split(' = ') # Separate variable from value
3180 if fields[0] == '__magicicada_data_directory__':
3181 # update to prefix, store oldvalue
3182 if not oldvalue:
3183 oldvalue = fields[1]
3184 line = "%s = '%s'\n" % (fields[0], prefix)
3185- else: # restore oldvalue
3186+ else: # restore oldvalue
3187 line = "%s = %s" % (fields[0], oldvalue)
3188 fout.write(line)
3189
3190@@ -37,19 +43,20 @@
3191 fout.close()
3192 fin.close()
3193 os.rename(fout.name, fin.name)
3194- except (OSError, IOError), e:
3195+ except (OSError, IOError):
3196 print ("ERROR: Can't find magicicada/magicicadaconfig.py")
3197 sys.exit(1)
3198 return oldvalue
3199
3200
3201 def update_desktop_file(datadir):
3202+ """Update desktop file."""
3203
3204 try:
3205 fin = file('magicicada.desktop.in', 'r')
3206 fout = file(fin.name + '.new', 'w')
3207
3208- for line in fin:
3209+ for line in fin:
3210 if 'Icon=' in line:
3211 line = "Icon=%s\n" % (datadir + 'media/icon.png')
3212 fout.write(line)
3213@@ -57,33 +64,33 @@
3214 fout.close()
3215 fin.close()
3216 os.rename(fout.name, fin.name)
3217- except (OSError, IOError), e:
3218+ except (OSError, IOError):
3219 print ("ERROR: Can't find magicicada.desktop.in")
3220 sys.exit(1)
3221
3222
3223 class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto):
3224+ """Install and update data dir."""
3225+
3226 def run(self):
3227+ """Run."""
3228 previous_value = update_data_path(self.prefix + '/share/magicicada/')
3229 update_desktop_file(self.prefix + '/share/magicicada/')
3230 DistUtilsExtra.auto.install_auto.run(self)
3231 update_data_path(self.prefix, previous_value)
3232
3233
3234-
3235-##################################################################################
3236-###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ######################
3237-##################################################################################
3238+##############################################################################
3239+#################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ####################
3240+##############################################################################
3241
3242 DistUtilsExtra.auto.setup(
3243 name='magicicada',
3244- version='0.1.1',
3245+ version='0.1.2',
3246 license='GPL-3',
3247 author='Natalia Bidart',
3248 author_email='natalia.bidart@ubuntu.com',
3249- description='A GTK+ frontend for the "Chicharra" part of Ubuntu One client.',
3250+ description='A GTK+ frontend for the "Chicharra" part of Ubuntu One.',
3251 #long_description='Here a longer description',
3252 url='https://launchpad.net/magicicada',
3253- cmdclass={'install': InstallAndUpdateDataDirectory}
3254- )
3255-
3256+ cmdclass={'install': InstallAndUpdateDataDirectory})

Subscribers

People subscribed via source and target branches

to all changes: