Merge lp:~statik/ubuntu/maverick/magicicada/zero-one-two into lp:ubuntu/maverick/magicicada
- Maverick (10.10)
- zero-one-two
- Merge into maverick
Proposed by
Elliot Murphy
on 2010-07-09
| 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 |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Ubuntu branches | 2010-07-09 | Pending | |
|
Review via email:
|
|||
Commit Message
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}) |

