Merge lp:~nataliabidart/ubuntu/natty/magicicada/magicicada-0.3.0 into lp:ubuntu/natty/magicicada

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 6
Proposed branch: lp:~nataliabidart/ubuntu/natty/magicicada/magicicada-0.3.0
Merge into: lp:ubuntu/natty/magicicada
Diff against target: 3391 lines (+1725/-336)
18 files modified
PKG-INFO (+4/-12)
data/ui/gui.glade (+124/-6)
debian/changelog (+42/-0)
debian/control (+13/-13)
debian/copyright (+14/-6)
debian/pycompat (+0/-1)
debian/rules (+2/-4)
magicicada/__init__.py (+58/-29)
magicicada/dbusiface.py (+95/-3)
magicicada/helpers.py (+29/-17)
magicicada/logger.py (+33/-9)
magicicada/syncdaemon.py (+112/-17)
magicicada/tests/helpers.py (+13/-4)
magicicada/tests/test_dbusiface.py (+302/-35)
magicicada/tests/test_logger.py (+35/-1)
magicicada/tests/test_magicicada.py (+331/-144)
magicicada/tests/test_syncdaemon.py (+499/-21)
setup.py (+19/-14)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/magicicada/magicicada-0.3.0
Reviewer Review Type Date Requested Status
Stefano Rivera Approve
Ubuntu Sponsors Pending
Review via email: mp+45528@code.launchpad.net

Description of the change

  * New upstream release:

    [ Natalia B. Bidart <email address hidden> ]
      * Added Public Files listing to the UI (LP: #568197).
      * The icon list is now set at creation time. This guarantees that proper
      icon sizes will be used (LP: #669947).
      * Default logging level is now INFO.
      * Volumes and metadata buttons are enabled when initial_data_ready
      callback is fired (LP: #612194).

    [ Facundo Batista <email address hidden> ]
      * Backend code to accept a share.
      * Support the new "CQ changed" signal, with no data in it.
      * Log all the errors that happen inside a deferred.
      * Splitted "on initial data callback" in both online and offline ones.
      * Handle Public Files info.
      * Support GetDelta in the MQ (LP: #665680).
      * Leave the "don't ask while asking" machinery ok if error while asking.
      * Support an error when calling the MD (LP: #665674).
      * Don't ask a lot of times for updates to Syncdaemon on changes bursts
      (LP: #587020) (LP: #643195).

  * debian/control
    - updated depedency list

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

Hi looks ok, but a couple of small things pile up:
* changelog is a bit weirdly formatted (text indented to the same level as the bullets.
* You can use this syntax: (LP: #587020, #643195)
* I'd prefer something a little more concrete than "updated dependency list".
  It looks like: sorted depends, dropped depends-indep, depend on python-ubuntu-one-client (>= foo), depend on python-xdg
* No mention of the standards version update (these are traditionally changelogged as "Bumped Standards-Version to X.Y.Z (no changes needed)"
* No mention of the maintainer change in the changelog. Should the maintainer not be Ubuntu Developers <email address hidden>?
* No mention of the description change.
* Copyright:
  - I assume the copyright years need a bump.
  - DEP5 has evolved quite a bit, at some point this file should be updated to match the current standards.
  - The short GPL licence grant should be included before the "full text" notice.
* At some point, you should consider migrating to dh_python2, AFAIK doko is quite keen for Ubuntu-specific packages to migrate during natty.

review: Needs Fixing
9. By Natalia Bidart

Depending on freshly released ubuntuone-client 1.5.2.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> Hi looks ok, but a couple of small things pile up:
> * changelog is a bit weirdly formatted (text indented to the same level as the
> bullets.

> * You can use this syntax: (LP: #587020, #643195)

> * I'd prefer something a little more concrete than "updated dependency list".
> It looks like: sorted depends, dropped depends-indep, depend on python-
> ubuntu-one-client (>= foo), depend on python-xdg

Fixed as per suggestion.

> * No mention of the standards version update (these are traditionally
> changelogged as "Bumped Standards-Version to X.Y.Z (no changes needed)"

Added (I bumped the version sin I got a lintian warning).

> * No mention of the maintainer change in the changelog. Should the maintainer
> not be Ubuntu Developers <email address hidden>?

I'm not sure, Elliot used to do the package maintenance but now is just Facundo Batista and me. I thought we had to put the actual maintainers on that field.
Can you please confirm?

> * No mention of the description change.

Added.

> * Copyright:
> - I assume the copyright years need a bump.

Added.

> - DEP5 has evolved quite a bit, at some point this file should be updated to
> match the current standards.

Sorry for my ignorance, what is DEP5? And how should it be updated?

> - The short GPL licence grant should be included before the "full text"
> notice.

I'm a bit lost, in which file this needs to be changed?

> * At some point, you should consider migrating to dh_python2, AFAIK doko is
> quite keen for Ubuntu-specific packages to migrate during natty.

Hum, my ignorance strikes again. I'm not using explicitly dh_python to build the package. Could you please be more specific where dh_python2 should be used?

Thanks a lot for the feedback!

10. By Natalia Bidart

* debian/control
  - Sorted Depends, dropped depends-indep
  - Added version depend restriction for python-ubuntuone-client (>= 1.5.2)
  - Added missing depend: python-xdg
  - Bumped Standards-Version to 3.9.1 (no changes needed)
  - Changed the description to match the new description in the project
  setup.py file

* debian/copyright
  - Added year 2011 to copyright statement
  - Fixed email ocurrences of nataliabidart to consistently be nataliabidart
  at gmail dot com

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Stefano, I forgot to as about:

"changelog is a bit weirdly formatted (text indented to the same level as the bullets"

what would be a better formatting?

Revision history for this message
Stefano Rivera (stefanor) wrote :

Thanks, that was quick.

> I thought we had to put the actual maintainers on that field.
> Can you please confirm?

I'm not sure on Maintainer policy either, best I can tell is that Ubuntu tends to use Ubuntu Developers for ubuntu-only packages. The reason for this is that Ubuntu packages don't have strong maintainers, but are maintained by all Ubuntu developers. However, there is no policy I know of stipulating this, so I'm happy with it as is.

> what is DEP5? And how should it be updated?

DEP5 is the machine-readable changelog format. http://dep.debian.net/deps/dep5/
A few of the fields have changed name, and there should only be one (multi-line) copyright item.

The licence block for GPL-3 should probably be:
License: GPL-3
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, version 3 of the License.
 .
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 General Public License for more details.
 .
 On Debian systems, the complete text of the GNU General Public License
 version 3 can be found in the /usr/share/common-licenses/GPL-3 file.

> Could you please be more specific where dh_python2 should be used?

dh_python2 is a replacement for python-support and python-central. Instead of creating per-python-version symlink farms at install time, they are shipped in the package, and only byte-compilation is done at install time. Unfortunately there is little documentation on it that I can point you to (yet).

CDBS only gained support in 0.4.90 (which isn't in Ubuntu yet), so the best way to use it is probably to switch to debhelper.

Here's a trivial port, which is untested, but looks ok: http://paste.ubuntu.com/551612/

> what would be a better formatting?
Wrapping like this

* this is a multilne
  bulleted point.

Oh, and lintian says "Splitted" isn't an english word. It's right about that :)

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> > I thought we had to put the actual maintainers on that field.
> > Can you please confirm?
>
> I'm not sure on Maintainer policy either, best I can tell is that Ubuntu tends
> to use Ubuntu Developers for ubuntu-only packages. The reason for this is that
> Ubuntu packages don't have strong maintainers, but are maintained by all
> Ubuntu developers. However, there is no policy I know of stipulating this, so
> I'm happy with it as is.

Good, I'll leave it as is since I plan to maintain magicicada in the long term.

> > what is DEP5? And how should it be updated?
>
> DEP5 is the machine-readable changelog format.
> http://dep.debian.net/deps/dep5/
> A few of the fields have changed name, and there should only be one (multi-
> line) copyright item.

Thanks for the pointer, all fixed.

> > Could you please be more specific where dh_python2 should be used?
>
> dh_python2 is a replacement for python-support and python-central. Instead of
> creating per-python-version symlink farms at install time, they are shipped in
> the package, and only byte-compilation is done at install time. Unfortunately
> there is little documentation on it that I can point you to (yet).
>
> CDBS only gained support in 0.4.90 (which isn't in Ubuntu yet), so the best
> way to use it is probably to switch to debhelper.
>
> Here's a trivial port, which is untested, but looks ok:
> http://paste.ubuntu.com/551612/

I've applied this patch and build and installed the packages, seems to work just fine. Thanks!

> > what would be a better formatting?
> Wrapping like this
>
> * this is a multilne
> bulleted point.

Fixed!

> Oh, and lintian says "Splitted" isn't an english word. It's right about that
> :)

Also fixed :-)

Thanks again!

11. By Natalia Bidart

* debian/copyright
    - Fixed formatting to be DEP5 compatible

12. By Natalia Bidart

* Switched to debhelper, thanks Stefano Rivera for the patch.

* debian/control
    - fixed bullet indent formatting
    - fixed typo (s/splitted/split/)

Revision history for this message
Stefano Rivera (stefanor) wrote :

OK, copyright still isn't quite current DEP5, I'll tweak it on upload.

I'll also document the transition to dh_python2 in the changelog, it's worthy of a mention.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'PKG-INFO'
--- PKG-INFO 2010-09-02 19:26:08 +0000
+++ PKG-INFO 2011-01-07 23:06:32 +0000
@@ -1,19 +1,11 @@
1Metadata-Version: 1.11Metadata-Version: 1.1
2Name: magicicada2Name: magicicada
3Version: 0.23Version: 0.3.0
4Summary: A GTK+ frontend for the "Chicharra" part of Ubuntu One.4Summary: A GTK+ frontend for Ubuntu One.
5Home-page: https://launchpad.net/magicicada5Home-page: https://launchpad.net/magicicada
6Author: Natalia Bidart6Author: Natalia Bidart
7Author-email: natalia.bidart@ubuntu.com7Author-email: nataliabidart@gmail.com
8License: GPL-38License: GPL-3
9Description: UNKNOWN9Description: This application provides a GTK frontend to manage the file synchronisation service of Ubuntu One.
10Platform: UNKNOWN10Platform: UNKNOWN
11Requires: dbus
12Requires: gobject
13Requires: gtk
14Requires: pango
15Requires: twisted.internet
16Requires: twisted.trial.unittest
17Requires: ubuntuone.syncdaemon.tools
18Requires: xdg.BaseDirectory
19Provides: magicicada11Provides: magicicada
2012
=== added file 'data/media/logo-048.png'
21Binary files data/media/logo-048.png 1970-01-01 00:00:00 +0000 and data/media/logo-048.png 2011-01-07 23:06:32 +0000 differ13Binary files data/media/logo-048.png 1970-01-01 00:00:00 +0000 and data/media/logo-048.png 2011-01-07 23:06:32 +0000 differ
=== modified file 'data/ui/gui.glade'
--- data/ui/gui.glade 2010-07-24 18:46:42 +0000
+++ data/ui/gui.glade 2011-01-07 23:06:32 +0000
@@ -1,4 +1,4 @@
1<?xml version="1.0"?>1<?xml version="1.0" encoding="UTF-8"?>
2<interface>2<interface>
3 <requires lib="gtk+" version="2.16"/>3 <requires lib="gtk+" version="2.16"/>
4 <!-- interface-naming-policy project-wide -->4 <!-- interface-naming-policy project-wide -->
@@ -84,6 +84,18 @@
84 <column type="gchararray"/>84 <column type="gchararray"/>
85 </columns>85 </columns>
86 </object>86 </object>
87 <object class="GtkListStore" id="public_files_store">
88 <columns>
89 <!-- column-name path -->
90 <column type="gchararray"/>
91 <!-- column-name public_url -->
92 <column type="gchararray"/>
93 <!-- column-name volume -->
94 <column type="gchararray"/>
95 <!-- column-name node -->
96 <column type="gchararray"/>
97 </columns>
98 </object>
87 <object class="GtkWindow" id="main_window">99 <object class="GtkWindow" id="main_window">
88 <property name="width_request">800</property>100 <property name="width_request">800</property>
89 <property name="height_request">600</property>101 <property name="height_request">600</property>
@@ -149,10 +161,12 @@
149 <object class="GtkToolbar" id="toolbar">161 <object class="GtkToolbar" id="toolbar">
150 <property name="visible">True</property>162 <property name="visible">True</property>
151 <property name="toolbar_style">both</property>163 <property name="toolbar_style">both</property>
164 <property name="icon_size">1</property>
152 <child>165 <child>
153 <object class="GtkToolButton" id="start">166 <object class="GtkToolButton" id="start">
154 <property name="width_request">50</property>167 <property name="width_request">50</property>
155 <property name="visible">True</property>168 <property name="visible">True</property>
169 <property name="is_important">True</property>
156 <property name="label" translatable="yes">Start</property>170 <property name="label" translatable="yes">Start</property>
157 <property name="use_underline">True</property>171 <property name="use_underline">True</property>
158 <property name="stock_id">gtk-apply</property>172 <property name="stock_id">gtk-apply</property>
@@ -166,6 +180,7 @@
166 <child>180 <child>
167 <object class="GtkToolButton" id="stop">181 <object class="GtkToolButton" id="stop">
168 <property name="width_request">50</property>182 <property name="width_request">50</property>
183 <property name="is_important">True</property>
169 <property name="label" translatable="yes">Stop</property>184 <property name="label" translatable="yes">Stop</property>
170 <property name="use_underline">True</property>185 <property name="use_underline">True</property>
171 <property name="stock_id">gtk-stop</property>186 <property name="stock_id">gtk-stop</property>
@@ -181,6 +196,7 @@
181 <property name="width_request">85</property>196 <property name="width_request">85</property>
182 <property name="visible">True</property>197 <property name="visible">True</property>
183 <property name="sensitive">False</property>198 <property name="sensitive">False</property>
199 <property name="is_important">True</property>
184 <property name="label" translatable="yes">Connect</property>200 <property name="label" translatable="yes">Connect</property>
185 <property name="use_underline">True</property>201 <property name="use_underline">True</property>
186 <property name="stock_id">gtk-connect</property>202 <property name="stock_id">gtk-connect</property>
@@ -194,6 +210,7 @@
194 <child>210 <child>
195 <object class="GtkToolButton" id="disconnect">211 <object class="GtkToolButton" id="disconnect">
196 <property name="width_request">85</property>212 <property name="width_request">85</property>
213 <property name="is_important">True</property>
197 <property name="label" translatable="yes">Disconnect</property>214 <property name="label" translatable="yes">Disconnect</property>
198 <property name="use_underline">True</property>215 <property name="use_underline">True</property>
199 <property name="stock_id">gtk-disconnect</property>216 <property name="stock_id">gtk-disconnect</property>
@@ -269,6 +286,20 @@
269 <property name="homogeneous">True</property>286 <property name="homogeneous">True</property>
270 </packing>287 </packing>
271 </child>288 </child>
289 <child>
290 <object class="GtkToolButton" id="public_files">
291 <property name="visible">True</property>
292 <property name="sensitive">False</property>
293 <property name="label" translatable="yes">Public Files</property>
294 <property name="use_underline">True</property>
295 <property name="stock_id">gtk-file</property>
296 <signal name="clicked" handler="on_public_files_clicked"/>
297 </object>
298 <packing>
299 <property name="expand">False</property>
300 <property name="homogeneous">True</property>
301 </packing>
302 </child>
272 </object>303 </object>
273 <packing>304 <packing>
274 <property name="expand">False</property>305 <property name="expand">False</property>
@@ -548,7 +579,6 @@
548 <object class="GtkAboutDialog" id="about_dialog">579 <object class="GtkAboutDialog" id="about_dialog">
549 <property name="border_width">5</property>580 <property name="border_width">5</property>
550 <property name="type_hint">normal</property>581 <property name="type_hint">normal</property>
551 <property name="has_separator">False</property>
552 <property name="program_name">Magicicada</property>582 <property name="program_name">Magicicada</property>
553 <property name="copyright" translatable="yes">Copyright 2010 Chicharreros583 <property name="copyright" translatable="yes">Copyright 2010 Chicharreros
554Copyright 2010 Natalia Bidart &lt;natalia.bidart@gmail.com&gt;584Copyright 2010 Natalia Bidart &lt;natalia.bidart@gmail.com&gt;
@@ -598,7 +628,6 @@
598 <property name="modal">True</property>628 <property name="modal">True</property>
599 <property name="window_position">center</property>629 <property name="window_position">center</property>
600 <property name="type_hint">normal</property>630 <property name="type_hint">normal</property>
601 <property name="has_separator">False</property>
602 <child internal-child="vbox">631 <child internal-child="vbox">
603 <object class="GtkVBox" id="dialog-vbox2">632 <object class="GtkVBox" id="dialog-vbox2">
604 <property name="visible">True</property>633 <property name="visible">True</property>
@@ -705,7 +734,6 @@
705 <property name="modal">True</property>734 <property name="modal">True</property>
706 <property name="window_position">center</property>735 <property name="window_position">center</property>
707 <property name="type_hint">normal</property>736 <property name="type_hint">normal</property>
708 <property name="has_separator">False</property>
709 <child internal-child="vbox">737 <child internal-child="vbox">
710 <object class="GtkVBox" id="dialog-vbox3">738 <object class="GtkVBox" id="dialog-vbox3">
711 <property name="visible">True</property>739 <property name="visible">True</property>
@@ -844,7 +872,6 @@
844 <property name="modal">True</property>872 <property name="modal">True</property>
845 <property name="window_position">center</property>873 <property name="window_position">center</property>
846 <property name="type_hint">normal</property>874 <property name="type_hint">normal</property>
847 <property name="has_separator">False</property>
848 <child internal-child="vbox">875 <child internal-child="vbox">
849 <object class="GtkVBox" id="dialog-vbox5">876 <object class="GtkVBox" id="dialog-vbox5">
850 <property name="visible">True</property>877 <property name="visible">True</property>
@@ -981,7 +1008,6 @@
981 <property name="border_width">5</property>1008 <property name="border_width">5</property>
982 <property name="window_position">center</property>1009 <property name="window_position">center</property>
983 <property name="type_hint">normal</property>1010 <property name="type_hint">normal</property>
984 <property name="has_separator">False</property>
985 <property name="create_folders">False</property>1011 <property name="create_folders">False</property>
986 <signal name="file_activated" handler="on_file_chooser_open_clicked"/>1012 <signal name="file_activated" handler="on_file_chooser_open_clicked"/>
987 <signal name="show" handler="on_file_chooser_show"/>1013 <signal name="show" handler="on_file_chooser_show"/>
@@ -1040,4 +1066,96 @@
1040 <action-widget response="0">file_chooser_open</action-widget>1066 <action-widget response="0">file_chooser_open</action-widget>
1041 </action-widgets>1067 </action-widgets>
1042 </object>1068 </object>
1069 <object class="GtkDialog" id="public_files_dialog">
1070 <property name="width_request">600</property>
1071 <property name="height_request">300</property>
1072 <property name="border_width">5</property>
1073 <property name="title" translatable="yes">Public files</property>
1074 <property name="modal">True</property>
1075 <property name="window_position">center</property>
1076 <property name="type_hint">normal</property>
1077 <child internal-child="vbox">
1078 <object class="GtkVBox" id="dialog-vbox9">
1079 <property name="visible">True</property>
1080 <property name="spacing">2</property>
1081 <child>
1082 <object class="GtkScrolledWindow" id="scrolledwindow6">
1083 <property name="visible">True</property>
1084 <property name="can_focus">True</property>
1085 <property name="hscrollbar_policy">automatic</property>
1086 <child>
1087 <object class="GtkTreeView" id="public_files_view">
1088 <property name="visible">True</property>
1089 <property name="can_focus">True</property>
1090 <property name="model">public_files_store</property>
1091 <property name="headers_clickable">False</property>
1092 <property name="rules_hint">True</property>
1093 <property name="search_column">0</property>
1094 <property name="enable_grid_lines">both</property>
1095 <property name="enable_tree_lines">True</property>
1096 <child>
1097 <object class="GtkTreeViewColumn" id="public_files_path">
1098 <property name="resizable">True</property>
1099 <property name="title">Path</property>
1100 <property name="expand">True</property>
1101 <child>
1102 <object class="GtkCellRendererText" id="cellrenderertext9"/>
1103 <attributes>
1104 <attribute name="text">0</attribute>
1105 </attributes>
1106 </child>
1107 </object>
1108 </child>
1109 <child>
1110 <object class="GtkTreeViewColumn" id="public_files_public_url">
1111 <property name="resizable">True</property>
1112 <property name="title">URL</property>
1113 <property name="expand">True</property>
1114 <child>
1115 <object class="GtkCellRendererText" id="cellrenderertext12"/>
1116 <attributes>
1117 <attribute name="text">1</attribute>
1118 </attributes>
1119 </child>
1120 </object>
1121 </child>
1122 </object>
1123 </child>
1124 </object>
1125 <packing>
1126 <property name="position">1</property>
1127 </packing>
1128 </child>
1129 <child internal-child="action_area">
1130 <object class="GtkHButtonBox" id="dialog-action_area9">
1131 <property name="visible">True</property>
1132 <property name="layout_style">end</property>
1133 <child>
1134 <object class="GtkButton" id="public_files_close">
1135 <property name="label">gtk-close</property>
1136 <property name="visible">True</property>
1137 <property name="can_focus">True</property>
1138 <property name="receives_default">True</property>
1139 <property name="use_stock">True</property>
1140 <signal name="clicked" handler="on_public_files_close_clicked"/>
1141 </object>
1142 <packing>
1143 <property name="expand">False</property>
1144 <property name="fill">False</property>
1145 <property name="position">0</property>
1146 </packing>
1147 </child>
1148 </object>
1149 <packing>
1150 <property name="expand">False</property>
1151 <property name="pack_type">end</property>
1152 <property name="position">0</property>
1153 </packing>
1154 </child>
1155 </object>
1156 </child>
1157 <action-widgets>
1158 <action-widget response="0">public_files_close</action-widget>
1159 </action-widgets>
1160 </object>
1043</interface>1161</interface>
10441162
=== modified file 'debian/changelog'
--- debian/changelog 2010-09-23 17:13:19 +0000
+++ debian/changelog 2011-01-07 23:06:32 +0000
@@ -1,3 +1,45 @@
1magicicada (0.3.0-0ubuntu1) UNRELEASED; urgency=low
2
3 * New upstream release (0.3.0):
4
5 [ Natalia B. Bidart <natalia.bidart@canonical.com> ]
6 - Added Public Files listing to the UI (LP: #568197).
7 - The icon list is now set at creation time. This guarantees that proper
8 icon sizes will be used (LP: #669947).
9 - Default logging level is now INFO.
10 - Volumes and metadata buttons are enabled when initial_data_ready
11 callback is fired (LP: #612194).
12
13 [ Facundo Batista <facundo@taniquetil.com.ar> ]
14 - Backend code to accept a share.
15 - Support the new "CQ changed" signal, with no data in it.
16 - Log all the errors that happen inside a deferred.
17 - Split "on initial data callback" in both online and offline ones.
18 - Handle Public Files info.
19 - Support GetDelta in the MQ (LP: #665680).
20 - Leave the "don't ask while asking" machinery ok if error while asking.
21 - Support an error when calling the MD (LP: #665674).
22 - Don't ask a lot of times for updates to Syncdaemon on changes bursts
23 (LP: #587020, #643195).
24
25 * debian/control
26 - Sorted Depends, dropped depends-indep
27 - Added version depend restriction for python-ubuntuone-client (>= 1.5.2)
28 - Added missing depend: python-xdg
29 - Bumped Standards-Version to 3.9.1 (no changes needed)
30 - Changed the description to match the new description in the project
31 setup.py file
32
33 * debian/copyright
34 - Added year 2011 to copyright statement
35 - Fixed email ocurrences of nataliabidart to consistently be nataliabidart
36 at gmail dot com
37 - Fixed formatting to be DEP5 compatible
38
39 * Switched to debhelper, thanks Stefano Rivera for the patch.
40
41 -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Fri, 07 Jan 2011 20:01:05 -0300
42
1magicicada (0.2-0ubuntu1) maverick; urgency=low43magicicada (0.2-0ubuntu1) maverick; urgency=low
244
3 [ Facundo Batista ]45 [ Facundo Batista ]
446
=== modified file 'debian/control'
--- debian/control 2010-06-08 10:53:02 +0000
+++ debian/control 2011-01-07 23:06:32 +0000
@@ -1,24 +1,24 @@
1Source: magicicada1Source: magicicada
2Section: python2Section: python
3Priority: extra3Priority: extra
4Build-Depends: cdbs (>= 0.4.43),4Build-Depends: debhelper (>= 7),
5 debhelper (>= 6),5 python-all (>= 2.6.5-13~),
6 python,6 python-distutils-extra (>= 2.10)
7 python-support (>= 0.6.4)7Maintainer: Natalia Bidart <nataliabidart@gmail.com>
8Build-Depends-Indep: python-distutils-extra (>= 2.10)8Standards-Version: 3.9.1
9Maintainer: Elliot Murphy <elliot@ubuntu.com>9X-Python-Version: >= 2.5
10Standards-Version: 3.8.4
1110
12Package: magicicada11Package: magicicada
13Architecture: all12Architecture: all
14Depends: ${misc:Depends},13Depends: ${misc:Depends},
15 ${misc:Depends},
16 ${python:Depends},14 ${python:Depends},
15 python-dbus,
17 python-gobject,16 python-gobject,
18 python-dbus,17 python-gtk2,
18 python-ubuntuone-client (>= 1.5.2),
19 python-twisted-core,19 python-twisted-core,
20 python-gtk2,20 python-xdg
21 python-ubuntuone-client21Breaks: ${python:Breaks}
22Description: A GTK+ frontend for Ubuntu One file sync.22Description: A GTK+ frontend for Ubuntu One file sync.
23 Displays diagnostic information about what the syncdaemon is doing23 This application provides a GTK+ frontend to manage the file
24 on your computer.24 synchronisation service of Ubuntu One.
2525
=== modified file 'debian/copyright'
--- debian/copyright 2010-06-08 10:53:02 +0000
+++ debian/copyright 2011-01-07 23:06:32 +0000
@@ -1,11 +1,19 @@
1Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat1Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat
2Upstream-Name: magicicada2Upstream-Name: magicicada
3Upstream-Maintainer: Natalia Bidart <natalia.bidart@ubuntu.com>3Upstream-Maintainer: Natalia Bidart <nataliabidart@gmail.com>
4Upstream-Source: https://launchpad.net/magicicada4Upstream-Source: https://launchpad.net/magicicada
55
6Files: *6Files: *
7Copyright: (C) 2010 Facundo Batista <facundo@canonical.com>7Copyright 2010, 2011 Facundo Batista <facundo@canonical.com> Natalia Bidart <nataliabidart@gmail.com>
8Copyright: (C) 2010 Natalia Bidart <natalia.bidart@gmail.com>8License: GPL-3+
9License: GPL-39 This program is free software; you can redistribute it and/or modify
10 The full text of the GPL is distributed in10 it under the terms of the GNU General Public License as published by
11 /usr/share/common-licenses/GPL-3 on Debian systems.11 the Free Software Foundation, version 3 of the License.
12 .
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17 .
18 On Debian systems, the complete text of the GNU General Public License
19 version 3 can be found in the /usr/share/common-licenses/GPL-3 file.
1220
=== removed file 'debian/pycompat'
--- debian/pycompat 2010-06-08 10:53:02 +0000
+++ debian/pycompat 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
12
20
=== modified file 'debian/rules'
--- debian/rules 2010-09-23 17:13:19 +0000
+++ debian/rules 2011-01-07 23:06:32 +0000
@@ -1,5 +1,3 @@
1#!/usr/bin/make -f1#!/usr/bin/make -f
2DEB_PYTHON_SYSTEM := pysupport2%:
33 dh $@ --with python2
4include /usr/share/cdbs/1/rules/debhelper.mk
5include /usr/share/cdbs/1/class/python-distutils.mk
64
=== modified file 'magicicada/__init__.py'
--- magicicada/__init__.py 2010-08-21 18:54:31 +0000
+++ magicicada/__init__.py 2011-01-07 23:06:32 +0000
@@ -16,7 +16,7 @@
16# You should have received a copy of the GNU General Public License along16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19"""Magicicada."""19"""Magicicada GTK UI."""
2020
21import logging21import logging
22import os22import os
@@ -31,14 +31,15 @@
31# this shouldn't crash if not found as it is simply used for bug reporting31# this shouldn't crash if not found as it is simply used for bug reporting
32try:32try:
33 import LaunchpadIntegration33 import LaunchpadIntegration
34 launchpad_available = True34 LAUNCHPAD_AVAILABLE = True
35except ImportError:35except ImportError:
36 launchpad_available = False36 LAUNCHPAD_AVAILABLE = False
3737
38from twisted.internet import gtk2reactor # for gtk-2.038from twisted.internet import gtk2reactor # for gtk-2.0
39gtk2reactor.install()39gtk2reactor.install()
4040
41from magicicada import syncdaemon, dbusiface, logger as logger_helper41from magicicada import syncdaemon, logger as logger_helper
42from magicicada.dbusiface import NOT_SYNCHED_PATH
42from magicicada.helpers import humanize_bytes, get_data_file, get_builder, \43from magicicada.helpers import humanize_bytes, get_data_file, get_builder, \
43 log, NO_OP44 log, NO_OP
4445
@@ -47,16 +48,11 @@
47UBUNTU_ONE_ROOT = os.path.expanduser('~/Ubuntu One')48UBUNTU_ONE_ROOT = os.path.expanduser('~/Ubuntu One')
4849
49# set up the logging for all the project50# set up the logging for all the project
51# Instance of 'RootLogger' has no 'set_up' member
52# pylint: disable=E1103
50logger_helper.set_up()53logger_helper.set_up()
51logger = logging.getLogger('magicicada.ui')54logger = logging.getLogger('magicicada.ui')
5255
53DEBUG = os.getenv('DEBUG')
54if DEBUG:
55 console = logging.StreamHandler()
56 console.setLevel(logging.DEBUG)
57 logger.addHandler(console)
58
59
60# Instance of 'A' has no 'y' member56# Instance of 'A' has no 'y' member
61# pylint: disable=E110157# pylint: disable=E1101
6258
@@ -81,7 +77,7 @@
81 self.builder = get_builder('gui.glade')77 self.builder = get_builder('gui.glade')
82 self.builder.connect_signals(self)78 self.builder.connect_signals(self)
8379
84 if launchpad_available:80 if LAUNCHPAD_AVAILABLE:
85 # for more information about LaunchpadIntegration:81 # for more information about LaunchpadIntegration:
86 # wiki.ubuntu.com/UbuntuDevelopment/Internationalisation/Coding82 # wiki.ubuntu.com/UbuntuDevelopment/Internationalisation/Coding
87 helpmenu = self.builder.get_object('helpMenu')83 helpmenu = self.builder.get_object('helpMenu')
@@ -105,6 +101,8 @@
105 'shares_to_others', 'shares_to_others_dialog', # shares_to_others101 'shares_to_others', 'shares_to_others_dialog', # shares_to_others
106 'shares_to_others_view', 'shares_to_others_store',102 'shares_to_others_view', 'shares_to_others_store',
107 'shares_to_others_close', 'metadata', # metadata103 'shares_to_others_close', 'metadata', # metadata
104 'public_files', 'public_files_dialog', # public_files
105 'public_files_view', 'public_files_store', 'public_files_close',
108 'is_started', 'is_connected', 'is_online', # status bar images106 'is_started', 'is_connected', 'is_online', # status bar images
109 'status_label', 'status_icon', # status label and systray icon107 'status_label', 'status_icon', # status label and systray icon
110 'metaq_view', 'contentq_view', # queues tree views108 'metaq_view', 'contentq_view', # queues tree views
@@ -122,18 +120,18 @@
122 self._make_view_sortable('folders')120 self._make_view_sortable('folders')
123 self._make_view_sortable('shares_to_me')121 self._make_view_sortable('shares_to_me')
124 self._make_view_sortable('shares_to_others')122 self._make_view_sortable('shares_to_others')
123 self._make_view_sortable('public_files')
125124
126 self.metadata_dialogs = {}125 self.metadata_dialogs = {}
127 self.volumes = (self.folders, self.shares_to_me, self.shares_to_others)126 self.volumes = (self.folders, self.shares_to_me, self.shares_to_others)
128 self.windows = (self.main_window, self.about_dialog,
129 self.folders_dialog, self.shares_to_me_dialog,
130 self.shares_to_others_dialog)
131127
132 icon_filename = get_data_file('media', 'logo-016.png')128 self._icons = {}
133 self._icon = gtk.gdk.pixbuf_new_from_file(icon_filename)129 for size in (16, 32, 48, 64, 128):
134 self.status_icon.set_from_file(icon_filename)130 icon_filename = get_data_file('media', 'logo-%.3i.png' % size)
135 for w in self.windows:131 self._icons[size] = gtk.gdk.pixbuf_new_from_file(icon_filename)
136 w.set_icon(self._icon)132 self.status_icon.set_from_pixbuf(self._icons[16])
133 self.main_window.set_icon_list(*self._icons.values())
134 gtk.window_set_default_icon_list(*self._icons.values())
137135
138 about_fname = get_data_file('media', 'logo-128.png')136 about_fname = get_data_file('media', 'logo-128.png')
139 self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(about_fname))137 self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(about_fname))
@@ -149,6 +147,9 @@
149 self.sd.content_queue_changed_callback = self.on_content_queue_changed147 self.sd.content_queue_changed_callback = self.on_content_queue_changed
150 self.sd.meta_queue_changed_callback = self.on_meta_queue_changed148 self.sd.meta_queue_changed_callback = self.on_meta_queue_changed
151 self.sd.on_metadata_ready_callback = self.on_metadata_ready149 self.sd.on_metadata_ready_callback = self.on_metadata_ready
150 self.sd.on_initial_data_ready_callback = self.on_initial_data_ready
151 self.sd.on_initial_online_data_ready_callback = \
152 self.on_initial_online_data_ready
152153
153 self.widget_is_visible = lambda w: w.get_property('visible')154 self.widget_is_visible = lambda w: w.get_property('visible')
154 self.widget_enabled = lambda w: self.widget_is_visible(w) and \155 self.widget_enabled = lambda w: self.widget_is_visible(w) and \
@@ -160,6 +161,8 @@
160 store = getattr(self, '%s_store' % view_name)161 store = getattr(self, '%s_store' % view_name)
161 view = getattr(self, '%s_view' % view_name)162 view = getattr(self, '%s_view' % view_name)
162 self._sorting_order[store] = {}163 self._sorting_order[store] = {}
164 # this enforces that the order that columns will be shown and sorted
165 # matches the order in the underlying model
163 for i, col in enumerate(view.get_columns()):166 for i, col in enumerate(view.get_columns()):
164 col.set_clickable(True)167 col.set_clickable(True)
165 col.connect('clicked', self.on_store_sort_column_changed, i, store)168 col.connect('clicked', self.on_store_sort_column_changed, i, store)
@@ -175,7 +178,6 @@
175 dialog.set_border_width(10)178 dialog.set_border_width(10)
176 # gtk.WIN_POS_CENTER makes dialogs overlap179 # gtk.WIN_POS_CENTER makes dialogs overlap
177 dialog.set_position(gtk.WIN_POS_MOUSE)180 dialog.set_position(gtk.WIN_POS_MOUSE)
178 dialog.set_icon(self._icon)
179181
180 close_button = dialog.action_area.get_children()[-1]182 close_button = dialog.action_area.get_children()[-1]
181 close_button.connect('clicked', self.on_metadata_close_clicked, path)183 close_button.connect('clicked', self.on_metadata_close_clicked, path)
@@ -222,9 +224,10 @@
222224
223 def on_stop_clicked(self, widget, data=None):225 def on_stop_clicked(self, widget, data=None):
224 """Stop syncdaemon."""226 """Stop syncdaemon."""
225 for v in self.volumes:227 for volume in self.volumes:
226 v.set_sensitive(False)228 volume.set_sensitive(False)
227 self.metadata.set_sensitive(False)229 self.metadata.set_sensitive(False)
230 self.public_files.set_sensitive(False)
228231
229 if self.widget_enabled(self.disconnect):232 if self.widget_enabled(self.disconnect):
230 self.on_disconnect_clicked(self.disconnect)233 self.on_disconnect_clicked(self.disconnect)
@@ -331,6 +334,24 @@
331 dialog.spinner.show()334 dialog.spinner.show()
332 dialog.show()335 dialog.show()
333336
337 def on_public_files_close_clicked(self, widget, data=None):
338 """Close the public_files dialog."""
339 self.public_files_dialog.response(gtk.RESPONSE_CLOSE)
340
341 def on_public_files_clicked(self, widget, data=None):
342 """Show information about public files."""
343 items = self.sd.public_files
344 if items is None:
345 items = {}
346
347 self.public_files_store.clear()
348 for item in items.itervalues():
349 row = (item.path, item.public_url, item.volume, item.node)
350 self.public_files_store.append(row)
351
352 self.public_files_dialog.run()
353 self.public_files_dialog.hide()
354
334 def on_status_icon_activate(self, widget, data=None):355 def on_status_icon_activate(self, widget, data=None):
335 """Systray icon was clicked."""356 """Systray icon was clicked."""
336 if self.widget_is_visible(self.main_window):357 if self.widget_is_visible(self.main_window):
@@ -370,10 +391,6 @@
370 self._activate_indicator(self.is_started)391 self._activate_indicator(self.is_started)
371 self.connect.set_sensitive(True)392 self.connect.set_sensitive(True)
372393
373 for v in self.volumes:
374 v.set_sensitive(True)
375 self.metadata.set_sensitive(True)
376
377 self._update_queues_and_status(self.sd.current_state)394 self._update_queues_and_status(self.sd.current_state)
378395
379 @log(logger)396 @log(logger)
@@ -482,13 +499,25 @@
482 dialog = self.metadata_dialogs[path]499 dialog = self.metadata_dialogs[path]
483 dialog.spinner.hide()500 dialog.spinner.hide()
484 dialog.view.show()501 dialog.view.show()
485 if metadata == dbusiface.NOT_SYNCHED_PATH:502 if metadata == NOT_SYNCHED_PATH:
486 # Metadata path doesn't exsit for syncdaemon503 # Metadata path doesn't exsit for syncdaemon
487 text = dbusiface.NOT_SYNCHED_PATH504 text = NOT_SYNCHED_PATH
488 else:505 else:
489 text = '\n'.join('%s: %s' % i for i in metadata.iteritems())506 text = '\n'.join('%s: %s' % i for i in metadata.iteritems())
490 dialog.view.get_buffer().set_text(text)507 dialog.view.get_buffer().set_text(text)
491508
509 @log(logger, level=logging.INFO)
510 def on_initial_data_ready(self):
511 """Initial data is now available in syncdaemon."""
512 for volume in self.volumes:
513 volume.set_sensitive(True)
514 self.metadata.set_sensitive(True)
515
516 @log(logger, level=logging.INFO)
517 def on_initial_online_data_ready(self):
518 """Online initial data is now available in syncdaemon."""
519 self.public_files.set_sensitive(True)
520
492 # custom521 # custom
493522
494 def _start_loading(self, what):523 def _start_loading(self, what):
495524
=== modified file 'magicicada/dbusiface.py'
--- magicicada/dbusiface.py 2010-08-23 16:48:07 +0000
+++ magicicada/dbusiface.py 2011-01-07 23:06:32 +0000
@@ -27,7 +27,17 @@
27from dbus.mainloop.glib import DBusGMainLoop27from dbus.mainloop.glib import DBusGMainLoop
28from twisted.internet import defer28from twisted.internet import defer
2929
30from ubuntuone.syncdaemon.tools import SyncDaemonTool30try:
31 # yes, they can be imported! pylint: disable=F0401,E0611
32 from ubuntuone.platform.tools import SyncDaemonTool, DBusClient
33 from ubuntuone.platform.dbus_interface import DBUS_IFACE_PUBLIC_FILES_NAME
34except ImportError:
35 # support for old structure (pre-Natty)
36 # yes, they can be imported! pylint: disable=F0401,E0611
37 from ubuntuone.syncdaemon.tools import SyncDaemonTool, DBusClient
38 from ubuntuone.syncdaemon.dbus_interface import (
39 DBUS_IFACE_PUBLIC_FILES_NAME,
40 )
3141
32# log!42# log!
33logger = logging.getLogger('magicicada.dbusiface')43logger = logging.getLogger('magicicada.dbusiface')
@@ -40,6 +50,8 @@
40ShareData = collections.namedtuple('ShareData', 'accepted access_level '50ShareData = collections.namedtuple('ShareData', 'accepted access_level '
41 'free_bytes name node_id other_username '51 'free_bytes name node_id other_username '
42 'other_visible_name path volume_id')52 'other_visible_name path volume_id')
53PublicFilesData = collections.namedtuple('PublicFilesData',
54 'volume node path public_url')
4355
44# regular expressions for parsing MetaQueue data56# regular expressions for parsing MetaQueue data
45RE_OP_LISTDIR = re.compile("(ListDir)\(share_id=(.*?), node_id=(.*?), .*")57RE_OP_LISTDIR = re.compile("(ListDir)\(share_id=(.*?), node_id=(.*?), .*")
@@ -61,6 +73,15 @@
61NOT_SYNCHED_PATH = "Not a valid path!"73NOT_SYNCHED_PATH = "Not a valid path!"
6274
6375
76# some exceptions
77class ShareOperationError(Exception):
78 """Error on an operation on a share."""
79 def __init__(self, share_id, error):
80 self.share_id = share_id
81 self.error = error
82 Exception.__init__(self)
83
84
64def _is_retry_exception(err):85def _is_retry_exception(err):
65 """Check if the exception is a retry one."""86 """Check if the exception is a retry one."""
66 if isinstance(err, dbus.exceptions.DBusException):87 if isinstance(err, dbus.exceptions.DBusException):
@@ -97,6 +118,7 @@
97 # magicicada's syncdaemon118 # magicicada's syncdaemon
98 self.msd = msd119 self.msd = msd
99 logger.info("DBus interface starting")120 logger.info("DBus interface starting")
121 self._public_files_deferred = None
100122
101 # set up dbus and related stuff123 # set up dbus and related stuff
102 loop = DBusGMainLoop(set_as_default=True)124 loop = DBusGMainLoop(set_as_default=True)
@@ -116,6 +138,9 @@
116 (self._on_share_created, 'Shares', 'ShareCreated'),138 (self._on_share_created, 'Shares', 'ShareCreated'),
117 (self._on_share_deleted, 'Shares', 'ShareDeleted'),139 (self._on_share_deleted, 'Shares', 'ShareDeleted'),
118 (self._on_share_changed, 'Shares', 'ShareChanged'),140 (self._on_share_changed, 'Shares', 'ShareChanged'),
141 (self._on_public_files_list, 'PublicFiles', 'PublicFilesList'),
142 (self._on_public_files_changed, 'PublicFiles',
143 'PublicAccessChanged'),
119 ]144 ]
120 self._dbus_matches = []145 self._dbus_matches = []
121 for method, dbus_lastname, signal_name in _signals:146 for method, dbus_lastname, signal_name in _signals:
@@ -164,7 +189,7 @@
164 data = self._process_status(state)189 data = self._process_status(state)
165 self.msd.on_sd_status_changed(*data)190 self.msd.on_sd_status_changed(*data)
166191
167 def _on_content_queue_changed(self, _):192 def _on_content_queue_changed(self, _=None):
168 """Call the SD callback."""193 """Call the SD callback."""
169 logger.info("Received Content Queue changed")194 logger.info("Received Content Queue changed")
170 self.msd.on_sd_content_queue_changed()195 self.msd.on_sd_content_queue_changed()
@@ -223,6 +248,57 @@
223 logger.info("Received Share changed")248 logger.info("Received Share changed")
224 self.msd.on_sd_shares_changed()249 self.msd.on_sd_shares_changed()
225250
251 def _on_public_files_changed(self, data):
252 """Call the SD callback."""
253 logger.debug("Received Public Files changed: %s", data)
254 pf = PublicFilesData(volume=data['share_id'], node=data['node_id'],
255 path=data['path'], public_url=data['public_url'])
256 is_public = bool(data['is_public'])
257 self.msd.on_sd_public_files_changed(pf, is_public)
258
259 def _on_public_files_list(self, data):
260 """Call the SD callback."""
261 logger.info("Received Public Files list (%d)", len(data))
262 processed = []
263 for d in data:
264 logger.debug(" Public Files data: %s", d)
265 p = PublicFilesData(volume=d['volume_id'], node=d['node_id'],
266 path=d['path'], public_url=d['public_url'])
267 processed.append(p)
268
269 # trigger the deferred if have one, else call the msd with the data
270 if self._public_files_deferred is None:
271 self.msd.on_sd_public_files_list(processed)
272 else:
273 d = self._public_files_deferred
274 self._public_files_deferred = None
275 d.callback(processed)
276
277 def get_public_files(self):
278 """Ask the Public Files info to syncdaemon."""
279 client = DBusClient(self._bus, '/publicfiles',
280 DBUS_IFACE_PUBLIC_FILES_NAME)
281
282 # note that these callbacks do not come with the requested info, the
283 # method just will return None, and the real info will come later
284 # in a signal
285
286 def call_done(result):
287 """Call was succesful."""
288 logger.debug("Public files asked ok.")
289
290 def call_error(error):
291 """Call was not succesful."""
292 logger.error("Public files asked with error: %s", error)
293
294 client.call_method('get_public_files',
295 reply_handler=call_done, error_handler=call_error)
296
297 # return a stored deferred that will be triggered later with the signal
298 d = defer.Deferred()
299 self._public_files_deferred = d
300 return d
301
226 @retryable302 @retryable
227 def get_content_queue(self):303 def get_content_queue(self):
228 """Get the content queue from SDT."""304 """Get the content queue from SDT."""
@@ -293,7 +369,7 @@
293 """Use MetaQueue dictionary and prepare command data."""369 """Use MetaQueue dictionary and prepare command data."""
294 if op in ('AccountInquiry', 'FreeSpaceInquiry', 'Query', 'ListShares',370 if op in ('AccountInquiry', 'FreeSpaceInquiry', 'Query', 'ListShares',
295 'GetPublicFiles', 'ListVolumes', 'ChangePublicAccess',371 'GetPublicFiles', 'ListVolumes', 'ChangePublicAccess',
296 'AnswerShare'):372 'AnswerShare', 'GetDelta'):
297 return QueueData(operation=op, path=None, node=None, share=None)373 return QueueData(operation=op, path=None, node=None, share=None)
298374
299 if op in ('ListDir', 'Unlink'):375 if op in ('ListDir', 'Unlink'):
@@ -473,3 +549,19 @@
473 d = self.sync_daemon_tool.get_metadata(path)549 d = self.sync_daemon_tool.get_metadata(path)
474 d.addCallbacks(process, fix_failure)550 d.addCallbacks(process, fix_failure)
475 return d551 return d
552
553 @retryable
554 @defer.inlineCallbacks
555 def accept_share(self, share_id):
556 """Accept a share."""
557 logger.debug("Accept share %s started", share_id)
558 try:
559 result = yield self.sync_daemon_tool.accept_share(share_id)
560 except dbus.exceptions.DBusException, e:
561 error = str(e.args[0])
562 logger.debug("Accept share %s crashed: %s", share_id, error)
563 raise ShareOperationError(share_id=share_id, error=error)
564
565 logger.debug("Accept share %s finished: %s", share_id, result)
566 if 'error' in result:
567 raise ShareOperationError(share_id=share_id, error=result['error'])
476568
=== modified file 'magicicada/helpers.py'
--- magicicada/helpers.py 2010-07-24 19:37:32 +0000
+++ magicicada/helpers.py 2011-01-07 23:06:32 +0000
@@ -1,21 +1,32 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2### BEGIN LICENSE2#
3# This file is in the public domain3# Author: Natalia Bidart <natalia.bidart@gmail.com>
4### END LICENSE4#
5# Copyright 2010 Chicharreros
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
518
6"""Helpers for an Ubuntu application."""19"""Helpers for an Ubuntu application."""
720
8from __future__ import division21from __future__ import division
922
10__all__ = [23import logging
11 'make_window',
12 ]
13
14import gtk
15import os24import os
1625
17from functools import wraps26from functools import wraps
1827
28import gtk
29
19from magicicada.magicicadaconfig import get_data_file30from magicicada.magicicadaconfig import get_data_file
2031
2132
@@ -39,7 +50,7 @@
39 return builder50 return builder
4051
4152
42def log(logger):53def log(logger, level=logging.DEBUG):
43 """Log input/ouput info for 'f' using 'logger'."""54 """Log input/ouput info for 'f' using 'logger'."""
4455
45 def decorator(f):56 def decorator(f):
@@ -50,13 +61,14 @@
50 """Wrap 'f', log input args and result using 'logger'."""61 """Wrap 'f', log input args and result using 'logger'."""
51 name = f.__name__62 name = f.__name__
52 result = None63 result = None
53 logger.debug("Calling '%s' with args '%s' and kwargs '%s'.",64 logger.log(level, "Calling '%s' with args '%s' and kwargs '%s'.",
54 name, args, kwargs)65 name, args, kwargs)
55 try:66 try:
56 result = f(*args, **kwargs)67 result = f(*args, **kwargs)
57 except Exception: # pylint: disable=W070368 except Exception: # pylint: disable=W0703
58 logger.exception('%s failed with exception:', name)69 logger.exception('%s failed with exception:', name)
59 logger.debug("Returning from '%s' with result '%s'.", name, result)70 logger.log(level, "Returning from '%s' with result '%s'.",
71 name, result)
60 return result72 return result
6173
62 return inner74 return inner
@@ -91,11 +103,11 @@
91 '1.3 GB'103 '1.3 GB'
92 """104 """
93 abbrevs = (105 abbrevs = (
94 (1<<50L, 'PB'),106 (1 << 50, 'PB'),
95 (1<<40L, 'TB'),107 (1 << 40, 'TB'),
96 (1<<30L, 'GB'),108 (1 << 30, 'GB'),
97 (1<<20L, 'MB'),109 (1 << 20, 'MB'),
98 (1<<10L, 'kB'),110 (1 << 10, 'kB'),
99 (1, 'bytes'))111 (1, 'bytes'))
100112
101 if numbytes == 1:113 if numbytes == 1:
102114
=== modified file 'magicicada/logger.py'
--- magicicada/logger.py 2010-08-18 21:17:20 +0000
+++ magicicada/logger.py 2011-01-07 23:06:32 +0000
@@ -26,6 +26,7 @@
2626
27from logging.handlers import RotatingFileHandler27from logging.handlers import RotatingFileHandler
2828
29import twisted.python.log
29import xdg.BaseDirectory30import xdg.BaseDirectory
3031
3132
@@ -37,21 +38,35 @@
37 self.doRollover()38 self.doRollover()
3839
3940
41def deferror_handler(data):
42 """Deferred error handler.
43
44 We receive all stuff here, filter the errors and use correct info. Note
45 that we don't send to stderr as Twisted already does that.
46 """
47 try:
48 failure = data['failure']
49 except KeyError:
50 msg = data['message']
51 else:
52 msg = failure.getTraceback()
53 logger = logging.getLogger('magicicada')
54 logger.error("Unhandled error in deferred!\n%s", msg)
55
56
40def exception_handler(exc_type, exc_value, tb):57def exception_handler(exc_type, exc_value, tb):
41 """Handle an unhandled exception."""58 """Handle an unhandled exception."""
42 # stderr
43 exception = traceback.format_exception(exc_type, exc_value, tb)59 exception = traceback.format_exception(exc_type, exc_value, tb)
44 exception = "".join(exception)60 msg = "".join(exception)
45 print >> sys.stderr, exception61 print >> sys.stderr, msg
4662
47 # log63 # log
48 logger = logging.getLogger('magicicada')64 logger = logging.getLogger('magicicada')
49 logger.error("Unhandled exception!\n%s", exception)65 logger.error("Unhandled exception!\n%s", msg)
5066
5167
52def set_up():68def set_up():
53 """Set up the logging."""69 """Set up the logging."""
54
55 # choose the folder to store the logs70 # choose the folder to store the logs
56 cache = xdg.BaseDirectory.xdg_cache_home71 cache = xdg.BaseDirectory.xdg_cache_home
57 logfolder = os.path.join(cache, 'magicicada')72 logfolder = os.path.join(cache, 'magicicada')
@@ -62,11 +77,20 @@
62 logger = logging.getLogger('magicicada')77 logger = logging.getLogger('magicicada')
63 handler = CustomRotatingFH(logfile, maxBytes=1e6, backupCount=10)78 handler = CustomRotatingFH(logfile, maxBytes=1e6, backupCount=10)
64 logger.addHandler(handler)79 logger.addHandler(handler)
65 formatter = logging.Formatter("%(asctime)s %(name)-23s"80 formatter = logging.Formatter("%(asctime)s %(name)-23s"
66 "%(levelname)-8s %(message)s",81 "%(levelname)-8s %(message)s")
67 '%Y-%m-%d %H:%M:%S')
68 handler.setFormatter(formatter)82 handler.setFormatter(formatter)
69 logger.setLevel(logging.DEBUG)83 logger.setLevel(logging.INFO)
84
85 if os.getenv('DEBUG'):
86 console = logging.StreamHandler()
87 console.setLevel(logging.DEBUG)
88 console.setFormatter(formatter)
89 logger.addHandler(console)
90 logger.setLevel(logging.DEBUG)
7091
71 # hook the exception handler92 # hook the exception handler
72 sys.excepthook = exception_handler93 sys.excepthook = exception_handler
94
95 # hook the twisted observer
96 twisted.python.log.addObserver(deferror_handler)
7397
=== modified file 'magicicada/syncdaemon.py'
--- magicicada/syncdaemon.py 2010-09-02 18:23:02 +0000
+++ magicicada/syncdaemon.py 2011-01-07 23:06:32 +0000
@@ -23,13 +23,24 @@
2323
24from twisted.internet import defer, reactor24from twisted.internet import defer, reactor
2525
26from magicicada.dbusiface import DBusInterface26from magicicada.dbusiface import DBusInterface, ShareOperationError
27from magicicada.helpers import NO_OP27from magicicada.helpers import NO_OP
2828
29
30# log!29# log!
31logger = logging.getLogger('magicicada.syncdaemon')30logger = logging.getLogger('magicicada.syncdaemon')
3231
32# states for MQ and CQ handling bursts
33ASKING_IDLE, ASKING_YES, ASKING_LATER = range(3)
34
35
36def mandatory_callback(function_name):
37 """Log that the callback was not overwritten."""
38 def f(*a, **k):
39 """Fake callback."""
40 logger.warning("Callback called but was not assigned! "
41 "%r called with %s %s", function_name, a, k)
42 return f
43
3344
34class State(object):45class State(object):
35 """Hold the state of SD."""46 """Hold the state of SD."""
@@ -93,6 +104,7 @@
93 self.shares_to_others = None104 self.shares_to_others = None
94 self.content_queue = None105 self.content_queue = None
95 self.meta_queue = None106 self.meta_queue = None
107 self.public_files = None
96108
97 # callbacks for GUI to hook in109 # callbacks for GUI to hook in
98 self.status_changed_callback = NO_OP110 self.status_changed_callback = NO_OP
@@ -107,8 +119,14 @@
107 self.on_folders_changed_callback = NO_OP119 self.on_folders_changed_callback = NO_OP
108 self.on_shares_to_me_changed_callback = NO_OP120 self.on_shares_to_me_changed_callback = NO_OP
109 self.on_shares_to_others_changed_callback = NO_OP121 self.on_shares_to_others_changed_callback = NO_OP
110 self.on_metadata_ready_callback = None # mandatory122 self.on_metadata_ready_callback = mandatory_callback(
123 'on_metadata_ready_callback')
111 self.on_initial_data_ready_callback = NO_OP124 self.on_initial_data_ready_callback = NO_OP
125 self.on_initial_online_data_ready_callback = NO_OP
126 self.on_share_op_success_callback = mandatory_callback(
127 'on_share_op_success_callback')
128 self.on_share_op_error_callback = mandatory_callback(
129 'on_share_op_error_callback')
112130
113 # mq needs to (maybe) be polled to know progress131 # mq needs to (maybe) be polled to know progress
114 self._must_poll_mq = True132 self._must_poll_mq = True
@@ -122,6 +140,10 @@
122 else:140 else:
123 self.current_state.set(is_started=False)141 self.current_state.set(is_started=False)
124142
143 # mq and cq burst handling
144 self._cq_asking = ASKING_IDLE
145 self._mq_asking = ASKING_IDLE
146
125 def shutdown(self):147 def shutdown(self):
126 """Shut down the SyncDaemon."""148 """Shut down the SyncDaemon."""
127 logger.info("SyncDaemon interface going down")149 logger.info("SyncDaemon interface going down")
@@ -134,7 +156,7 @@
134 @defer.inlineCallbacks156 @defer.inlineCallbacks
135 def _get_initial_data(self):157 def _get_initial_data(self):
136 """Get the initial SD data."""158 """Get the initial SD data."""
137 logger.info("Getting initial data")159 logger.info("Getting offline initial data")
138160
139 status_data = yield self.dbus.get_status()161 status_data = yield self.dbus.get_status()
140 self._send_status_changed(*status_data)162 self._send_status_changed(*status_data)
@@ -150,10 +172,34 @@
150 self.shares_to_me = yield self.dbus.get_shares_to_me()172 self.shares_to_me = yield self.dbus.get_shares_to_me()
151 self.shares_to_others = yield self.dbus.get_shares_to_others()173 self.shares_to_others = yield self.dbus.get_shares_to_others()
152174
153 # let frontend know that we have all the initial data175 # let frontend know that we have all the initial offline data
154 logger.info("All initial data is ready")176 logger.info("All initial offline data is ready")
155 self.on_initial_data_ready_callback()177 self.on_initial_data_ready_callback()
156178
179 logger.info("Getting online initial data")
180 pf_list = yield self.dbus.get_public_files()
181 self.on_sd_public_files_list(pf_list)
182
183 # let frontend know that we have all the initial online data
184 logger.info("All initial online data is ready")
185 self.on_initial_online_data_ready_callback()
186
187 def on_sd_public_files_list(self, data):
188 """Set a new Public Files list."""
189 logger.info("Got new Public Files list (%d items)", len(data))
190 self.public_files = dict((d.node, d) for d in data)
191
192 def on_sd_public_files_changed(self, pf, is_public):
193 """Update the Public Files list."""
194 logger.info("Change in Public Files list! is_public=%s (volume=%s "
195 "node=%s url=%s path=%r)", is_public, pf.volume, pf.node,
196 pf.public_url, pf.path)
197 if is_public:
198 self.public_files[pf.node] = pf
199 else:
200 # remove it if there
201 self.public_files.pop(pf.node, None)
202
157 @defer.inlineCallbacks203 @defer.inlineCallbacks
158 def on_sd_shares_changed(self):204 def on_sd_shares_changed(self):
159 """Shares changed, ask for new information."""205 """Shares changed, ask for new information."""
@@ -234,19 +280,47 @@
234 @defer.inlineCallbacks280 @defer.inlineCallbacks
235 def on_sd_content_queue_changed(self):281 def on_sd_content_queue_changed(self):
236 """Content Queue changed, ask for new information."""282 """Content Queue changed, ask for new information."""
237 logger.info("SD Content Queue changed")283 if self._cq_asking != ASKING_IDLE:
238 new_cq = yield self.dbus.get_content_queue()284 self._cq_asking = ASKING_LATER
239 if new_cq != self.content_queue:285 logger.info("SD Content Queue changed, but already asking")
240 logger.info("Content Queue info is new! %d items", len(new_cq))286 return
241 self.content_queue = new_cq287
242 self.content_queue_changed_callback(new_cq)288 logger.info("SD Content Queue changed, asking for info")
289 self._cq_asking = ASKING_YES
290 while self._cq_asking != ASKING_IDLE:
291 new_cq = yield self.dbus.get_content_queue()
292 if new_cq != self.content_queue:
293 logger.info("Content Queue info is new! %d items", len(new_cq))
294 self.content_queue = new_cq
295 self.content_queue_changed_callback(new_cq)
296 if self._cq_asking == ASKING_LATER:
297 self._cq_asking = ASKING_YES
298 else:
299 self._cq_asking = ASKING_IDLE
243300
244 @defer.inlineCallbacks301 @defer.inlineCallbacks
245 def on_sd_meta_queue_changed(self):302 def on_sd_meta_queue_changed(self):
246 """Meta Queue changed, ask for new information."""303 """Meta Queue changed, ask for new information."""
247 logger.info("SD Meta Queue changed")304 if self._mq_asking != ASKING_IDLE:
305 self._mq_asking = ASKING_LATER
306 logger.info("SD Meta Queue changed, but already asking")
307 return
308
309 logger.info("SD Meta Queue changed, asking for info")
248 self._must_poll_mq = False310 self._must_poll_mq = False
249 yield self._get_mq_data()311 self._mq_asking = ASKING_YES
312 while self._mq_asking != ASKING_IDLE:
313 try:
314 yield self._get_mq_data()
315 except Exception:
316 # on any error, leave the state sane
317 self._mq_asking = ASKING_IDLE
318 raise
319
320 if self._mq_asking == ASKING_LATER:
321 self._mq_asking = ASKING_YES
322 else:
323 self._mq_asking = ASKING_IDLE
250324
251 @defer.inlineCallbacks325 @defer.inlineCallbacks
252 def _get_mq_data(self):326 def _get_mq_data(self):
@@ -274,6 +348,8 @@
274 self._get_mq_data()348 self._get_mq_data()
275349
276 if self._must_poll_mq:350 if self._must_poll_mq:
351 # Module 'twisted.internet.reactor' has no 'callLater' member
352 # pylint: disable=E1101
277 self._mqcaller = reactor.callLater(self._mq_poll_time,353 self._mqcaller = reactor.callLater(self._mq_poll_time,
278 self._check_mq)354 self._check_mq)
279355
@@ -300,8 +376,27 @@
300376
301 def get_metadata(self, path):377 def get_metadata(self, path):
302 """Get the metadata for given path."""378 """Get the metadata for given path."""
303 if self.on_metadata_ready_callback is None:
304 raise ValueError("Missing the mandatory cback for get_metadata.")
305
306 d = self.dbus.get_metadata(os.path.realpath(path))379 d = self.dbus.get_metadata(os.path.realpath(path))
307 d.addCallback(lambda resp: self.on_metadata_ready_callback(path, resp))380 d.addCallback(lambda resp: self.on_metadata_ready_callback(path, resp))
381
382 def accept_share(self, share_id):
383 """Accept a share."""
384 def error(failure):
385 """Operation failed."""
386 if failure.check(ShareOperationError):
387 error = failure.value.error
388 logger.info("Accepting share %s finished with error: %s",
389 share_id, error)
390 self.on_share_op_error_callback(share_id, error)
391 else:
392 logger.error("Unexpected error when accepting share %s: %s %s",
393 share_id, failure.type, failure.value)
394
395 def success(_):
396 """Operation finished ok."""
397 logger.info("Accepting share %s finished successfully", share_id)
398 self.on_share_op_success_callback(share_id)
399
400 logger.info("Accepting share %s started", share_id)
401 d = self.dbus.accept_share(share_id)
402 d.addCallbacks(success, error)
308403
=== modified file 'magicicada/tests/helpers.py'
--- magicicada/tests/helpers.py 2010-08-18 21:04:16 +0000
+++ magicicada/tests/helpers.py 2011-01-07 23:06:32 +0000
@@ -28,6 +28,7 @@
28 """Create the instance, and add a records attribute."""28 """Create the instance, and add a records attribute."""
29 logging.Handler.__init__(self, *args, **kwargs)29 logging.Handler.__init__(self, *args, **kwargs)
30 self.records = []30 self.records = []
31 self.debug = False
3132
32 def emit(self, record):33 def emit(self, record):
33 """Just add the record to self.records."""34 """Just add the record to self.records."""
@@ -36,18 +37,26 @@
36 def check(self, level, *msgs):37 def check(self, level, *msgs):
37 """Check that something is logged."""38 """Check that something is logged."""
38 for rec in self.records:39 for rec in self.records:
39 if rec.levelname == level and all(m in rec.message for m in msgs):40 if rec.levelno == level and all(m in rec.message for m in msgs):
40 return True41 return True
42 if self.debug:
43 recorded = [(logging.getLevelName(r.levelno), r.message)
44 for r in self.records]
45 print "Memento messages:", recorded
41 return False46 return False
4247
43 def check_error(self, *msgs):48 def check_error(self, *msgs):
44 """Shortcut for ERROR check."""49 """Shortcut for ERROR check."""
45 return self.check('ERROR', *msgs)50 return self.check(logging.ERROR, *msgs)
51
52 def check_warning(self, *msgs):
53 """Shortcut for WARNING check."""
54 return self.check(logging.WARNING, *msgs)
4655
47 def check_info(self, *msgs):56 def check_info(self, *msgs):
48 """Shortcut for INFO check."""57 """Shortcut for INFO check."""
49 return self.check('INFO', *msgs)58 return self.check(logging.INFO, *msgs)
5059
51 def check_debug(self, *msgs):60 def check_debug(self, *msgs):
52 """Shortcut for DEBUG check."""61 """Shortcut for DEBUG check."""
53 return self.check('DEBUG', *msgs)62 return self.check(logging.DEBUG, *msgs)
5463
=== modified file 'magicicada/tests/test_dbusiface.py'
--- magicicada/tests/test_dbusiface.py 2010-08-23 16:48:07 +0000
+++ magicicada/tests/test_dbusiface.py 2011-01-07 23:06:32 +0000
@@ -27,11 +27,38 @@
27from magicicada import dbusiface27from magicicada import dbusiface
28from magicicada.tests.helpers import MementoHandler28from magicicada.tests.helpers import MementoHandler
2929
30try:
31 # yes, they can be imported! pylint: disable=F0401,E0611
32 from ubuntuone.platform.dbus_interface import DBUS_IFACE_PUBLIC_FILES_NAME
33except ImportError:
34 # support for old structure (pre-Natty)
35 # yes, they can be imported! pylint: disable=F0401,E0611
36 from ubuntuone.syncdaemon.dbus_interface import (
37 DBUS_IFACE_PUBLIC_FILES_NAME,
38 )
39
3040
31# It's ok to access private data in the test suite41# It's ok to access private data in the test suite
32# pylint: disable=W021242# pylint: disable=W0212
3343
3444
45class FakeDBusClient(object):
46 """Fake DBusClient, but with different behaviour at instantiation time."""
47
48 def __init__(self, *args):
49 self.init_args = None
50 self.method_call_args = None
51
52 def call_method(self, *args, **kwargs):
53 """Record the arguments of the method call."""
54 self.method_call_args = args, kwargs
55
56 def __call__(self, *args):
57 """Simulate instantiation."""
58 self.init_args = args
59 return self
60
61
35class FakeSessionBus(object):62class FakeSessionBus(object):
36 """Fake Session Bus."""63 """Fake Session Bus."""
3764
@@ -59,6 +86,9 @@
59 else:86 else:
60 raise self.fake_name_owner87 raise self.fake_name_owner
6188
89 def send_message_with_reply(self, *a, **k):
90 """Fake."""
91
6292
63class CallLoguer(object):93class CallLoguer(object):
64 """Class that logs the methods called."""94 """Class that logs the methods called."""
@@ -104,6 +134,12 @@
104134
105 def setUp(self):135 def setUp(self):
106 """Set up."""136 """Set up."""
137 self.handler = MementoHandler()
138 self.handler.setLevel(logging.DEBUG)
139 logger = logging.getLogger('magicicada.dbusiface')
140 logger.addHandler(self.handler)
141 logger.setLevel(logging.DEBUG)
142
107 dbusiface.SessionBus = FakeSessionBus143 dbusiface.SessionBus = FakeSessionBus
108 dbusiface.SyncDaemonTool = FakeSDTool144 dbusiface.SyncDaemonTool = FakeSDTool
109 self.fsd = FakeSyncDaemon()145 self.fsd = FakeSyncDaemon()
@@ -199,6 +235,17 @@
199 self.assertEqual(self._get_hooked('Shares', 'ShareChanged'),235 self.assertEqual(self._get_hooked('Shares', 'ShareChanged'),
200 self.dbus._on_share_changed)236 self.dbus._on_share_changed)
201237
238 def test_public_files_list(self):
239 """Test public files list callback."""
240 self.assertEqual(self._get_hooked('PublicFiles', 'PublicFilesList'),
241 self.dbus._on_public_files_list)
242
243 def test_public_files_changed(self):
244 """Test public files changed callback."""
245 self.assertEqual(self._get_hooked('PublicFiles',
246 'PublicAccessChanged'),
247 self.dbus._on_public_files_changed)
248
202249
203class TestSimpleCalls(SafeTests):250class TestSimpleCalls(SafeTests):
204 """Tests for some simple calls."""251 """Tests for some simple calls."""
@@ -263,13 +310,13 @@
263 self.dbus._on_name_owner_changed("foo", "bar", "baz")310 self.dbus._on_name_owner_changed("foo", "bar", "baz")
264 self.get_msd_called(None)311 self.get_msd_called(None)
265312
266 def test_name_owner_changed_yes_syncdaemon_TF(self):313 def test_name_owner_changed_yes_syncdaemon_true_false(self):
267 """Test name owner changed callback."""314 """Test name owner changed callback."""
268 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "T", "")315 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "T", "")
269 rcv, = self.get_msd_called("on_sd_name_owner_changed")316 rcv, = self.get_msd_called("on_sd_name_owner_changed")
270 self.assertEqual(rcv, False)317 self.assertEqual(rcv, False)
271318
272 def test_name_owner_changed_yes_syncdaemon_FT(self):319 def test_name_owner_changed_yes_syncdaemon_false_true(self):
273 """Test name owner changed callback."""320 """Test name owner changed callback."""
274 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "", "T")321 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "", "T")
275 rcv, = self.get_msd_called("on_sd_name_owner_changed")322 rcv, = self.get_msd_called("on_sd_name_owner_changed")
@@ -279,11 +326,16 @@
279class TestDataProcessingCQ(SafeTests):326class TestDataProcessingCQ(SafeTests):
280 """Process CQ data before sending it to SyncDaemon."""327 """Process CQ data before sending it to SyncDaemon."""
281328
282 def test_content_queue_changed_signal(self):329 def test_content_queue_changed_signal_withsomething(self):
283 """Test content queue changed signal."""330 """Test content queue changed signal, old version with data."""
284 self.dbus._on_content_queue_changed(None)331 self.dbus._on_content_queue_changed(None)
285 self.get_msd_called("on_sd_content_queue_changed")332 self.get_msd_called("on_sd_content_queue_changed")
286333
334 def test_content_queue_changed_signal_nodata(self):
335 """Test content queue changed signal new version (just the signal)."""
336 self.dbus._on_content_queue_changed()
337 self.get_msd_called("on_sd_content_queue_changed")
338
287 @defer.inlineCallbacks339 @defer.inlineCallbacks
288 def test_nodata(self):340 def test_nodata(self):
289 """Test with no data in the queue."""341 """Test with no data in the queue."""
@@ -371,7 +423,7 @@
371 self.assertEqual(data.node, None)423 self.assertEqual(data.node, None)
372424
373 @defer.inlineCallbacks425 @defer.inlineCallbacks
374 def test_GetPublicFiles_old(self):426 def test_getpublicfiles_old(self):
375 """Test meta with GetPublicFiles."""427 """Test meta with GetPublicFiles."""
376 cmd = 'GetPublicFiles'428 cmd = 'GetPublicFiles'
377 self.fake_sdt_response('waiting_metadata', [cmd])429 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -383,7 +435,7 @@
383 self.assertEqual(data.node, None)435 self.assertEqual(data.node, None)
384436
385 @defer.inlineCallbacks437 @defer.inlineCallbacks
386 def test_AccountInquiry_old(self):438 def test_accountinquiry_old(self):
387 """Test meta with AccountInquiry."""439 """Test meta with AccountInquiry."""
388 cmd = 'AccountInquiry'440 cmd = 'AccountInquiry'
389 self.fake_sdt_response('waiting_metadata', [cmd])441 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -395,7 +447,7 @@
395 self.assertEqual(data.node, None)447 self.assertEqual(data.node, None)
396448
397 @defer.inlineCallbacks449 @defer.inlineCallbacks
398 def test_FreeSpaceInquiry_old(self):450 def test_freespaceinquiry_old(self):
399 """Test meta with FreeSpaceInquiry."""451 """Test meta with FreeSpaceInquiry."""
400 cmd = 'FreeSpaceInquiry'452 cmd = 'FreeSpaceInquiry'
401 self.fake_sdt_response('waiting_metadata', [cmd])453 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -407,7 +459,7 @@
407 self.assertEqual(data.node, None)459 self.assertEqual(data.node, None)
408460
409 @defer.inlineCallbacks461 @defer.inlineCallbacks
410 def test_ListShares_old(self):462 def test_listshares_old(self):
411 """Test meta with ListShares."""463 """Test meta with ListShares."""
412 cmd = 'ListShares'464 cmd = 'ListShares'
413 self.fake_sdt_response('waiting_metadata', [cmd])465 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -419,7 +471,7 @@
419 self.assertEqual(data.node, None)471 self.assertEqual(data.node, None)
420472
421 @defer.inlineCallbacks473 @defer.inlineCallbacks
422 def test_ListVolumes_old(self):474 def test_listvolumes_old(self):
423 """Test meta with ListVolumes."""475 """Test meta with ListVolumes."""
424 cmd = 'ListVolumes'476 cmd = 'ListVolumes'
425 self.fake_sdt_response('waiting_metadata', [cmd])477 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -431,7 +483,7 @@
431 self.assertEqual(data.node, None)483 self.assertEqual(data.node, None)
432484
433 @defer.inlineCallbacks485 @defer.inlineCallbacks
434 def test_Query_old(self):486 def test_query_old(self):
435 """Test meta with Query."""487 """Test meta with Query."""
436 cmd = 'Query'488 cmd = 'Query'
437 self.fake_sdt_response('waiting_metadata', [cmd])489 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -443,7 +495,7 @@
443 self.assertEqual(data.node, None)495 self.assertEqual(data.node, None)
444496
445 @defer.inlineCallbacks497 @defer.inlineCallbacks
446 def test_ListDir_old(self):498 def test_listdir_old(self):
447 """Test meta with ListDir."""499 """Test meta with ListDir."""
448 cmd = 'ListDir(share_id=a, node_id=b, server_hash=c)'500 cmd = 'ListDir(share_id=a, node_id=b, server_hash=c)'
449 self.fake_sdt_response('waiting_metadata', [cmd])501 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -455,7 +507,7 @@
455 self.assertEqual(data.node, 'b')507 self.assertEqual(data.node, 'b')
456508
457 @defer.inlineCallbacks509 @defer.inlineCallbacks
458 def test_MakeDir_old(self):510 def test_makedir_old(self):
459 """Test meta with MakeDir."""511 """Test meta with MakeDir."""
460 cmd = 'MakeDir(share_id=a, parent_id=b, name=c, marker=d)'512 cmd = 'MakeDir(share_id=a, parent_id=b, name=c, marker=d)'
461 self.fake_sdt_response('waiting_metadata', [cmd])513 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -467,7 +519,7 @@
467 self.assertEqual(data.node, None)519 self.assertEqual(data.node, None)
468520
469 @defer.inlineCallbacks521 @defer.inlineCallbacks
470 def test_MakeFile_old(self):522 def test_makefile_old(self):
471 """Test meta with MakeFile."""523 """Test meta with MakeFile."""
472 cmd = 'MakeFile(share_id=a, parent_id=b, name=c, marker=d)'524 cmd = 'MakeFile(share_id=a, parent_id=b, name=c, marker=d)'
473 self.fake_sdt_response('waiting_metadata', [cmd])525 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -479,7 +531,7 @@
479 self.assertEqual(data.node, None)531 self.assertEqual(data.node, None)
480532
481 @defer.inlineCallbacks533 @defer.inlineCallbacks
482 def test_Unlink_old(self):534 def test_unlink_old(self):
483 """Test meta with Unlink."""535 """Test meta with Unlink."""
484 cmd = 'Unlink(share_id=a, node_id=b, server_hash=c)'536 cmd = 'Unlink(share_id=a, node_id=b, server_hash=c)'
485 self.fake_sdt_response('waiting_metadata', [cmd])537 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -491,7 +543,7 @@
491 self.assertEqual(data.node, 'b')543 self.assertEqual(data.node, 'b')
492544
493 @defer.inlineCallbacks545 @defer.inlineCallbacks
494 def test_Move_old(self):546 def test_move_old(self):
495 """Test meta with Move."""547 """Test meta with Move."""
496 cmd = 'Move(share_id=a, node_id=b, old_parent_id=c, '\548 cmd = 'Move(share_id=a, node_id=b, old_parent_id=c, '\
497 'new_parent_id=d, new_name=e)'549 'new_parent_id=d, new_name=e)'
@@ -504,7 +556,7 @@
504 self.assertEqual(data.node, 'b')556 self.assertEqual(data.node, 'b')
505557
506 @defer.inlineCallbacks558 @defer.inlineCallbacks
507 def test_ChangePublicAccess_old(self):559 def test_changepublicaccess_old(self):
508 """Test meta with ChangePublicAccess."""560 """Test meta with ChangePublicAccess."""
509 cmd = 'ChangePublicAccess'561 cmd = 'ChangePublicAccess'
510 self.fake_sdt_response('waiting_metadata', [cmd])562 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -516,7 +568,7 @@
516 self.assertEqual(data.node, None)568 self.assertEqual(data.node, None)
517569
518 @defer.inlineCallbacks570 @defer.inlineCallbacks
519 def test_AnswerShare_old(self):571 def test_answershare_old(self):
520 """Test meta with AnswerShare."""572 """Test meta with AnswerShare."""
521 cmd = 'AnswerShare'573 cmd = 'AnswerShare'
522 self.fake_sdt_response('waiting_metadata', [cmd])574 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -528,7 +580,7 @@
528 self.assertEqual(data.node, None)580 self.assertEqual(data.node, None)
529581
530 @defer.inlineCallbacks582 @defer.inlineCallbacks
531 def test_GetPublicFiles_dict(self):583 def test_getpublicfiles_dict(self):
532 """Test meta with GetPublicFiles."""584 """Test meta with GetPublicFiles."""
533 cmd = ('GetPublicFiles', {})585 cmd = ('GetPublicFiles', {})
534 self.fake_sdt_response('waiting_metadata', [cmd])586 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -540,7 +592,7 @@
540 self.assertEqual(data.node, None)592 self.assertEqual(data.node, None)
541593
542 @defer.inlineCallbacks594 @defer.inlineCallbacks
543 def test_AccountInquiry_dict(self):595 def test_accountinquiry_dict(self):
544 """Test meta with AccountInquiry."""596 """Test meta with AccountInquiry."""
545 cmd = ('AccountInquiry', {})597 cmd = ('AccountInquiry', {})
546 self.fake_sdt_response('waiting_metadata', [cmd])598 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -552,7 +604,7 @@
552 self.assertEqual(data.node, None)604 self.assertEqual(data.node, None)
553605
554 @defer.inlineCallbacks606 @defer.inlineCallbacks
555 def test_FreeSpaceInquiry_dict(self):607 def test_freespaceinquiry_dict(self):
556 """Test meta with FreeSpaceInquiry."""608 """Test meta with FreeSpaceInquiry."""
557 cmd = ('FreeSpaceInquiry', {})609 cmd = ('FreeSpaceInquiry', {})
558 self.fake_sdt_response('waiting_metadata', [cmd])610 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -564,7 +616,7 @@
564 self.assertEqual(data.node, None)616 self.assertEqual(data.node, None)
565617
566 @defer.inlineCallbacks618 @defer.inlineCallbacks
567 def test_ListShares_dict(self):619 def test_listshares_dict(self):
568 """Test meta with ListShares."""620 """Test meta with ListShares."""
569 cmd = ('ListShares', {})621 cmd = ('ListShares', {})
570 self.fake_sdt_response('waiting_metadata', [cmd])622 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -576,7 +628,7 @@
576 self.assertEqual(data.node, None)628 self.assertEqual(data.node, None)
577629
578 @defer.inlineCallbacks630 @defer.inlineCallbacks
579 def test_ListVolumes_dict(self):631 def test_listvolumes_dict(self):
580 """Test meta with ListVolumes."""632 """Test meta with ListVolumes."""
581 cmd = ('ListVolumes', {})633 cmd = ('ListVolumes', {})
582 self.fake_sdt_response('waiting_metadata', [cmd])634 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -588,7 +640,7 @@
588 self.assertEqual(data.node, None)640 self.assertEqual(data.node, None)
589641
590 @defer.inlineCallbacks642 @defer.inlineCallbacks
591 def test_Query_dict(self):643 def test_query_dict(self):
592 """Test meta with Query."""644 """Test meta with Query."""
593 cmd = ('Query', {})645 cmd = ('Query', {})
594 self.fake_sdt_response('waiting_metadata', [cmd])646 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -600,7 +652,7 @@
600 self.assertEqual(data.node, None)652 self.assertEqual(data.node, None)
601653
602 @defer.inlineCallbacks654 @defer.inlineCallbacks
603 def test_ListDir_dict(self):655 def test_listdir_dict(self):
604 """Test meta with ListDir."""656 """Test meta with ListDir."""
605 cmd = ('ListDir', dict(share_id='a', node_id='b',657 cmd = ('ListDir', dict(share_id='a', node_id='b',
606 server_hash='c', path='d'))658 server_hash='c', path='d'))
@@ -613,7 +665,7 @@
613 self.assertEqual(data.node, 'b')665 self.assertEqual(data.node, 'b')
614666
615 @defer.inlineCallbacks667 @defer.inlineCallbacks
616 def test_MakeDir_dict(self):668 def test_makedir_dict(self):
617 """Test meta with MakeDir."""669 """Test meta with MakeDir."""
618 cmd = ('MakeDir', dict(share_id='a', parent_id='b',670 cmd = ('MakeDir', dict(share_id='a', parent_id='b',
619 name='c', marker='d'))671 name='c', marker='d'))
@@ -626,7 +678,7 @@
626 self.assertEqual(data.node, None)678 self.assertEqual(data.node, None)
627679
628 @defer.inlineCallbacks680 @defer.inlineCallbacks
629 def test_MakeFile_dict(self):681 def test_makefile_dict(self):
630 """Test meta with MakeFile."""682 """Test meta with MakeFile."""
631 cmd = ('MakeFile', dict(share_id='a', parent_id='b',683 cmd = ('MakeFile', dict(share_id='a', parent_id='b',
632 name='c', marker='d'))684 name='c', marker='d'))
@@ -639,7 +691,7 @@
639 self.assertEqual(data.node, None)691 self.assertEqual(data.node, None)
640692
641 @defer.inlineCallbacks693 @defer.inlineCallbacks
642 def test_Unlink_dict(self):694 def test_unlink_dict(self):
643 """Test meta with Unlink."""695 """Test meta with Unlink."""
644 cmd = ('Unlink', dict(share_id='a', node_id='b',696 cmd = ('Unlink', dict(share_id='a', node_id='b',
645 server_hash='c', path='d'))697 server_hash='c', path='d'))
@@ -652,7 +704,7 @@
652 self.assertEqual(data.node, 'b')704 self.assertEqual(data.node, 'b')
653705
654 @defer.inlineCallbacks706 @defer.inlineCallbacks
655 def test_Move_dict(self):707 def test_move_dict(self):
656 """Test meta with Move."""708 """Test meta with Move."""
657 cmd = ('Move', dict(share_id='a', node_id='b', old_parent_id='c',709 cmd = ('Move', dict(share_id='a', node_id='b', old_parent_id='c',
658 new_parent_id='d', new_name='e', path='f'))710 new_parent_id='d', new_name='e', path='f'))
@@ -665,7 +717,7 @@
665 self.assertEqual(data.node, 'b')717 self.assertEqual(data.node, 'b')
666718
667 @defer.inlineCallbacks719 @defer.inlineCallbacks
668 def test_ChangePublicAccess_dict(self):720 def test_changepublicaccess_dict(self):
669 """Test meta with ChangePublicAccess."""721 """Test meta with ChangePublicAccess."""
670 cmd = ('ChangePublicAccess', {})722 cmd = ('ChangePublicAccess', {})
671 self.fake_sdt_response('waiting_metadata', [cmd])723 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -677,7 +729,7 @@
677 self.assertEqual(data.node, None)729 self.assertEqual(data.node, None)
678730
679 @defer.inlineCallbacks731 @defer.inlineCallbacks
680 def test_AnswerShare_dict(self):732 def test_answershare_dict(self):
681 """Test meta with AnswerShare."""733 """Test meta with AnswerShare."""
682 cmd = ('AnswerShare', {})734 cmd = ('AnswerShare', {})
683 self.fake_sdt_response('waiting_metadata', [cmd])735 self.fake_sdt_response('waiting_metadata', [cmd])
@@ -688,6 +740,18 @@
688 self.assertEqual(data.share, None)740 self.assertEqual(data.share, None)
689 self.assertEqual(data.node, None)741 self.assertEqual(data.node, None)
690742
743 @defer.inlineCallbacks
744 def test_getdelta(self):
745 """Test meta with AnswerShare."""
746 cmd = ('GetDelta', {})
747 self.fake_sdt_response('waiting_metadata', [cmd])
748 rcv = yield self.dbus.get_meta_queue()
749 data = rcv[0]
750 self.assertEqual(data.operation, 'GetDelta')
751 self.assertEqual(data.path, None)
752 self.assertEqual(data.share, None)
753 self.assertEqual(data.node, None)
754
691755
692class TestDataProcessingFolders(SafeTests):756class TestDataProcessingFolders(SafeTests):
693 """Process Folders data before sending it to SyncDaemon."""757 """Process Folders data before sending it to SyncDaemon."""
@@ -910,6 +974,138 @@
910 self.assertEqual(share.volume_id, 'vol')974 self.assertEqual(share.volume_id, 'vol')
911975
912976
977class TestPublicFiles(SafeTests):
978 """PublicFiles data handling and related services."""
979
980 def test_public_files_changed_yes(self):
981 """Call the changed callback."""
982 d = dict(share_id='share', node_id='node', is_public='True',
983 public_url='url', path='path')
984 self.dbus._on_public_files_changed(d)
985 pf, is_public = self.get_msd_called("on_sd_public_files_changed")
986 self.assertEqual(pf.volume, 'share')
987 self.assertEqual(pf.node, 'node')
988 self.assertEqual(pf.public_url, 'url')
989 self.assertEqual(pf.path, 'path')
990 self.assertEqual(is_public, True)
991
992 def test_public_files_changed_no(self):
993 """Call the changed callback."""
994 d = dict(share_id='share', node_id='node', is_public='',
995 public_url='url', path='path')
996 self.dbus._on_public_files_changed(d)
997 pf, is_public = self.get_msd_called("on_sd_public_files_changed")
998 self.assertEqual(pf.volume, 'share')
999 self.assertEqual(pf.node, 'node')
1000 self.assertEqual(pf.public_url, 'url')
1001 self.assertEqual(pf.path, 'path')
1002 self.assertEqual(is_public, False)
1003
1004 def test_public_files_empty(self):
1005 """Call the callback without info."""
1006 self.dbus._on_public_files_list([])
1007 res, = self.get_msd_called("on_sd_public_files_list")
1008 self.assertEqual(res, [])
1009
1010 def test_public_files_one(self):
1011 """Call the callback with a public file."""
1012 d = dict(volume_id='volume', node_id='node',
1013 public_url='url', path='path')
1014 self.dbus._on_public_files_list([d])
1015 res, = self.get_msd_called("on_sd_public_files_list")
1016 self.assertEqual(len(res), 1)
1017 pf = res[0]
1018 self.assertEqual(pf.volume, 'volume')
1019 self.assertEqual(pf.node, 'node')
1020 self.assertEqual(pf.public_url, 'url')
1021 self.assertEqual(pf.path, 'path')
1022
1023 def test_public_files_more(self):
1024 """Call the callback with a mixed content."""
1025 d1 = dict(volume_id='volume1', node_id='node1',
1026 public_url='url1', path='path1')
1027 d2 = dict(volume_id='volume2', node_id='node2',
1028 public_url='url2', path='path2')
1029 d3 = dict(volume_id='volume3', node_id='node3',
1030 public_url='url3', path='path3')
1031 self.dbus._on_public_files_list([d1, d2, d3])
1032 res, = self.get_msd_called("on_sd_public_files_list")
1033 self.assertEqual(len(res), 3)
1034 pf = res[0]
1035 self.assertEqual(pf.volume, 'volume1')
1036 self.assertEqual(pf.node, 'node1')
1037 self.assertEqual(pf.public_url, 'url1')
1038 self.assertEqual(pf.path, 'path1')
1039 pf = res[1]
1040 self.assertEqual(pf.volume, 'volume2')
1041 self.assertEqual(pf.node, 'node2')
1042 self.assertEqual(pf.public_url, 'url2')
1043 self.assertEqual(pf.path, 'path2')
1044 pf = res[2]
1045 self.assertEqual(pf.volume, 'volume3')
1046 self.assertEqual(pf.node, 'node3')
1047 self.assertEqual(pf.public_url, 'url3')
1048 self.assertEqual(pf.path, 'path3')
1049
1050 def test_getpublicfiles_deferred(self):
1051 """The method should return a deferred and store it."""
1052 d = self.dbus.get_public_files()
1053 self.assertTrue(isinstance(d, defer.Deferred))
1054 self.assertIdentical(d, self.dbus._public_files_deferred)
1055
1056 @defer.inlineCallbacks
1057 def test_public_files_when_asked(self):
1058 """If we receive the signal and data was asked, change behaviour."""
1059 # set up stuff
1060 d = defer.Deferred()
1061 self.dbus._public_files_deferred = d
1062
1063 # call the signal
1064 self.dbus._on_public_files_list([])
1065
1066 # check that the deferred was callbacked, and not the normal SD method
1067 result = yield d
1068 self.assertEqual(result, [])
1069 self.assertTrue(self.fsd._called_method[0] is None)
1070
1071 def test_getpublicfiles_asktoclient_ok(self):
1072 """The info was requested to the DBusClient, and was ok."""
1073 # inject a different DBusClient
1074 fake_dbusclient = FakeDBusClient()
1075 self.patch(dbusiface, 'DBusClient', fake_dbusclient)
1076
1077 # call, and check init and method args
1078 self.dbus.get_public_files()
1079 self.assertEqual(fake_dbusclient.init_args, (self.dbus._bus,
1080 '/publicfiles', DBUS_IFACE_PUBLIC_FILES_NAME))
1081 (method_name,), kwargs = fake_dbusclient.method_call_args
1082 done_cback = kwargs['reply_handler']
1083 self.assertEqual(method_name, 'get_public_files')
1084
1085 # trigger done callback and check logging
1086 done_cback(None)
1087 self.assertTrue(self.handler.check_debug("Public files asked ok."))
1088
1089 def test_getpublicfiles_asktoclient_error(self):
1090 """The info was requested to the DBusClient, and was not ok."""
1091 # inject a different DBusClient
1092 fake_dbusclient = FakeDBusClient()
1093 self.patch(dbusiface, 'DBusClient', fake_dbusclient)
1094
1095 # call, and check init and method args
1096 self.dbus.get_public_files()
1097 self.assertEqual(fake_dbusclient.init_args, (self.dbus._bus,
1098 '/publicfiles', DBUS_IFACE_PUBLIC_FILES_NAME))
1099 (method_name,), kwargs = fake_dbusclient.method_call_args
1100 error_cback = kwargs['error_handler']
1101 self.assertEqual(method_name, 'get_public_files')
1102
1103 # trigger error callback and check logging
1104 error_cback('foo')
1105 self.assertTrue(self.handler.check_error(
1106 "Public files asked with error: foo"))
1107
1108
913class TestToolActions(SafeTests):1109class TestToolActions(SafeTests):
914 """Actions against SD.tools.1110 """Actions against SD.tools.
9151111
@@ -944,8 +1140,10 @@
944 def setUp(self):1140 def setUp(self):
945 """Set up."""1141 """Set up."""
946 self.handler = MementoHandler()1142 self.handler = MementoHandler()
947 logging.getLogger('magicicada.dbusiface').addHandler(self.handler)
948 self.handler.setLevel(logging.DEBUG)1143 self.handler.setLevel(logging.DEBUG)
1144 logger = logging.getLogger('magicicada.dbusiface')
1145 logger.addHandler(self.handler)
1146 logger.setLevel(logging.DEBUG)
949 SafeTests.setUp(self)1147 SafeTests.setUp(self)
9501148
951 def test_instancing(self):1149 def test_instancing(self):
@@ -1052,13 +1250,13 @@
1052 self.assertTrue(self.handler.check_info("Received Name Owner changed"))1250 self.assertTrue(self.handler.check_info("Received Name Owner changed"))
1053 self.assertTrue(self.handler.check_debug("Name Owner data: u'' u'T'"))1251 self.assertTrue(self.handler.check_debug("Name Owner data: u'' u'T'"))
10541252
1055 def test_name_owner_changed_yes_syncdaemon_TF(self):1253 def test_name_owner_changed_yes_syncdaemon_true_false(self):
1056 """Test name owner changed callback, SD value bad."""1254 """Test name owner changed callback, SD value bad."""
1057 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "F", "T")1255 self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "F", "T")
1058 self.assertTrue(self.handler.check_info("Received Name Owner changed"))1256 self.assertTrue(self.handler.check_info("Received Name Owner changed"))
1059 self.assertTrue(self.handler.check_debug("Name Owner data: u'F' u'T'"))1257 self.assertTrue(self.handler.check_debug("Name Owner data: u'F' u'T'"))
1060 self.assertTrue(self.handler.check("ERROR",1258 msg = "Name Owner invalid data: Same bool in old and new!"
1061 "Name Owner invalid data: Same bool in old and new!"))1259 self.assertTrue(self.handler.check_error(msg))
10621260
1063 def test_folder_created_changed(self):1261 def test_folder_created_changed(self):
1064 """Test folder created changed callback."""1262 """Test folder created changed callback."""
@@ -1091,12 +1289,32 @@
1091 self.dbus._on_share_deleted("foo")1289 self.dbus._on_share_deleted("foo")
1092 self.assertTrue(self.handler.check_info("Received Share deleted"))1290 self.assertTrue(self.handler.check_info("Received Share deleted"))
10931291
1094 def test_share__changed(self):1292 def test_share_changed(self):
1095 """Test share changed callback."""1293 """Test share changed callback."""
1096 self.dbus._on_share_changed("foo")1294 self.dbus._on_share_changed("foo")
1097 self.assertTrue(self.handler.check_info("Received Share changed"))1295 self.assertTrue(self.handler.check_info("Received Share changed"))
10981296
1099 @defer.inlineCallbacks1297 @defer.inlineCallbacks
1298 def test_public_files_list(self):
1299 """Test public files list callback."""
1300 d = dict(volume_id='volume', node_id='node',
1301 public_url='url', path='path')
1302 yield self.dbus._on_public_files_list([d])
1303 self.assertTrue(self.handler.check_info(
1304 "Received Public Files list (1)"))
1305 self.assertTrue(self.handler.check_debug(
1306 " Public Files data: %s" % d))
1307
1308 @defer.inlineCallbacks
1309 def test_public_files_changed(self):
1310 """Test public files changed callback."""
1311 d = dict(share_id='volume', node_id='node', is_public='',
1312 public_url='url', path='path')
1313 yield self.dbus._on_public_files_changed(d)
1314 self.assertTrue(self.handler.check_debug(
1315 "Received Public Files changed: %s" % d))
1316
1317 @defer.inlineCallbacks
1100 def test_content_queue_processing(self):1318 def test_content_queue_processing(self):
1101 """Test with one item in the queue."""1319 """Test with one item in the queue."""
1102 c = dict(operation='oper', path='path', share='share', node='node')1320 c = dict(operation='oper', path='path', share='share', node='node')
@@ -1270,3 +1488,52 @@
1270 d.addCallbacks(lambda _: deferred.errback(Exception()),1488 d.addCallbacks(lambda _: deferred.errback(Exception()),
1271 lambda _: deferred.callback(True))1489 lambda _: deferred.callback(True))
1272 return deferred1490 return deferred
1491
1492
1493class TestHandlingShares(SafeTests):
1494 """Handle shares."""
1495
1496 @defer.inlineCallbacks
1497 def test_accept_share_ok(self):
1498 """Accepting share finishes ok."""
1499 d = dict(volume_id='foo', answer='bar')
1500 self.fake_sdt_response('accept_share', d)
1501 yield self.dbus.accept_share('foo')
1502 self.assertTrue(self.handler.check_debug(
1503 "Accept share foo started", "foo"))
1504 self.assertTrue(self.handler.check_debug(
1505 "Accept share foo finished", str(d)))
1506
1507 @defer.inlineCallbacks
1508 def test_accept_share_bad(self):
1509 """Accepting share finishes bad."""
1510 d = dict(volume_id='foo', answer='bar', error='baz')
1511 self.fake_sdt_response('accept_share', d)
1512 try:
1513 yield self.dbus.accept_share('foo')
1514 except dbusiface.ShareOperationError, e:
1515 self.assertEqual(e.share_id, 'foo')
1516 self.assertEqual(e.error, 'baz')
1517 else:
1518 raise Exception("Test should have raised an exception")
1519 self.assertTrue(self.handler.check_debug(
1520 "Accept share foo started", "foo"))
1521 self.assertTrue(self.handler.check_debug(
1522 "Accept share foo finished", str(d)))
1523
1524 @defer.inlineCallbacks
1525 def test_accept_share_ugly_error(self):
1526 """Accepting share went really bad."""
1527 e = dbus.exceptions.DBusException('ugly!')
1528 self.fake_sdt_response('accept_share', e)
1529 try:
1530 yield self.dbus.accept_share('foo')
1531 except dbusiface.ShareOperationError, e:
1532 self.assertEqual(e.share_id, 'foo')
1533 self.assertEqual(e.error, "ugly!")
1534 else:
1535 raise Exception("Test should have raised an exception")
1536 self.assertTrue(self.handler.check_debug(
1537 "Accept share foo started", "foo"))
1538 self.assertTrue(self.handler.check_debug(
1539 "Accept share foo crashed", "ugly!"))
12731540
=== modified file 'magicicada/tests/test_logger.py'
--- magicicada/tests/test_logger.py 2010-08-18 21:17:20 +0000
+++ magicicada/tests/test_logger.py 2011-01-07 23:06:32 +0000
@@ -23,7 +23,9 @@
23import sys23import sys
24import unittest24import unittest
2525
26from magicicada.logger import exception_handler26from twisted.python import log, failure
27
28from magicicada.logger import exception_handler, deferror_handler
27from magicicada.tests.helpers import MementoHandler29from magicicada.tests.helpers import MementoHandler
2830
2931
@@ -81,3 +83,35 @@
81 shown = fh.getvalue()83 shown = fh.getvalue()
82 self.assertTrue("Traceback" in shown)84 self.assertTrue("Traceback" in shown)
83 self.assertTrue("ZeroDivisionError" in shown)85 self.assertTrue("ZeroDivisionError" in shown)
86
87
88class DeferredTests(unittest.TestCase):
89 """Error logging when it happened inside deferreds."""
90
91 def test_observer_added(self):
92 """Test that the observer was added to Twisted logging."""
93 self.assertTrue(deferror_handler in log.theLogPublisher.observers)
94
95 def test_noerror(self):
96 """No error, no action."""
97 handler = MementoHandler()
98 handler.setLevel(logging.DEBUG)
99 deferror_handler(dict(isError=False, message=''))
100 self.assertFalse(handler.check_error("error"))
101
102 def test_message(self):
103 """Just a message."""
104 handler = MementoHandler()
105 handler.setLevel(logging.DEBUG)
106 deferror_handler(dict(isError=True, message="foobar"))
107 self.assertFalse(handler.check_error("Unhandled error in deferred",
108 "foobar"))
109
110 def test_failure(self):
111 """Received a full failure."""
112 handler = MementoHandler()
113 handler.setLevel(logging.DEBUG)
114 f = failure.Failure(ValueError('foobar'))
115 deferror_handler(dict(isError=True, failure=f, message=''))
116 self.assertFalse(handler.check_error("Unhandled error in deferred",
117 "ValueError", "foobar"))
84118
=== modified file 'magicicada/tests/test_magicicada.py'
--- magicicada/tests/test_magicicada.py 2010-08-21 16:58:46 +0000
+++ magicicada/tests/test_magicicada.py 2011-01-07 23:06:32 +0000
@@ -32,9 +32,9 @@
32from twisted.trial.unittest import TestCase32from twisted.trial.unittest import TestCase
3333
34from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, \34from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, \
35 UBUNTU_ONE_ROOT, syncdaemon35 NOT_SYNCHED_PATH, UBUNTU_ONE_ROOT, syncdaemon
36from magicicada.dbusiface import QueueData, FolderData, ShareData, \36from magicicada.dbusiface import QueueData, FolderData, ShareData, \
37 NOT_SYNCHED_PATH37 PublicFilesData
38from magicicada.helpers import NO_OP, humanize_bytes, get_data_file38from magicicada.helpers import NO_OP, humanize_bytes, get_data_file
39from magicicada.tests.helpers import MementoHandler39from magicicada.tests.helpers import MementoHandler
4040
@@ -52,6 +52,25 @@
52# pylint: disable=E110352# pylint: disable=E1103
5353
5454
55SAMPLE_PUBLIC_FILES = {
56 u'4d0ffa01-5375-4e67-bc47-d993b721d8af':
57 PublicFilesData(volume=u'',
58 node=u'4d0ffa01-5375-4e67-bc47-d993b721d8af',
59 path=u'/home/user/Ubuntu One/test.png',
60 public_url=u'http://ubuntuone.com/p/CUG/'),
61 u'1be1ea29-426e-41b3-8853-5cce03b0c511':
62 PublicFilesData(volume=u'',
63 node=u'1be1ea29-426e-41b3-8853-5cce03b0c511',
64 path=u'/home/user/Ubuntu One/public/text.txt',
65 public_url=u'http://ubuntuone.com/p/6TX/'),
66 u'8dba0484-86e9-4505-8fba-db3db7cc551d':
67 PublicFilesData(volume=u'f5cc6b0d-85a0-4046-a72f-bd42c41538cb',
68 node=u'8dba0484-86e9-4505-8fba-db3db7cc551d',
69 path=u'/home/user/udf0/yadda.py',
70 public_url=u'http://ubuntuone.com/p/U4R/'),
71}
72
73
55def process_gtk_pendings():74def process_gtk_pendings():
56 """Process all gtk pending events."""75 """Process all gtk pending events."""
57 while gtk.events_pending():76 while gtk.events_pending():
@@ -101,8 +120,13 @@
101 self.content_queue_changed_callback = NO_OP120 self.content_queue_changed_callback = NO_OP
102 self.meta_queue_changed_callback = NO_OP121 self.meta_queue_changed_callback = NO_OP
103 self.on_metadata_ready_callback = None # mandatory122 self.on_metadata_ready_callback = None # mandatory
123 self.on_initial_data_ready_callback = NO_OP
124 self.on_initial_online_data_ready_callback = NO_OP
104 self.shutdown = NO_OP125 self.shutdown = NO_OP
105126
127 # Lambda may not be necessary
128 # pylint: disable=W0108
129
106 self.start = lambda: setattr(self.current_state, 'is_started', True)130 self.start = lambda: setattr(self.current_state, 'is_started', True)
107 self.quit = lambda: setattr(self.current_state, 'is_started', False)131 self.quit = lambda: setattr(self.current_state, 'is_started', False)
108 self.connect = lambda: setattr(self.current_state,132 self.connect = lambda: setattr(self.current_state,
@@ -116,6 +140,7 @@
116 """UI test cases for Magicicada UI."""140 """UI test cases for Magicicada UI."""
117141
118 TEST_FILE = get_data_file('tests', 'metadata-test.txt')142 TEST_FILE = get_data_file('tests', 'metadata-test.txt')
143 data_type = mapping = store = view = None
119144
120 def setUp(self):145 def setUp(self):
121 """Init."""146 """Init."""
@@ -155,30 +180,6 @@
155 else:180 else:
156 return TestCase.__getattribute__(self, name)181 return TestCase.__getattribute__(self, name)
157182
158 if self._failed_test:
159 # no, I'm not raising a bool. pylint: disable=E0702
160 raise self._failed_test
161
162 def __getattribute__(self, name):
163 """Overwrite the assert methods with safer ones.
164
165 This way if a test called by gobject in the future fails, it
166 makes the whole test suite fail.
167 """
168 if name.startswith('assert') and hasattr(TestCase, name):
169
170 def proxy(*args, **kwargs):
171 """Function that will call the real assert."""
172 real_assert = getattr(TestCase, name)
173 try:
174 real_assert(self, *args, **kwargs)
175 except Exception, e:
176 self._failed_test = e
177 raise
178 return proxy
179 else:
180 return TestCase.__getattribute__(self, name)
181
182 def do_start(self):183 def do_start(self):
183 """Simulate that start fully happened."""184 """Simulate that start fully happened."""
184 self.ui.on_start_clicked(self.ui.start)185 self.ui.on_start_clicked(self.ui.start)
@@ -190,41 +191,56 @@
190 self.ui.on_connect_clicked(self.ui.connect)191 self.ui.on_connect_clicked(self.ui.connect)
191 self.ui.on_connected()192 self.ui.on_connected()
192193
193 def build_some_data(self, data_type, limit=5):194 def build_some_data(self, limit=5):
194 """Build some data using named_tuple 'data_type'."""195 """Build some data using named_tuple 'data_type'."""
195 attrs = data_type._fields196 assert self.data_type is not None, 'class muct provide a data_type'
197
198 attrs = self.data_type._fields
196 result = []199 result = []
197 for i in xrange(limit):200 for i in xrange(limit):
198 kwargs = dict([(attr, '%s %i' % (attr, i)) for attr in attrs])201 kwargs = dict([(attr, '%s %i' % (attr, i)) for attr in attrs])
199 result.append(data_type(**kwargs))202 # self.data_type is not callable, pylint: disable=E1102
203 result.append(self.data_type(**kwargs))
200 return result204 return result
201205
202 def assert_store_correct(self, store, items, mapping, markup=None):206 def assert_store_correct(self, items, markup=None):
203 """Test that 'store' has 'items' as content."""207 """Test that 'self.store' has 'items' as content."""
208 assert self.mapping is not None, 'class must provide a mapping'
209 assert self.store is not None, 'class must provide a store'
210
204 msg = 'amount of rows for %s must be %s (got %s).'211 msg = 'amount of rows for %s must be %s (got %s).'
205 self.assertEqual(len(store), len(items),212 self.assertEqual(len(self.store), len(items),
206 msg % (store, len(items), len(store)))213 msg % (self.store, len(items), len(self.store)))
207214
208 # assert rows content equal to items content215 # assert rows content equal to items content
209 tree_iter = store.get_iter_root()216 tree_iter = self.store.get_iter_root()
210 tmp = list(reversed(items))217 tmp = list(reversed(items))
211 do_markup = markup is not None218 do_markup = markup is not None
212 msg = "column %i ('%s') must be '%s' (got '%s' instead)"219 msg = "column %i ('%s') must be '%s' (got '%s' instead)"
213 while tree_iter is not None:220 while tree_iter is not None:
214 head = tmp.pop()221 head = tmp.pop()
215 for i, field in mapping:222 for i, field in self.mapping:
216 actual, = store.get(tree_iter, i)223 actual, = self.store.get(tree_iter, i)
217 expected = getattr(head, field)224 expected = getattr(head, field)
218 if store.get_column_type(i).name == 'gboolean':225 if self.store.get_column_type(i).name == 'gboolean':
219 expected = bool(expected)226 expected = bool(expected)
220 elif do_markup:227 elif do_markup:
221 expected = markup(expected)228 expected = markup(expected)
222 self.assertEqual(expected, actual,229 self.assertEqual(expected, actual,
223 msg % (i, field, expected, actual))230 msg % (i, field, expected, actual))
224231
225 tree_iter = store.iter_next(tree_iter)232 tree_iter = self.store.iter_next(tree_iter)
226 do_markup = False # only for first row233 do_markup = False # only for first row
227234
235 def debug_store(self):
236 """Print the whole content of a store."""
237 store_iter = self.store.get_iter_root()
238 columns = self.store.get_n_columns()
239 print '\nShowing contents of store:', self.store
240 while store_iter is not None:
241 print self.store.get(store_iter, *range(columns))
242 store_iter = self.store.iter_next(store_iter)
243
228 def assert_indicator_disabled(self, indicator):244 def assert_indicator_disabled(self, indicator):
229 """Test that 'indicator' is not sensitive."""245 """Test that 'indicator' is not sensitive."""
230 self.assertFalse(indicator.is_sensitive(),246 self.assertFalse(indicator.is_sensitive(),
@@ -283,7 +299,41 @@
283 msg = '%s must not skip taskbar.'299 msg = '%s must not skip taskbar.'
284 self.assertFalse(dialog.get_skip_taskbar_hint(), msg % dialog_name)300 self.assertFalse(dialog.get_skip_taskbar_hint(), msg % dialog_name)
285301
286 self.assertEqual(dialog.get_icon(), self.ui._icon)302 def assert_sort_order_correct(self, column, idx, expected_order):
303 """Check that sort order is correctly set for 'self.store'."""
304 assert self.store is not None, 'class must provide a store'
305
306 msg0 = 'Store sort id must be %r (got %r instead).'
307 msg1 = 'Store sort order must be %r (got %r instead).'
308 msg3 = 'Column sort order must be %r (got %r instead).'
309
310 actual_id, actual_order = self.store.get_sort_column_id()
311
312 # store sort column id and order
313 self.assertEqual(idx, actual_id, msg0 % (idx, actual_id))
314 self.assertEqual(expected_order, actual_order,
315 msg1 % (expected_order, actual_order))
316
317 # column sort order
318 actual_order = column.get_sort_order()
319 self.assertEqual(expected_order, actual_order,
320 msg3 % (expected_order, actual_order))
321
322 def assert_sort_indicator_correct(self, column):
323 """Check that sort indicator is correctly set."""
324 assert self.view is not None, 'class must provide a view'
325
326 msg = 'Column %s must have sort indicator %s.'
327 colname = column.get_name()
328 # column sort indicator
329 self.assertTrue(column.get_sort_indicator(), msg % (colname, 'on'))
330
331 # all the other columns must not have the sort indicator on
332 for other_column in self.view.get_columns():
333 if other_column.get_name() == colname:
334 continue
335 self.assertFalse(other_column.get_sort_indicator(),
336 msg % (other_column.get_name(), 'off'))
287337
288338
289class MagicicadaUIBasicTestCase(MagicicadaUITestCase):339class MagicicadaUIBasicTestCase(MagicicadaUITestCase):
@@ -305,10 +355,16 @@
305 """UI can be created and main_window is visible."""355 """UI can be created and main_window is visible."""
306 self.assertTrue(self.ui.widget_is_visible(self.ui.main_window))356 self.assertTrue(self.ui.widget_is_visible(self.ui.main_window))
307357
308 def test_windows_have_correct_icon(self):358 def test_main_window_have_correct_icon_list(self):
309 """Every window has the icon set."""359 """Every window has the icon set."""
310 for w in self.ui.windows:360 self.assertEqual(len(self.ui.main_window.get_icon_list()),
311 self.assertEqual(w.get_icon(), self.ui._icon)361 len(self.ui._icons.values()))
362
363 def test_every_window_has_correct_list(self):
364 """The default icon list is set."""
365 self.patch(gtk, 'window_set_default_icon_list', self._set_called)
366 MagicicadaUI(syncdaemon_class=FakedSyncdaemon)
367 self.assertEqual(len(self._called[0]), len(self.ui._icons.values()))
312368
313 def test_start_connect_are_visible(self):369 def test_start_connect_are_visible(self):
314 """Start and Connect buttons are visible."""370 """Start and Connect buttons are visible."""
@@ -456,6 +512,8 @@
456 """Abstratc UI test cases for queue tree views."""512 """Abstratc UI test cases for queue tree views."""
457513
458 name = None514 name = None
515 data_type = QueueData
516 mapping = enumerate(['operation', 'path', 'share', 'node'])
459517
460 def setUp(self):518 def setUp(self):
461 """Init."""519 """Init."""
@@ -468,15 +526,8 @@
468 'on_%s_queue_changed' % self.name)526 'on_%s_queue_changed' % self.name)
469 self.set_sd_queue = lambda q: \527 self.set_sd_queue = lambda q: \
470 setattr(self.ui.sd, '%s_queue' % self.name, q)528 setattr(self.ui.sd, '%s_queue' % self.name, q)
471 self.queue_store = getattr(self.ui, '%sq_store' % self.name)529 self.store = getattr(self.ui, '%sq_store' % self.name)
472 self.queue_view = getattr(self.ui, '%sq_view' % self.name)530 self.view = getattr(self.ui, '%sq_view' % self.name)
473
474 def build_some_data(self, limit=5):
475 """Build some data to act as queue data."""
476 kwargs = dict(data_type=QueueData, limit=limit)
477 res = super(_MagicicadaUIQueueTestCase, self).build_some_data(**kwargs)
478 # operation path share node
479 return res
480531
481 def expected_markup(self, value):532 def expected_markup(self, value):
482 """Return the markup for row at index 'i'."""533 """Return the markup for row at index 'i'."""
@@ -491,10 +542,9 @@
491 if must_highlight and value is not None else value542 if must_highlight and value is not None else value
492 return result543 return result
493544
494 def assert_store_correct(self, store, items):545 def assert_store_correct(self, items):
495 """Test that 'store' has 'items' as content."""546 """Test that 'store' has 'items' as content."""
496 mapping = enumerate(['operation', 'path', 'share', 'node'])547 args = (items, self.expected_markup)
497 args = (store, items, mapping, self.expected_markup)
498 super(_MagicicadaUIQueueTestCase, self).assert_store_correct(*args)548 super(_MagicicadaUIQueueTestCase, self).assert_store_correct(*args)
499549
500 def assert_current_processing_row_is_different(self):550 def assert_current_processing_row_is_different(self):
@@ -508,8 +558,8 @@
508 markup = self.expected_markup558 markup = self.expected_markup
509 expected = tuple(markup(getattr(item, attr)) for attr in attrs)559 expected = tuple(markup(getattr(item, attr)) for attr in attrs)
510560
511 iter_root = self.queue_store.get_iter_root()561 iter_root = self.store.get_iter_root()
512 actual = self.queue_store.get(iter_root, *xrange(len(attrs)))562 actual = self.store.get(iter_root, *xrange(len(attrs)))
513563
514 msg = 'first row for %s queue must be %s (got %s instead)' % \564 msg = 'first row for %s queue must be %s (got %s instead)' % \
515 (self.name, expected, actual)565 (self.name, expected, actual)
@@ -524,30 +574,30 @@
524 @skip_abstract_class574 @skip_abstract_class
525 def test_model_is_binded(self):575 def test_model_is_binded(self):
526 """List store is binded."""576 """List store is binded."""
527 actual = self.queue_view.get_model()577 actual = self.view.get_model()
528 msg = 'model for view %s differs from %s'578 msg = 'model for view %s differs from %s'
529 self.assertEqual(self.queue_store, actual,579 self.assertEqual(self.store, actual,
530 msg % (self.name, self.queue_store))580 msg % (self.name, self.store))
531581
532 @skip_abstract_class582 @skip_abstract_class
533 def test_on_queue_changed_updates_view(self):583 def test_on_queue_changed_updates_view(self):
534 """On queue changed the view is updated."""584 """On queue changed the view is updated."""
535 items = self.build_some_data()585 items = self.build_some_data()
536 self.sd_changed(items)586 self.sd_changed(items)
537 self.assert_store_correct(self.queue_store, items)587 self.assert_store_correct(items)
538588
539 @skip_abstract_class589 @skip_abstract_class
540 def test_on_queue_changed_handles_none(self):590 def test_on_queue_changed_handles_none(self):
541 """On queue changed handles None as items."""591 """On queue changed handles None as items."""
542 self.sd_changed(None)592 self.sd_changed(None)
543 self.assert_store_correct(self.queue_store, [])593 self.assert_store_correct([])
544594
545 @skip_abstract_class595 @skip_abstract_class
546 def test_on_queue_changed_handles_an_item_none(self):596 def test_on_queue_changed_handles_an_item_none(self):
547 """On queue changed handles None as items."""597 """On queue changed handles None as items."""
548 items = [QueueData(operation='Test', path='', share=None, node=None)]598 items = [QueueData(operation='Test', path='', share=None, node=None)]
549 self.sd_changed(items)599 self.sd_changed(items)
550 self.assert_store_correct(self.queue_store, items)600 self.assert_store_correct(items)
551601
552 @skip_abstract_class602 @skip_abstract_class
553 def test_model_is_cleared_before_updating(self):603 def test_model_is_cleared_before_updating(self):
@@ -557,17 +607,17 @@
557607
558 items = self.build_some_data()608 items = self.build_some_data()
559 self.sd_changed(items)609 self.sd_changed(items)
560 self.assertEqual(len(self.queue_store), len(items))610 self.assertEqual(len(self.store), len(items))
561611
562 @skip_abstract_class612 @skip_abstract_class
563 def test_view_is_enabled_if_disabled_on_changed(self):613 def test_view_is_enabled_if_disabled_on_changed(self):
564 """The tree view is enabled on changed if it was disabled."""614 """The tree view is enabled on changed if it was disabled."""
565 self.assertFalse(self.queue_view.is_sensitive(),615 self.assertFalse(self.view.is_sensitive(),
566 'Tree view must be disabled by default.')616 'Tree view must be disabled by default.')
567 items = self.build_some_data()617 items = self.build_some_data()
568 self.sd_changed(items)618 self.sd_changed(items)
569619
570 self.assertTrue(self.queue_view.is_sensitive(),620 self.assertTrue(self.view.is_sensitive(),
571 'Tree view must be enabled on changed.')621 'Tree view must be enabled on changed.')
572622
573 @skip_abstract_class623 @skip_abstract_class
@@ -578,7 +628,7 @@
578628
579 self.ui.update()629 self.ui.update()
580630
581 self.assert_store_correct(self.queue_store, data)631 self.assert_store_correct(data)
582632
583 @skip_abstract_class633 @skip_abstract_class
584 def test_on_stopped_updates_queue(self):634 def test_on_stopped_updates_queue(self):
@@ -594,7 +644,7 @@
594 label = '%sq_label' % self.name644 label = '%sq_label' % self.name
595 actual = getattr(self.ui, label).get_text()645 actual = getattr(self.ui, label).get_text()
596 expected = '%s Queue (%i)' % (self.name.capitalize(),646 expected = '%s Queue (%i)' % (self.name.capitalize(),
597 len(self.queue_store))647 len(self.store))
598 msg = '%s should be %s (got %s instead)'648 msg = '%s should be %s (got %s instead)'
599 self.assertEqual(expected, actual, msg % (label, expected, actual))649 self.assertEqual(expected, actual, msg % (label, expected, actual))
600650
@@ -608,13 +658,13 @@
608 items = self.build_some_data()658 items = self.build_some_data()
609 self.set_sd_queue(items)659 self.set_sd_queue(items)
610 self.do_start()660 self.do_start()
611 self.assert_store_correct(self.queue_store, items)661 self.assert_store_correct(items)
612662
613 items = items[:len(items) / 2]663 items = items[:len(items) / 2]
614 self.set_sd_queue(items)664 self.set_sd_queue(items)
615 self.ui.on_stop_clicked(self.ui.stop)665 self.ui.on_stop_clicked(self.ui.stop)
616 self.ui.on_stopped()666 self.ui.on_stopped()
617 self.assert_store_correct(self.queue_store, items)667 self.assert_store_correct(items)
618668
619 @skip_abstract_class669 @skip_abstract_class
620 def test_current_processing_row_is_different_if_online(self):670 def test_current_processing_row_is_different_if_online(self):
@@ -945,74 +995,40 @@
945 if self.name is None:995 if self.name is None:
946 return996 return
947 self.volume = getattr(self.ui, self.name)997 self.volume = getattr(self.ui, self.name)
948 self.volume_store = getattr(self.ui, '%s_store' % self.name)998 self.store = getattr(self.ui, '%s_store' % self.name)
949 self.volume_view = getattr(self.ui, '%s_view' % self.name)999 self.view = getattr(self.ui, '%s_view' % self.name)
950 self.volume_dialog_name = '%s_dialog' % self.name1000 self.volume_dialog_name = '%s_dialog' % self.name
951 self.volume_dialog = getattr(self.ui, self.volume_dialog_name)1001 self.volume_dialog = getattr(self.ui, self.volume_dialog_name)
952 self.on_volume_clicked = getattr(self.ui, 'on_%s_clicked' % self.name)1002 self.on_volume_clicked = getattr(self.ui, 'on_%s_clicked' % self.name)
953 self.volume_close = getattr(self.ui, '%s_close' % self.name)1003 self.volume_close = getattr(self.ui, '%s_close' % self.name)
9541004
955 def build_some_data(self, limit=5):
956 """Build some data to act as volume."""
957 kwargs = dict(data_type=self.data_type, limit=limit)
958 r = super(_MagicicadaUIVolumeTestCase, self).build_some_data(**kwargs)
959 return r
960
961 def assert_store_correct(self, store, items):
962 """Test that 'store' has 'items' as content."""
963 args = (store, items, self.mapping)
964 super(_MagicicadaUIVolumeTestCase, self).assert_store_correct(*args)
965
966 def assert_widget_availability(self, enabled=True):1005 def assert_widget_availability(self, enabled=True):
967 """Check volume availability according to 'enabled'."""1006 """Check volume availability according to 'enabled'."""
968 s = super(_MagicicadaUIVolumeTestCase, self)1007 s = super(_MagicicadaUIVolumeTestCase, self)
969 s.assert_widget_availability(self.name, enabled)1008 s.assert_widget_availability(self.name, enabled)
9701009
971 def assert_sort_order_correct(self, column, i, expected_order):1010 @skip_abstract_class
972 """Check that sort order is correctly set."""1011 def test_initial_data_ready_callback_connected(self):
973 msg0 = 'Store sort id must be %r (got %r instead).'1012 """The callback 'on_initial_data_ready' is connected to SD."""
974 msg1 = 'Store sort order must be %r (got %r instead).'1013 self.assertEqual(self.ui.sd.on_initial_data_ready_callback,
975 msg3 = 'Column sort order must be %r (got %r instead).'1014 self.ui.on_initial_data_ready,
9761015 "on_initial_data_ready should be connected.")
977 actual_id, actual_order = self.volume_store.get_sort_column_id()1016
9781017 @skip_abstract_class
979 # store sort column id and order1018 def test_volume_are_disabled_until_initial_data_ready(self):
980 self.assertEqual(i, actual_id, msg0 % (i, actual_id))1019 """Folders and shares are disabled until data ready."""
981 self.assertEqual(expected_order, actual_order,
982 msg1 % (expected_order, actual_order))
983
984 # column sort order
985 actual_order = column.get_sort_order()
986 self.assertEqual(expected_order, actual_order,
987 msg3 % (expected_order, actual_order))
988
989 def assert_sort_indicator_correct(self, column):
990 """Check that sort indicator is correctly set."""
991 msg = 'Column %s must have sort indicator %s.'
992 colname = column.get_name()
993 # column sort indicator
994 self.assertTrue(column.get_sort_indicator(), msg % (colname, 'on'))
995
996 # all the other columns must not have the sort indicator on
997 for other_column in self.volume_view.get_columns():
998 if other_column.get_name() == colname:
999 continue
1000 self.assertFalse(other_column.get_sort_indicator(),
1001 msg % (other_column.get_name(), 'off'))
1002
1003 @skip_abstract_class
1004 def test_volume_are_disabled_until_started(self):
1005 """Folders and shares are disabled until online."""
1006 # disabled at startup1020 # disabled at startup
1007 self.assert_widget_availability(enabled=False)1021 self.assert_widget_availability(enabled=False)
10081022
1009 # enabled when started1023 # enabled when initial data ready
1010 self.do_start()1024 self.ui.on_initial_data_ready()
1011 self.assert_widget_availability(enabled=True)1025 self.assert_widget_availability(enabled=True)
10121026
1013 @skip_abstract_class1027 @skip_abstract_class
1014 def test_volume_are_enabled_until_stopped(self):1028 def test_volume_are_enabled_until_stopped(self):
1015 """Folders and shares are enabled until offline."""1029 """Folders and shares are enabled until offline."""
1030 self.ui.on_initial_data_ready()
1031
1016 self.do_connect()1032 self.do_connect()
1017 self.assert_widget_availability(enabled=True)1033 self.assert_widget_availability(enabled=True)
10181034
@@ -1057,7 +1073,7 @@
1057 """Perform the test per se before closing the dialog."""1073 """Perform the test per se before closing the dialog."""
1058 self.assertTrue(self.ui.widget_is_visible(self.volume_dialog),1074 self.assertTrue(self.ui.widget_is_visible(self.volume_dialog),
1059 '%s should be visible.' % self.volume_dialog_name)1075 '%s should be visible.' % self.volume_dialog_name)
1060 self.assert_store_correct(self.volume_store, items)1076 self.assert_store_correct(items)
10611077
1062 items = self.build_some_data()1078 items = self.build_some_data()
1063 setattr(self.ui.sd, self.name, items)1079 setattr(self.ui.sd, self.name, items)
@@ -1077,7 +1093,7 @@
1077 """Perform the test per se before closing the dialog."""1093 """Perform the test per se before closing the dialog."""
1078 self.assertTrue(self.ui.widget_is_visible(self.volume_dialog),1094 self.assertTrue(self.ui.widget_is_visible(self.volume_dialog),
1079 '%s should be visible.' % self.volume_dialog_name)1095 '%s should be visible.' % self.volume_dialog_name)
1080 self.assert_store_correct(self.volume_store, items)1096 self.assert_store_correct(items)
10811097
1082 items = self.build_some_data()1098 items = self.build_some_data()
1083 setattr(self.ui.sd, self.name, items)1099 setattr(self.ui.sd, self.name, items)
@@ -1094,7 +1110,7 @@
1094 def test_on_volume_clicked_handles_none(self):1110 def test_on_volume_clicked_handles_none(self):
1095 """On volume clicked handles None as items."""1111 """On volume clicked handles None as items."""
1096 setattr(self.ui.sd, self.name, None)1112 setattr(self.ui.sd, self.name, None)
1097 test = lambda: self.assert_store_correct(self.volume_store, [])1113 test = lambda: self.assert_store_correct([])
1098 gobject.timeout_add(100, close_dialog,1114 gobject.timeout_add(100, close_dialog,
1099 (self.volume_dialog, test))1115 (self.volume_dialog, test))
1100 self.on_volume_clicked(self.volume)1116 self.on_volume_clicked(self.volume)
@@ -1110,36 +1126,36 @@
1110 def test_volume_columns_not_sorted_at_start(self):1126 def test_volume_columns_not_sorted_at_start(self):
1111 """Test volume columns are not sorted at start."""1127 """Test volume columns are not sorted at start."""
1112 msg = 'Column %s must not have the sort indicator on.'1128 msg = 'Column %s must not have the sort indicator on.'
1113 for col in self.volume_view.get_columns():1129 for col in self.view.get_columns():
1114 self.assertFalse(col.get_sort_indicator(), msg % col.get_name())1130 self.assertFalse(col.get_sort_indicator(), msg % col.get_name())
11151131
1116 @skip_abstract_class1132 @skip_abstract_class
1117 def test_volume_columns_are_clickable(self):1133 def test_volume_columns_are_clickable(self):
1118 """Test volume columns are clickable."""1134 """Test volume columns are clickable."""
1119 msg = 'Column %s must be clickable.'1135 msg = 'Column %s must be clickable.'
1120 for col in self.volume_view.get_columns():1136 for col in self.view.get_columns():
1121 self.assertTrue(col.get_clickable(), msg % col.get_name())1137 self.assertTrue(col.get_clickable(), msg % col.get_name())
11221138
1123 @skip_abstract_class1139 @skip_abstract_class
1124 def test_volume_columns_clicked_signal(self):1140 def test_volume_columns_clicked_signal(self):
1125 """Test volume columns clicks signal is properly connected."""1141 """Test volume columns clicks signal is properly connected."""
1126 msg = 'Column %s must be connected to on_store_sort_column_changed.'1142 msg = 'Column %s must be connected to on_store_sort_column_changed.'
1127 for col in self.volume_view.get_columns():1143 for col in self.view.get_columns():
1128 self.assertTrue(col.get_clickable(), msg % col.get_name())1144 self.assertTrue(col.get_clickable(), msg % col.get_name())
11291145
1130 @skip_abstract_class1146 @skip_abstract_class
1131 def test_volume_sorting(self):1147 def test_volume_sorting(self):
1132 """Test volume panel can be re-sorted."""1148 """Test volume panel can be re-sorted."""
1133 for i, col in enumerate(self.volume_view.get_columns()):1149 for idx, col in enumerate(self.view.get_columns()):
1134 col.clicked() # click on the column1150 col.clicked() # click on the column
1135 self.assert_sort_order_correct(col, i, gtk.SORT_ASCENDING)1151 self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING)
1136 self.assert_sort_indicator_correct(col)1152 self.assert_sort_indicator_correct(col)
11371153
1138 col.clicked() # click on the column, sort order must change1154 col.clicked() # click on the column, sort order must change
1139 self.assert_sort_order_correct(col, i, gtk.SORT_DESCENDING)1155 self.assert_sort_order_correct(col, idx, gtk.SORT_DESCENDING)
11401156
1141 col.clicked() # click again, sort order must be the first one1157 col.clicked() # click again, sort order must be the first one
1142 self.assert_sort_order_correct(col, i, gtk.SORT_ASCENDING)1158 self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING)
11431159
11441160
1145class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase):1161class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase):
@@ -1173,7 +1189,7 @@
1173 i = int(kwargs['free_bytes'])1189 i = int(kwargs['free_bytes'])
1174 kwargs['free_bytes'] = humanize_bytes(i, precision=2)1190 kwargs['free_bytes'] = humanize_bytes(i, precision=2)
1175 item = self.data_type(**kwargs)1191 item = self.data_type(**kwargs)
1176 self.assert_store_correct(self.volume_store, [item])1192 self.assert_store_correct([item])
11771193
1178 gobject.timeout_add(100, close_dialog,1194 gobject.timeout_add(100, close_dialog,
1179 (self.volume_dialog, test))1195 (self.volume_dialog, test))
@@ -1266,17 +1282,19 @@
1266 msg = 'buffer content must be %s (got %s instead).'1282 msg = 'buffer content must be %s (got %s instead).'
1267 self.assertEqual(actual, expected, msg % (expected, actual))1283 self.assertEqual(actual, expected, msg % (expected, actual))
12681284
1269 def test_metadata_are_disabled_until_started(self):1285 def test_metadata_are_disabled_until_initial_data_ready(self):
1270 """Metadata button is disabled until online."""1286 """Metadata button is disabled until initial data."""
1271 # disabled at startup1287 # disabled at startup
1272 self.assert_widget_availability(enabled=False)1288 self.assert_widget_availability(enabled=False)
12731289
1274 # enabled when started1290 # enabled when initial data ready
1275 self.do_start()1291 self.ui.on_initial_data_ready()
1276 self.assert_widget_availability(enabled=True)1292 self.assert_widget_availability(enabled=True)
12771293
1278 def test_metadata_are_enabled_until_stopped(self):1294 def test_metadata_are_enabled_until_stopped(self):
1279 """Metadata button is enabled until offline."""1295 """Metadata button is enabled until offline."""
1296 self.ui.on_initial_data_ready()
1297
1280 self.do_connect()1298 self.do_connect()
1281 self.assert_widget_availability(enabled=True)1299 self.assert_widget_availability(enabled=True)
12821300
@@ -1515,11 +1533,12 @@
1515 self.memento.setLevel(logging.DEBUG)1533 self.memento.setLevel(logging.DEBUG)
1516 logger = logging.getLogger('magicicada.ui')1534 logger = logging.getLogger('magicicada.ui')
1517 logger.addHandler(self.memento)1535 logger.addHandler(self.memento)
1536 logger.setLevel(logging.DEBUG)
15181537
1519 def assert_function_logs(self, func, *args, **kwargs):1538 def assert_function_logs(self, level, func, *args, **kwargs):
1520 """Check 'funcion' logs its inputs as DEBUG."""1539 """Check 'funcion' logs its inputs as 'level'."""
1521 name = func.__name__1540 name = func.__name__
1522 msg = '%s must be logged as DEBUG'1541 msg = '%s must be logged with level %r'
1523 try:1542 try:
1524 func(*args, **kwargs)1543 func(*args, **kwargs)
1525 except Exception: # pylint: disable=E0501, W07031544 except Exception: # pylint: disable=E0501, W0703
@@ -1528,33 +1547,201 @@
1528 'function (%s) must be logged as ERROR' % name)1547 'function (%s) must be logged as ERROR' % name)
1529 self.assertTrue(self.memento.check_error(exc),1548 self.assertTrue(self.memento.check_error(exc),
1530 'sys.exc_info (%s) must be logged as ERROR' % exc)1549 'sys.exc_info (%s) must be logged as ERROR' % exc)
1531 self.assertTrue(self.memento.check_debug(name), msg % name)1550
1551 memento_func = getattr(self.memento, 'check_%s' % level.lower())
1552 self.assertTrue(memento_func(name), msg % (name, level))
1532 for arg in args:1553 for arg in args:
1533 self.assertTrue(self.memento.check_debug(str(arg)), msg % arg)1554 self.assertTrue(memento_func(str(arg)), msg % (arg, level))
1534 for key, val in kwargs.iteritems():1555 for key, val in kwargs.iteritems():
1535 arg = "'%s': %r" % (key, val)1556 arg = "'%s': %r" % (key, val)
1536 self.assertTrue(self.memento.check_debug(arg), msg % arg)1557 self.assertTrue(memento_func(arg), msg % (arg, level))
15371558
1538 def test_on_shares_clicked_logs(self):1559 def test_on_shares_clicked_logs(self):
1539 """Check _on_shares_clicked logs properly."""1560 """Check _on_shares_clicked logs properly."""
1540 args = ([0, object(), 'test', {}], object())1561 args = ([0, object(), 'test', {}], object())
1541 kwargs = dict(dialog=object())1562 kwargs = dict(dialog=object())
1542 self.assert_function_logs(self.ui._on_shares_clicked, *args, **kwargs)1563 self.assert_function_logs(logging.getLevelName(logging.DEBUG),
1564 self.ui._on_shares_clicked, *args, **kwargs)
15431565
1544 def test_on_status_changed_logs(self):1566 def test_on_status_changed_logs(self):
1545 """Check _on_status_changed logs properly."""1567 """Check _on_status_changed logs properly."""
1546 args = ('test status', 'status description', True, False, True)1568 args = ('test status', 'status description', True, False, True)
1547 kwargs = dict(queues='bla', connection=None)1569 kwargs = dict(queues='bla', connection=None)
1548 self.assert_function_logs(self.ui.on_status_changed, *args, **kwargs)1570 self.assert_function_logs(logging.getLevelName(logging.DEBUG),
1571 self.ui.on_status_changed, *args, **kwargs)
15491572
1550 def test_on_queue_changed_logs(self):1573 def test_on_queue_changed_logs(self):
1551 """Check _on_queue_changed logs properly."""1574 """Check _on_queue_changed logs properly."""
1552 args = ('meta',)1575 args = ('meta',)
1553 kwargs = dict(items=[0, object(), 'test', {}], must_highlight=True)1576 kwargs = dict(items=[0, object(), 'test', {}], must_highlight=True)
1554 self.assert_function_logs(self.ui._on_queue_changed, *args, **kwargs)1577 self.assert_function_logs(logging.getLevelName(logging.DEBUG),
1578 self.ui._on_queue_changed, *args, **kwargs)
15551579
1556 def test_on_metadata_ready_logs(self):1580 def test_on_metadata_ready_logs(self):
1557 """Check on_metadata_ready logs properly."""1581 """Check on_metadata_ready logs properly."""
1558 args = ()1582 args = ()
1559 kwargs = dict(path='test', metadata=True)1583 kwargs = dict(path='test', metadata=True)
1560 self.assert_function_logs(self.ui.on_metadata_ready, *args, **kwargs)1584 self.assert_function_logs(logging.getLevelName(logging.DEBUG),
1585 self.ui.on_metadata_ready, *args, **kwargs)
1586
1587 def test_on_initial_data_ready_logs(self):
1588 """Check on_initial_data_ready logs properly."""
1589 args = ()
1590 kwargs = dict(path='test', metadata=True)
1591 self.assert_function_logs(logging.getLevelName(logging.INFO),
1592 self.ui.on_initial_data_ready,
1593 *args, **kwargs)
1594
1595
1596class PublicFilesTestCase(MagicicadaUITestCase):
1597 """UI test cases for public files."""
1598
1599 name = 'public_files'
1600 data_type = PublicFilesData
1601 mapping = enumerate(['path', 'public_url', 'volume', 'node'])
1602
1603 def setUp(self):
1604 """Init."""
1605 super(PublicFilesTestCase, self).setUp()
1606 self.store = self.ui.public_files_store
1607 self.view = self.ui.public_files_view
1608
1609 def assert_widget_availability(self, enabled=True, msg=None):
1610 """Check widget availability according to 'enabled'."""
1611 widget = self.ui.public_files
1612 self.assertTrue(self.ui.widget_is_visible(widget), 'must be visible')
1613
1614 sensitive = widget.is_sensitive()
1615 if msg is None:
1616 msg = 'must %sbe sensitive' % ('' if enabled else 'not ',)
1617 self.assertTrue(sensitive if enabled else not sensitive, msg)
1618
1619 def test_initial_data_ready_callback_connected(self):
1620 """The callback 'on_initial_online_data_ready' is connected to SD."""
1621 self.assertEqual(self.ui.sd.on_initial_online_data_ready_callback,
1622 self.ui.on_initial_online_data_ready,
1623 "on_initial_online_data_ready should be connected.")
1624
1625 def test_disabled_until_initial_online_data_ready(self):
1626 """Widget is disabled until initial online data ready."""
1627 self.assert_widget_availability(enabled=False,
1628 msg='must be disabled at startup')
1629
1630 self.ui.on_initial_online_data_ready()
1631 msg = 'must be enabled when initial data ready'
1632 self.assert_widget_availability(enabled=True, msg=msg)
1633
1634 def test_enabled_until_stopped(self):
1635 """Widget is enabled until stopped."""
1636 self.ui.on_initial_online_data_ready()
1637
1638 self.do_connect()
1639 self.assert_widget_availability(enabled=True)
1640
1641 self.ui.on_online()
1642 self.assert_widget_availability(enabled=True)
1643
1644 self.ui.on_offline()
1645 self.assert_widget_availability(enabled=True)
1646
1647 self.ui.on_stopped()
1648 self.assert_widget_availability(enabled=False)
1649
1650 def test_public_files_close_emits_response_close(self):
1651 """Test close button emits RESPONSE_CLOSE when clicked."""
1652 self.patch(self.ui.public_files_dialog, 'response', self._set_called)
1653 self.ui.public_files_close.clicked()
1654 self.assertEqual(self._called, ((gtk.RESPONSE_CLOSE,), {}),
1655 'close button should emit RESPONSE_CLOSE.')
1656
1657 def test_on_public_files_clicked_runs_the_dialog(self):
1658 """Test on_public_files_clicked run the public_files_dialog."""
1659 self.patch(self.ui.public_files_dialog, 'run', self._set_called)
1660 self.ui.sd.public_files = {}
1661
1662 visible = self.ui.widget_is_visible(self.ui.public_files_dialog)
1663 self.assertFalse(visible, 'dialog should not be visible.')
1664
1665 self.ui.on_public_files_clicked(self.ui.public_files)
1666
1667 self.assertEqual(self._called, ((), {}))
1668
1669 def test_on_public_files_clicked_hides_the_dialog(self):
1670 """Test on_public_files_clicked hides the public_files_dialog."""
1671 self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run
1672 self.patch(self.ui.public_files_dialog, 'hide', self._set_called)
1673 self.ui.sd.public_files = {}
1674
1675 self.ui.on_public_files_clicked(self.ui.public_files)
1676
1677 self.assertEqual(self._called, ((), {}))
1678
1679 def test_on_public_files_clicked_grabs_info_from_backend(self):
1680 """Test on_public_files_clicked."""
1681 self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run
1682 self.ui.sd.public_files = SAMPLE_PUBLIC_FILES
1683
1684 self.ui.on_public_files_clicked(self.ui.public_files)
1685
1686 self.assert_store_correct(SAMPLE_PUBLIC_FILES.values())
1687
1688 def test_on_public_files_clicked_twice(self):
1689 """Test on_public_files_clicked twice."""
1690 self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run
1691 self.ui.sd.public_files = SAMPLE_PUBLIC_FILES
1692
1693 self.ui.on_public_files_clicked(self.ui.public_files)
1694 self.ui.on_public_files_clicked(self.ui.public_files)
1695
1696 self.assert_store_correct(SAMPLE_PUBLIC_FILES.values())
1697
1698 def test_on_public_files_clicked_handles_none(self):
1699 """On public_files clicked handles None as items."""
1700 self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run
1701 self.ui.sd.public_files = None
1702
1703 self.ui.on_public_files_clicked(self.ui.public_files)
1704
1705 self.assert_store_correct([])
1706
1707 def test_dialog_properties(self):
1708 """The dialog has correct properties."""
1709 title = self.name.replace('_', ' ').capitalize()
1710 self.assert_dialog_properties(dialog_name='public_files_dialog',
1711 title=title)
1712
1713 def test_columns_not_sorted_at_start(self):
1714 """Test columns are not sorted at start."""
1715 msg = 'Column %s must not have the sort indicator on.'
1716 for col in self.ui.public_files_view.get_columns():
1717 self.assertFalse(col.get_sort_indicator(), msg % col.get_name())
1718
1719 def test_columns_are_clickable(self):
1720 """Test columns are clickable."""
1721 msg = 'Column %s must be clickable.'
1722 for col in self.ui.public_files_view.get_columns():
1723 self.assertTrue(col.get_clickable(), msg % col.get_name())
1724
1725 def test_columns_clicked_signal(self):
1726 """Test columns clicked signal is properly connected."""
1727 msg = 'Column %s must be connected to on_store_sort_column_changed.'
1728 for col in self.ui.public_files_view.get_columns():
1729 self.assertTrue(col.get_clickable(), msg % col.get_name())
1730
1731 def test_sorting(self):
1732 """Test public files panel can be re-sorted."""
1733 self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run
1734 self.ui.sd.public_files = SAMPLE_PUBLIC_FILES
1735
1736 self.ui.on_public_files_clicked(self.ui.public_files)
1737
1738 for idx, col in enumerate(self.ui.public_files_view.get_columns()):
1739 col.clicked() # click on the column
1740 self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING)
1741 self.assert_sort_indicator_correct(col)
1742
1743 col.clicked() # click on the column, sort order must change
1744 self.assert_sort_order_correct(col, idx, gtk.SORT_DESCENDING)
1745
1746 col.clicked() # click again, sort order must be the first one
1747 self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING)
15611748
=== modified file 'magicicada/tests/test_syncdaemon.py'
--- magicicada/tests/test_syncdaemon.py 2010-09-02 18:23:02 +0000
+++ magicicada/tests/test_syncdaemon.py 2011-01-07 23:06:32 +0000
@@ -22,7 +22,13 @@
22import os22import os
23import unittest23import unittest
2424
25from magicicada.syncdaemon import SyncDaemon, State25from magicicada.dbusiface import PublicFilesData, ShareOperationError
26from magicicada.syncdaemon import (
27 ASKING_IDLE,
28 mandatory_callback,
29 State,
30 SyncDaemon,
31)
26from magicicada.tests.helpers import MementoHandler32from magicicada.tests.helpers import MementoHandler
2733
28from twisted.trial.unittest import TestCase as TwistedTestCase34from twisted.trial.unittest import TestCase as TwistedTestCase
@@ -32,18 +38,23 @@
32# It's ok to access private data in the test suite38# It's ok to access private data in the test suite
33# pylint: disable=W021239# pylint: disable=W0212
3440
41# Lambda may not be necessary
42# pylint: disable=W0108
43
3544
36class FakeDBusInterface(object):45class FakeDBusInterface(object):
37 """Fake DBus Interface, for SD to not use dbus at all during tests."""46 """Fake DBus Interface, for SD to not use dbus at all during tests."""
3847
39 fake_sd_started = False48 fake_sd_started = False
49 fake_pf_data = PublicFilesData(volume='v', node='n',
50 path='p', public_url='u')
51 fake_share_response = None
4052
41 def __init__(self, sd):53 def __init__(self, sd):
42 pass54 pass
4355
44 def shutdown(self):56 def shutdown(self):
45 """Fake shutdown."""57 """Fake shutdown."""
46 pass
4758
48 def get_status(self):59 def get_status(self):
49 """Fake status."""60 """Fake status."""
@@ -58,10 +69,18 @@
58 start = quit = connect = disconnect = get_folders69 start = quit = connect = disconnect = get_folders
59 get_shares_to_me = get_shares_to_others = get_folders70 get_shares_to_me = get_shares_to_others = get_folders
6071
72 def get_public_files(self):
73 """Fake public files."""
74 return defer.succeed([self.fake_pf_data])
75
61 def is_sd_started(self):76 def is_sd_started(self):
62 """Fake response."""77 """Fake response."""
63 return self.fake_sd_started78 return self.fake_sd_started
6479
80 def accept_share(self, share_id):
81 """Fake accept share."""
82 return self.fake_share_response
83
6584
66class BaseTest(TwistedTestCase):85class BaseTest(TwistedTestCase):
67 """Base test with a SD."""86 """Base test with a SD."""
@@ -70,6 +89,11 @@
7089
71 def setUp(self):90 def setUp(self):
72 """Set up."""91 """Set up."""
92 self.hdlr = MementoHandler()
93 self.hdlr.setLevel(logging.DEBUG)
94 logger = logging.getLogger('magicicada.syncdaemon')
95 logger.addHandler(self.hdlr)
96 logger.setLevel(logging.DEBUG)
73 self.sd = SyncDaemon(FakeDBusInterface)97 self.sd = SyncDaemon(FakeDBusInterface)
7498
75 def tearDown(self):99 def tearDown(self):
@@ -77,9 +101,44 @@
77 self.sd.shutdown()101 self.sd.shutdown()
78102
79103
104class MandatoryCallbackTests(BaseTest):
105 """Tests for the mandatory callback generic function."""
106
107 def test_log_function_name(self):
108 """Log the function name."""
109 some_function = mandatory_callback('bar')
110 some_function()
111 self.assertTrue(self.hdlr.check_warning(
112 "Callback called but was not assigned", "bar"))
113
114 def test_log_args(self):
115 """Log the arguments."""
116 some_function = mandatory_callback('bar')
117 some_function(1, 2, b=45)
118 self.hdlr.debug = True
119 self.assertTrue(self.hdlr.check_warning(
120 "Callback called but was not assigned",
121 "1", "2", "'b': 45"))
122
123
80class InitialDataTests(unittest.TestCase):124class InitialDataTests(unittest.TestCase):
81 """Tests for initial data gathering."""125 """Tests for initial data gathering."""
82126
127 def setUp(self):
128 """Set up the test."""
129 self.sd = SyncDaemon(FakeDBusInterface)
130
131 self.offline_called = False
132 self.sd.on_initial_data_ready_callback = lambda: setattr(self,
133 'offline_called', True)
134 self.online_called = False
135 self.sd.on_initial_online_data_ready_callback = lambda: setattr(self,
136 'online_called', True)
137
138 def tearDown(self):
139 """Tear down the test."""
140 self.sd.shutdown()
141
83 def test_called_by_start(self):142 def test_called_by_start(self):
84 """Check that start calls get initial data."""143 """Check that start calls get initial data."""
85 sd = SyncDaemon(FakeDBusInterface)144 sd = SyncDaemon(FakeDBusInterface)
@@ -135,6 +194,68 @@
135 sd._get_initial_data()194 sd._get_initial_data()
136 self.assertEqual(len(called), 3)195 self.assertEqual(len(called), 3)
137196
197 def test_public_files_info(self):
198 """Check we get the public files info at start."""
199 sd = SyncDaemon(FakeDBusInterface)
200 fake_data = FakeDBusInterface.fake_pf_data
201 sd._get_initial_data()
202 self.assertEqual(sd.public_files[fake_data.node], fake_data)
203
204 def test_all_ready(self):
205 """All data is ready."""
206 self.sd._get_initial_data()
207 self.assertTrue(self.offline_called)
208 self.assertTrue(self.online_called)
209
210 def test_no_public_files(self):
211 """Initial gathering is stuck in public files."""
212 self.sd.dbus.get_public_files = lambda: defer.Deferred()
213 self.sd._get_initial_data()
214 self.assertTrue(self.offline_called)
215 self.assertFalse(self.online_called)
216
217 def test_no_shares_to_others(self):
218 """Initial gathering is stuck in shares to others."""
219 self.sd.dbus.get_shares_to_others = lambda: defer.Deferred()
220 self.sd._get_initial_data()
221 self.assertFalse(self.offline_called)
222 self.assertFalse(self.online_called)
223
224 def test_no_shares_to_me(self):
225 """Initial gathering is stuck in shares to me."""
226 self.sd.dbus.get_shares_to_me = lambda: defer.Deferred()
227 self.sd._get_initial_data()
228 self.assertFalse(self.offline_called)
229 self.assertFalse(self.online_called)
230
231 def test_no_folders(self):
232 """Initial gathering is stuck in folders."""
233 self.sd.dbus.get_folders = lambda: defer.Deferred()
234 self.sd._get_initial_data()
235 self.assertFalse(self.offline_called)
236 self.assertFalse(self.online_called)
237
238 def test_no_meta_queue(self):
239 """Initial gathering is stuck in meta queue."""
240 self.sd.dbus.get_meta_queue = lambda: defer.Deferred()
241 self.sd._get_initial_data()
242 self.assertFalse(self.offline_called)
243 self.assertFalse(self.online_called)
244
245 def test_no_content_queue(self):
246 """Initial gathering is stuck in content queue."""
247 self.sd.dbus.get_content_queue = lambda: defer.Deferred()
248 self.sd._get_initial_data()
249 self.assertFalse(self.offline_called)
250 self.assertFalse(self.online_called)
251
252 def test_no_status(self):
253 """Initial gathering is stuck in status."""
254 self.sd.dbus.get_status = lambda: defer.Deferred()
255 self.sd._get_initial_data()
256 self.assertFalse(self.offline_called)
257 self.assertFalse(self.online_called)
258
138259
139class StatusChangedTests(BaseTest):260class StatusChangedTests(BaseTest):
140 """Simple signals checking."""261 """Simple signals checking."""
@@ -175,7 +296,7 @@
175 False, 'queues', 'connection')296 False, 'queues', 'connection')
176 return deferred297 return deferred
177298
178 def test_status_changed_affects_cuurent_status(self):299 def test_status_changed_affects_current_status(self):
179 """Make changes to see how status are reflected."""300 """Make changes to see how status are reflected."""
180 # one set of values301 # one set of values
181 self.sd.on_sd_status_changed('name1', 'description1', False, True,302 self.sd.on_sd_status_changed('name1', 'description1', False, True,
@@ -322,24 +443,115 @@
322 self.sd.on_sd_content_queue_changed()443 self.sd.on_sd_content_queue_changed()
323 self.assertEqual(called, [['foo']])444 self.assertEqual(called, [['foo']])
324445
325 def test_CQ_state_nothing(self):446 def test_cq_state_nothing(self):
326 """Check the ContentQueue info, being nothing."""447 """Check the ContentQueue info, being nothing."""
327 self.sd.dbus.get_content_queue = lambda: defer.succeed([])448 self.sd.dbus.get_content_queue = lambda: defer.succeed([])
328 self.sd.on_sd_content_queue_changed()449 self.sd.on_sd_content_queue_changed()
329 self.assertEqual(self.sd.content_queue, [])450 self.assertEqual(self.sd.content_queue, [])
330451
331 def test_CQ_state_one(self):452 def test_cq_state_one(self):
332 """Check the ContentQueue info, being one."""453 """Check the ContentQueue info, being one."""
333 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo'])454 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo'])
334 self.sd.on_sd_content_queue_changed()455 self.sd.on_sd_content_queue_changed()
335 self.assertEqual(self.sd.content_queue, ['foo'])456 self.assertEqual(self.sd.content_queue, ['foo'])
336457
337 def test_CQ_state_two(self):458 def test_cq_state_two(self):
338 """Check the ContentQueue info, two."""459 """Check the ContentQueue info, two."""
339 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo', 'bar'])460 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo', 'bar'])
340 self.sd.on_sd_content_queue_changed()461 self.sd.on_sd_content_queue_changed()
341 self.assertEqual(self.sd.content_queue, ['foo', 'bar'])462 self.assertEqual(self.sd.content_queue, ['foo', 'bar'])
342463
464 def test_cq_multiple_without_response_having_two(self):
465 """Behave correctly on two-changes burst."""
466 d1 = defer.Deferred()
467 d2 = defer.Deferred()
468 called = []
469
470 def fake():
471 """Fake."""
472 d = d2 if called else d1
473 called.append(None)
474 return d
475 self.sd.dbus.get_content_queue = fake
476
477 # call twice
478 self.sd.on_sd_content_queue_changed()
479 self.sd.on_sd_content_queue_changed()
480
481 # dbus function should be called once
482 self.assertEqual(len(called), 1)
483
484 # first call returns
485 d1.callback(['foo'])
486 d2.callback(['bar'])
487
488 # it should be called once more only
489 self.assertEqual(len(called), 2)
490
491 def test_cq_multiple_without_response_having_three(self):
492 """Behave correctly on three-changes burst."""
493 d1 = defer.Deferred()
494 d2 = defer.Deferred()
495 called = []
496
497 def fake():
498 """Fake."""
499 d = d2 if called else d1
500 called.append(None)
501 return d
502 self.sd.dbus.get_content_queue = fake
503
504 # call thrice
505 self.sd.on_sd_content_queue_changed()
506 self.sd.on_sd_content_queue_changed()
507 self.sd.on_sd_content_queue_changed()
508
509 # dbus function should be called once
510 self.assertEqual(len(called), 1)
511
512 # first call returns
513 d1.callback(['foo'])
514 d2.callback(['bar'])
515
516 # it should be called once more only
517 self.assertEqual(len(called), 2)
518
519 def test_cq_multiple_without_response_having_more_later(self):
520 """Behave correctly on changes burst delayed."""
521 d1 = defer.Deferred()
522 d2 = defer.Deferred()
523 d3 = defer.Deferred()
524 defs = [d1, d2, d3]
525 called = []
526
527 def fake():
528 """Fake."""
529 d = defs[len(called)]
530 called.append(None)
531 return d
532 self.sd.dbus.get_content_queue = fake
533
534 # call some times, dbus function should be called once
535 self.sd.on_sd_content_queue_changed()
536 self.sd.on_sd_content_queue_changed()
537 self.sd.on_sd_content_queue_changed()
538 self.assertEqual(len(called), 1)
539
540 # first call returns
541 d1.callback(['foo'])
542
543 # while calling second time, generate more requests
544 self.sd.on_sd_content_queue_changed()
545 self.sd.on_sd_content_queue_changed()
546 self.sd.on_sd_content_queue_changed()
547
548 # second call returns
549 d2.callback(['foo'])
550 d3.callback(['foo'])
551
552 # it should be called three times total
553 self.assertEqual(len(called), 3)
554
343555
344class MetaQueueChangedTests(BaseTest):556class MetaQueueChangedTests(BaseTest):
345 """Check the MetaQueueChanged handling."""557 """Check the MetaQueueChanged handling."""
@@ -526,6 +738,8 @@
526738
527 def test_mq_caller_is_reset_last_time(self):739 def test_mq_caller_is_reset_last_time(self):
528 """When MQ is polled last time, the caller should be back to None."""740 """When MQ is polled last time, the caller should be back to None."""
741 # Module 'twisted.internet.reactor' has no 'callLater' member
742 # pylint: disable=E1101
529 self.sd._mqcaller = reactor.callLater(100, lambda: None)743 self.sd._mqcaller = reactor.callLater(100, lambda: None)
530 self.sd.current_state.set(name='QUEUE_MANAGER',744 self.sd.current_state.set(name='QUEUE_MANAGER',
531 queues='WORKING_ON_CONTENT')745 queues='WORKING_ON_CONTENT')
@@ -550,6 +764,8 @@
550 'WORKING_ON_CONTENT', 'connect')764 'WORKING_ON_CONTENT', 'connect')
551765
552 # allow time to see if a mistaken call happens766 # allow time to see if a mistaken call happens
767 # Module 'twisted.internet.reactor' has no 'callLater' member
768 # pylint: disable=E1101
553 reactor.callLater(.5, deferred.callback, True)769 reactor.callLater(.5, deferred.callback, True)
554 elif len(calls) == 4:770 elif len(calls) == 4:
555 pass # last call after state changed771 pass # last call after state changed
@@ -566,24 +782,132 @@
566 deferred = defer.Deferred()782 deferred = defer.Deferred()
567 return deferred783 return deferred
568784
569 def test_MQ_state_nothing(self):785 def test_mq_state_nothing(self):
570 """Check the MetaQueue info, being nothing."""786 """Check the MetaQueue info, being nothing."""
571 self.sd.dbus.get_meta_queue = lambda: defer.succeed([])787 self.sd.dbus.get_meta_queue = lambda: defer.succeed([])
572 self.sd._check_mq()788 self.sd._check_mq()
573 self.assertEqual(self.sd.meta_queue, [])789 self.assertEqual(self.sd.meta_queue, [])
574790
575 def test_MQ_state_one(self):791 def test_mq_state_one(self):
576 """Check the MetaQueue info, being one."""792 """Check the MetaQueue info, being one."""
577 self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo'])793 self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo'])
578 self.sd._check_mq()794 self.sd._check_mq()
579 self.assertEqual(self.sd.meta_queue, ['foo'])795 self.assertEqual(self.sd.meta_queue, ['foo'])
580796
581 def test_MQ_state_two(self):797 def test_mq_state_two(self):
582 """Check the MetaQueue info, two."""798 """Check the MetaQueue info, two."""
583 self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo', 'bar'])799 self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo', 'bar'])
584 self.sd._check_mq()800 self.sd._check_mq()
585 self.assertEqual(self.sd.meta_queue, ['foo', 'bar'])801 self.assertEqual(self.sd.meta_queue, ['foo', 'bar'])
586802
803 def test_mq_multiple_without_response_having_two(self):
804 """Behave correctly on two-changes burst."""
805 d1 = defer.Deferred()
806 d2 = defer.Deferred()
807 called = []
808
809 def fake():
810 """Fake."""
811 d = d2 if called else d1
812 called.append(None)
813 return d
814 self.sd.dbus.get_meta_queue = fake
815
816 # call twice
817 self.sd.on_sd_meta_queue_changed()
818 self.sd.on_sd_meta_queue_changed()
819
820 # dbus function should be called once
821 self.assertEqual(len(called), 1)
822
823 # first call returns
824 d1.callback(['foo'])
825 d2.callback(['bar'])
826
827 # it should be called once more only
828 self.assertEqual(len(called), 2)
829
830 def test_mq_multiple_without_response_having_three(self):
831 """Behave correctly on three-changes burst."""
832 d1 = defer.Deferred()
833 d2 = defer.Deferred()
834 called = []
835
836 def fake():
837 """Fake."""
838 d = d2 if called else d1
839 called.append(None)
840 return d
841 self.sd.dbus.get_meta_queue = fake
842
843 # call thrice
844 self.sd.on_sd_meta_queue_changed()
845 self.sd.on_sd_meta_queue_changed()
846 self.sd.on_sd_meta_queue_changed()
847
848 # dbus function should be called once
849 self.assertEqual(len(called), 1)
850
851 # first call returns
852 d1.callback(['foo'])
853 d2.callback(['bar'])
854
855 # it should be called once more only
856 self.assertEqual(len(called), 2)
857
858 def test_mq_multiple_without_response_having_more_later(self):
859 """Behave correctly on changes burst delayed."""
860 d1 = defer.Deferred()
861 d2 = defer.Deferred()
862 d3 = defer.Deferred()
863 defs = [d1, d2, d3]
864 called = []
865
866 def fake():
867 """Fake."""
868 d = defs[len(called)]
869 called.append(None)
870 return d
871 self.sd.dbus.get_meta_queue = fake
872
873 # call some times, dbus function should be called once
874 self.sd.on_sd_meta_queue_changed()
875 self.sd.on_sd_meta_queue_changed()
876 self.sd.on_sd_meta_queue_changed()
877 self.assertEqual(len(called), 1)
878
879 # first call returns
880 d1.callback(['foo'])
881
882 # while calling second time, generate more requests
883 self.sd.on_sd_meta_queue_changed()
884 self.sd.on_sd_meta_queue_changed()
885 self.sd.on_sd_meta_queue_changed()
886
887 # second call returns
888 d2.callback(['foo'])
889 d3.callback(['foo'])
890
891 # it should be called three times total
892 self.assertEqual(len(called), 3)
893
894 @defer.inlineCallbacks
895 def test_mq_multiple_support_error(self):
896 """Leave the state ok even with an error in the underlying call."""
897 def fake():
898 """Fake."""
899 raise ValueError('foo')
900 self.sd.dbus.get_meta_queue = fake
901
902 # call it, and "absorb" the error we just generated
903 try:
904 yield self.sd.on_sd_meta_queue_changed()
905 except ValueError:
906 pass
907
908 # it should be in IDLE
909 self.assertEqual(self.sd._mq_asking, ASKING_IDLE)
910
587911
588class StateTests(unittest.TestCase):912class StateTests(unittest.TestCase):
589 """Test State class."""913 """Test State class."""
@@ -732,13 +1056,6 @@
732 False, 'queues', 'connection')1056 False, 'queues', 'connection')
733 self.assertTrue(self.called)1057 self.assertTrue(self.called)
7341058
735 @defer.inlineCallbacks
736 def test_on_initial_data_ready(self):
737 """Called when SD gets all the initial data."""
738 self.flag_called(self.sd, 'on_initial_data_ready_callback')
739 yield self.sd._get_initial_data()
740 self.assertTrue(self.called)
741
7421059
743class TestLogs(unittest.TestCase):1060class TestLogs(unittest.TestCase):
744 """Test logging."""1061 """Test logging."""
@@ -746,8 +1063,10 @@
746 def setUp(self):1063 def setUp(self):
747 """Set up."""1064 """Set up."""
748 self.hdlr = MementoHandler()1065 self.hdlr = MementoHandler()
749 logging.getLogger('magicicada.syncdaemon').addHandler(self.hdlr)
750 self.hdlr.setLevel(logging.DEBUG)1066 self.hdlr.setLevel(logging.DEBUG)
1067 logger = logging.getLogger('magicicada.syncdaemon')
1068 logger.addHandler(self.hdlr)
1069 logger.setLevel(logging.DEBUG)
751 self.sd = SyncDaemon(FakeDBusInterface)1070 self.sd = SyncDaemon(FakeDBusInterface)
7521071
753 def tearDown(self):1072 def tearDown(self):
@@ -768,8 +1087,12 @@
768 def test_initial_value(self):1087 def test_initial_value(self):
769 """Log the initial filling."""1088 """Log the initial filling."""
770 yield self.sd._get_initial_data()1089 yield self.sd._get_initial_data()
771 self.assertTrue(self.hdlr.check_info("Getting initial data"))1090 self.assertTrue(self.hdlr.check_info("Getting offline initial data"))
772 self.assertTrue(self.hdlr.check_info("All initial data is ready"))1091 self.assertTrue(self.hdlr.check_info(
1092 "All initial offline data is ready"))
1093 self.assertTrue(self.hdlr.check_info("Getting online initial data"))
1094 self.assertTrue(self.hdlr.check_info(
1095 "All initial online data is ready"))
7731096
774 def test_start(self):1097 def test_start(self):
775 """Log the call to start."""1098 """Log the call to start."""
@@ -823,7 +1146,7 @@
823 self.assertTrue(self.hdlr.check_info(1146 self.assertTrue(self.hdlr.check_info(
824 "SD Meta Queue info is new! 1 items"))1147 "SD Meta Queue info is new! 1 items"))
8251148
826 def test_content_queue_changed(self):1149 def test_meta_queue_changed(self):
827 """Log that process_cq has new data."""1150 """Log that process_cq has new data."""
828 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo'])1151 self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo'])
829 self.sd.on_sd_content_queue_changed()1152 self.sd.on_sd_content_queue_changed()
@@ -857,13 +1180,32 @@
857 self.sd.on_sd_name_owner_changed(True)1180 self.sd.on_sd_name_owner_changed(True)
858 self.assertTrue(self.hdlr.check_info("SD Name Owner changed: True"))1181 self.assertTrue(self.hdlr.check_info("SD Name Owner changed: True"))
8591182
1183 def test_on_public_files_list(self):
1184 """Log we got a new public files list."""
1185 pf1 = PublicFilesData(volume='v', node='n1', path='p', public_url='u')
1186 pf2 = PublicFilesData(volume='v', node='n2', path='p', public_url='u')
1187 self.sd.on_sd_public_files_list([pf1, pf2])
1188 self.assertTrue(self.hdlr.check_info(
1189 "Got new Public Files list (2 items)"))
1190
1191 def test_on_public_files_changed(self):
1192 """Log we got a change in the public files list."""
1193 self.sd.public_files = {}
1194 pf = PublicFilesData(volume='volume', node='node',
1195 path='path', public_url='url')
1196 self.sd.on_sd_public_files_changed(pf, True)
1197 self.assertTrue(self.hdlr.check_info(
1198 "Change in Public Files list! is_public=True",
1199 "node=node", "path=u'path'"))
1200
8601201
861class MetadataTests(BaseTest):1202class MetadataTests(BaseTest):
862 """Get Metadata info."""1203 """Get Metadata info."""
8631204
864 def test_get_metadata_no_callback_set(self):1205 def test_get_metadata_no_callback_set(self):
865 """It's mandatory to set the callback for this response."""1206 """It's mandatory to set the callback for this response."""
866 self.assertRaises(ValueError, self.sd.get_metadata, 'path')1207 self.sd.on_metadata_ready_callback()
1208 self.assertTrue(self.hdlr.check_warning('on_metadata_ready_callback'))
8671209
868 def test_get_metadata_ok(self):1210 def test_get_metadata_ok(self):
869 """Get the metadata for given path."""1211 """Get the metadata for given path."""
@@ -988,3 +1330,139 @@
9881330
989 # test1331 # test
990 self.assertEqual(cal, [2])1332 self.assertEqual(cal, [2])
1333
1334
1335class PublicFilesTests(BaseTest):
1336 """PublicFiles checking."""
1337
1338 def test_signal_updates_value(self):
1339 """Test that when signal arrives it updates the internal attribute."""
1340 pf = PublicFilesData(volume='volume', node='node',
1341 path='path', public_url='url')
1342 self.sd.on_sd_public_files_list([pf])
1343 self.assertEqual(len(self.sd.public_files), 1)
1344 pf = self.sd.public_files['node']
1345 self.assertEqual(pf.volume, 'volume')
1346 self.assertEqual(pf.node, 'node')
1347 self.assertEqual(pf.public_url, 'url')
1348 self.assertEqual(pf.path, 'path')
1349
1350 def test_public_files_changed_added_noprevious(self):
1351 """Test that it adds the new public file."""
1352 # reset and add
1353 self.sd.public_files = {}
1354 pf = PublicFilesData(volume='volume', node='node',
1355 path='path', public_url='url')
1356 self.sd.on_sd_public_files_changed(pf, True)
1357
1358 # check is there
1359 pf = self.sd.public_files['node']
1360 self.assertEqual(pf.volume, 'volume')
1361 self.assertEqual(pf.node, 'node')
1362 self.assertEqual(pf.public_url, 'url')
1363 self.assertEqual(pf.path, 'path')
1364
1365 def test_public_files_changed_added_previous(self):
1366 """Test that it updates the public file."""
1367 # add one
1368 pf = PublicFilesData(volume='volume', node='node',
1369 path='path', public_url='url1')
1370 self.sd.on_sd_public_files_changed(pf, True)
1371
1372 # update it
1373 pf = PublicFilesData(volume='volume', node='node',
1374 path='path', public_url='url2')
1375 self.sd.on_sd_public_files_changed(pf, True)
1376
1377 # check is updated
1378 pf = self.sd.public_files['node']
1379 self.assertEqual(pf.public_url, 'url2')
1380
1381 def test_public_files_changed_removed_previous(self):
1382 """Test that it deletes the public file."""
1383 # add one
1384 pf = PublicFilesData(volume='volume', node='node',
1385 path='path', public_url='url')
1386 self.sd.on_sd_public_files_changed(pf, True)
1387
1388 # remove it
1389 self.sd.on_sd_public_files_changed(pf, False)
1390
1391 # check is no longer there
1392 self.assertFalse('node' in self.sd.public_files)
1393
1394 def test_public_files_changed_removed_noprevious(self):
1395 """Test that support the deletion of anything."""
1396 # reset and remove
1397 self.sd.public_files = {}
1398 pf = PublicFilesData(volume='volume', node='node',
1399 path='path', public_url='url')
1400 self.sd.on_sd_public_files_changed(pf, False)
1401
1402 # check that still is not there
1403 self.assertFalse('node' in self.sd.public_files)
1404
1405
1406class HandlingSharesTests(BaseTest):
1407 """Handling shares checking."""
1408
1409 def test_on_share_op_success_callback_set(self):
1410 """It's mandatory to set the callback for this response."""
1411 self.sd.on_share_op_success_callback()
1412 self.assertTrue(self.hdlr.check_warning(
1413 'on_share_op_success_callback'))
1414
1415 def test_on_share_op_error_callback_set(self):
1416 """It's mandatory to set the callback for this response."""
1417 self.sd.on_share_op_error_callback()
1418 self.assertTrue(self.hdlr.check_warning('on_share_op_error_callback'))
1419
1420 def test_accept_share_ok(self):
1421 """Accepting a share finishes ok."""
1422 # monkeypatch
1423 called = []
1424 self.sd.dbus.fake_share_response = defer.succeed(None)
1425 self.sd.on_share_op_success_callback = lambda sid: called.append(sid)
1426 self.sd.on_share_op_error_callback = lambda *a: None
1427
1428 # execute and test
1429 self.sd.accept_share('share_id')
1430 self.assertEqual(called, ['share_id'])
1431 self.assertTrue(self.hdlr.check_info("Accepting share", "share_id",
1432 "started"))
1433 self.assertTrue(self.hdlr.check_info("Accepting share", "share_id",
1434 "finished successfully"))
1435
1436 def test_accept_share_failure(self):
1437 """Accepting a share finishes bad."""
1438 # monkeypatch
1439 called = []
1440 e = ShareOperationError(share_id='foo', error='bar')
1441 self.sd.dbus.fake_share_response = defer.fail(e)
1442 self.sd.on_share_op_error_callback = \
1443 lambda sid, e: called.append((sid, e))
1444 self.sd.on_share_op_success_callback = lambda *a: None
1445
1446 # execute and test
1447 self.sd.accept_share('share_id')
1448 self.assertEqual(called, [('share_id', 'bar')])
1449 self.assertTrue(self.hdlr.check_info("Accepting share", "share_id",
1450 "started"))
1451 self.assertTrue(self.hdlr.check_info("Accepting share", "share_id",
1452 "finished with error", "bar"))
1453
1454 def test_accept_share_error(self):
1455 """Really bad error when accepting a share."""
1456 # monkeypatch
1457 e = ValueError('unexpected failure')
1458 self.sd.dbus.fake_share_response = defer.fail(e)
1459 self.sd.on_share_op_error_callback = lambda *a: None
1460 self.sd.on_share_op_success_callback = lambda *a: None
1461
1462 # execute and test
1463 self.sd.accept_share('share_id')
1464 self.assertTrue(self.hdlr.check_info("Accepting share", "share_id",
1465 "started"))
1466 self.assertTrue(self.hdlr.check_error(
1467 "Unexpected error when accepting share", "share_id",
1468 "ValueError", "unexpected failure"))
9911469
=== modified file 'setup.py' (properties changed: -x to +x)
--- setup.py 2010-09-02 19:26:08 +0000
+++ setup.py 2011-01-07 23:06:32 +0000
@@ -1,13 +1,21 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2
3### BEGIN LICENSE3# Copyright 2010 Chicharreros
4# This file is in the public domain4#
5### END LICENSE5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
616
7"""Build tar.gz and related for magicicada."""17"""Build tar.gz and related for magicicada."""
818
9################# DO NOT TOUCH THIS (HEAD TO THE SECOND PART) #################
10
11import os19import os
12import sys20import sys
1321
@@ -80,17 +88,14 @@
80 update_data_path(self.prefix, previous_value)88 update_data_path(self.prefix, previous_value)
8189
8290
83##############################################################################
84#################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ####################
85##############################################################################
86
87DistUtilsExtra.auto.setup(91DistUtilsExtra.auto.setup(
88 name='magicicada',92 name='magicicada',
89 version='0.2',93 version='0.3.0',
90 license='GPL-3',94 license='GPL-3',
91 author='Natalia Bidart',95 author='Natalia Bidart',
92 author_email='natalia.bidart@ubuntu.com',96 author_email='nataliabidart@gmail.com',
93 description='A GTK+ frontend for the "Chicharra" part of Ubuntu One.',97 description='A GTK+ frontend for Ubuntu One.',
94 #long_description='Here a longer description',98 long_description='This application provides a GTK frontend to manage ' \
99 'the file synchronisation service of Ubuntu One.',
95 url='https://launchpad.net/magicicada',100 url='https://launchpad.net/magicicada',
96 cmdclass={'install': InstallAndUpdateDataDirectory})101 cmdclass={'install': InstallAndUpdateDataDirectory})

Subscribers

People subscribed via source and target branches