Merge lp:~nataliabidart/ubuntu/natty/magicicada/magicicada-0.3.0 into lp:ubuntu/natty/magicicada
- Natty (11.04)
- magicicada-0.3.0
- Merge into natty
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stefano Rivera | Approve | ||
Ubuntu Sponsors | Pending | ||
Review via email: mp+45528@code.launchpad.net |
Commit message
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
- 9. By Natalia Bidart
-
Depending on freshly released ubuntuone-client 1.5.2.
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
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?
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://
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/
> 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://
> 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 :)
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://
> 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://
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/)
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.
Preview Diff
1 | === modified file 'PKG-INFO' |
2 | --- PKG-INFO 2010-09-02 19:26:08 +0000 |
3 | +++ PKG-INFO 2011-01-07 23:06:32 +0000 |
4 | @@ -1,19 +1,11 @@ |
5 | Metadata-Version: 1.1 |
6 | Name: magicicada |
7 | -Version: 0.2 |
8 | -Summary: A GTK+ frontend for the "Chicharra" part of Ubuntu One. |
9 | +Version: 0.3.0 |
10 | +Summary: A GTK+ frontend for Ubuntu One. |
11 | Home-page: https://launchpad.net/magicicada |
12 | Author: Natalia Bidart |
13 | -Author-email: natalia.bidart@ubuntu.com |
14 | +Author-email: nataliabidart@gmail.com |
15 | License: GPL-3 |
16 | -Description: UNKNOWN |
17 | +Description: This application provides a GTK frontend to manage the file synchronisation service of Ubuntu One. |
18 | Platform: UNKNOWN |
19 | -Requires: dbus |
20 | -Requires: gobject |
21 | -Requires: gtk |
22 | -Requires: pango |
23 | -Requires: twisted.internet |
24 | -Requires: twisted.trial.unittest |
25 | -Requires: ubuntuone.syncdaemon.tools |
26 | -Requires: xdg.BaseDirectory |
27 | Provides: magicicada |
28 | |
29 | === added file 'data/media/logo-048.png' |
30 | Binary 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 |
31 | === modified file 'data/ui/gui.glade' |
32 | --- data/ui/gui.glade 2010-07-24 18:46:42 +0000 |
33 | +++ data/ui/gui.glade 2011-01-07 23:06:32 +0000 |
34 | @@ -1,4 +1,4 @@ |
35 | -<?xml version="1.0"?> |
36 | +<?xml version="1.0" encoding="UTF-8"?> |
37 | <interface> |
38 | <requires lib="gtk+" version="2.16"/> |
39 | <!-- interface-naming-policy project-wide --> |
40 | @@ -84,6 +84,18 @@ |
41 | <column type="gchararray"/> |
42 | </columns> |
43 | </object> |
44 | + <object class="GtkListStore" id="public_files_store"> |
45 | + <columns> |
46 | + <!-- column-name path --> |
47 | + <column type="gchararray"/> |
48 | + <!-- column-name public_url --> |
49 | + <column type="gchararray"/> |
50 | + <!-- column-name volume --> |
51 | + <column type="gchararray"/> |
52 | + <!-- column-name node --> |
53 | + <column type="gchararray"/> |
54 | + </columns> |
55 | + </object> |
56 | <object class="GtkWindow" id="main_window"> |
57 | <property name="width_request">800</property> |
58 | <property name="height_request">600</property> |
59 | @@ -149,10 +161,12 @@ |
60 | <object class="GtkToolbar" id="toolbar"> |
61 | <property name="visible">True</property> |
62 | <property name="toolbar_style">both</property> |
63 | + <property name="icon_size">1</property> |
64 | <child> |
65 | <object class="GtkToolButton" id="start"> |
66 | <property name="width_request">50</property> |
67 | <property name="visible">True</property> |
68 | + <property name="is_important">True</property> |
69 | <property name="label" translatable="yes">Start</property> |
70 | <property name="use_underline">True</property> |
71 | <property name="stock_id">gtk-apply</property> |
72 | @@ -166,6 +180,7 @@ |
73 | <child> |
74 | <object class="GtkToolButton" id="stop"> |
75 | <property name="width_request">50</property> |
76 | + <property name="is_important">True</property> |
77 | <property name="label" translatable="yes">Stop</property> |
78 | <property name="use_underline">True</property> |
79 | <property name="stock_id">gtk-stop</property> |
80 | @@ -181,6 +196,7 @@ |
81 | <property name="width_request">85</property> |
82 | <property name="visible">True</property> |
83 | <property name="sensitive">False</property> |
84 | + <property name="is_important">True</property> |
85 | <property name="label" translatable="yes">Connect</property> |
86 | <property name="use_underline">True</property> |
87 | <property name="stock_id">gtk-connect</property> |
88 | @@ -194,6 +210,7 @@ |
89 | <child> |
90 | <object class="GtkToolButton" id="disconnect"> |
91 | <property name="width_request">85</property> |
92 | + <property name="is_important">True</property> |
93 | <property name="label" translatable="yes">Disconnect</property> |
94 | <property name="use_underline">True</property> |
95 | <property name="stock_id">gtk-disconnect</property> |
96 | @@ -269,6 +286,20 @@ |
97 | <property name="homogeneous">True</property> |
98 | </packing> |
99 | </child> |
100 | + <child> |
101 | + <object class="GtkToolButton" id="public_files"> |
102 | + <property name="visible">True</property> |
103 | + <property name="sensitive">False</property> |
104 | + <property name="label" translatable="yes">Public Files</property> |
105 | + <property name="use_underline">True</property> |
106 | + <property name="stock_id">gtk-file</property> |
107 | + <signal name="clicked" handler="on_public_files_clicked"/> |
108 | + </object> |
109 | + <packing> |
110 | + <property name="expand">False</property> |
111 | + <property name="homogeneous">True</property> |
112 | + </packing> |
113 | + </child> |
114 | </object> |
115 | <packing> |
116 | <property name="expand">False</property> |
117 | @@ -548,7 +579,6 @@ |
118 | <object class="GtkAboutDialog" id="about_dialog"> |
119 | <property name="border_width">5</property> |
120 | <property name="type_hint">normal</property> |
121 | - <property name="has_separator">False</property> |
122 | <property name="program_name">Magicicada</property> |
123 | <property name="copyright" translatable="yes">Copyright 2010 Chicharreros |
124 | Copyright 2010 Natalia Bidart <natalia.bidart@gmail.com> |
125 | @@ -598,7 +628,6 @@ |
126 | <property name="modal">True</property> |
127 | <property name="window_position">center</property> |
128 | <property name="type_hint">normal</property> |
129 | - <property name="has_separator">False</property> |
130 | <child internal-child="vbox"> |
131 | <object class="GtkVBox" id="dialog-vbox2"> |
132 | <property name="visible">True</property> |
133 | @@ -705,7 +734,6 @@ |
134 | <property name="modal">True</property> |
135 | <property name="window_position">center</property> |
136 | <property name="type_hint">normal</property> |
137 | - <property name="has_separator">False</property> |
138 | <child internal-child="vbox"> |
139 | <object class="GtkVBox" id="dialog-vbox3"> |
140 | <property name="visible">True</property> |
141 | @@ -844,7 +872,6 @@ |
142 | <property name="modal">True</property> |
143 | <property name="window_position">center</property> |
144 | <property name="type_hint">normal</property> |
145 | - <property name="has_separator">False</property> |
146 | <child internal-child="vbox"> |
147 | <object class="GtkVBox" id="dialog-vbox5"> |
148 | <property name="visible">True</property> |
149 | @@ -981,7 +1008,6 @@ |
150 | <property name="border_width">5</property> |
151 | <property name="window_position">center</property> |
152 | <property name="type_hint">normal</property> |
153 | - <property name="has_separator">False</property> |
154 | <property name="create_folders">False</property> |
155 | <signal name="file_activated" handler="on_file_chooser_open_clicked"/> |
156 | <signal name="show" handler="on_file_chooser_show"/> |
157 | @@ -1040,4 +1066,96 @@ |
158 | <action-widget response="0">file_chooser_open</action-widget> |
159 | </action-widgets> |
160 | </object> |
161 | + <object class="GtkDialog" id="public_files_dialog"> |
162 | + <property name="width_request">600</property> |
163 | + <property name="height_request">300</property> |
164 | + <property name="border_width">5</property> |
165 | + <property name="title" translatable="yes">Public files</property> |
166 | + <property name="modal">True</property> |
167 | + <property name="window_position">center</property> |
168 | + <property name="type_hint">normal</property> |
169 | + <child internal-child="vbox"> |
170 | + <object class="GtkVBox" id="dialog-vbox9"> |
171 | + <property name="visible">True</property> |
172 | + <property name="spacing">2</property> |
173 | + <child> |
174 | + <object class="GtkScrolledWindow" id="scrolledwindow6"> |
175 | + <property name="visible">True</property> |
176 | + <property name="can_focus">True</property> |
177 | + <property name="hscrollbar_policy">automatic</property> |
178 | + <child> |
179 | + <object class="GtkTreeView" id="public_files_view"> |
180 | + <property name="visible">True</property> |
181 | + <property name="can_focus">True</property> |
182 | + <property name="model">public_files_store</property> |
183 | + <property name="headers_clickable">False</property> |
184 | + <property name="rules_hint">True</property> |
185 | + <property name="search_column">0</property> |
186 | + <property name="enable_grid_lines">both</property> |
187 | + <property name="enable_tree_lines">True</property> |
188 | + <child> |
189 | + <object class="GtkTreeViewColumn" id="public_files_path"> |
190 | + <property name="resizable">True</property> |
191 | + <property name="title">Path</property> |
192 | + <property name="expand">True</property> |
193 | + <child> |
194 | + <object class="GtkCellRendererText" id="cellrenderertext9"/> |
195 | + <attributes> |
196 | + <attribute name="text">0</attribute> |
197 | + </attributes> |
198 | + </child> |
199 | + </object> |
200 | + </child> |
201 | + <child> |
202 | + <object class="GtkTreeViewColumn" id="public_files_public_url"> |
203 | + <property name="resizable">True</property> |
204 | + <property name="title">URL</property> |
205 | + <property name="expand">True</property> |
206 | + <child> |
207 | + <object class="GtkCellRendererText" id="cellrenderertext12"/> |
208 | + <attributes> |
209 | + <attribute name="text">1</attribute> |
210 | + </attributes> |
211 | + </child> |
212 | + </object> |
213 | + </child> |
214 | + </object> |
215 | + </child> |
216 | + </object> |
217 | + <packing> |
218 | + <property name="position">1</property> |
219 | + </packing> |
220 | + </child> |
221 | + <child internal-child="action_area"> |
222 | + <object class="GtkHButtonBox" id="dialog-action_area9"> |
223 | + <property name="visible">True</property> |
224 | + <property name="layout_style">end</property> |
225 | + <child> |
226 | + <object class="GtkButton" id="public_files_close"> |
227 | + <property name="label">gtk-close</property> |
228 | + <property name="visible">True</property> |
229 | + <property name="can_focus">True</property> |
230 | + <property name="receives_default">True</property> |
231 | + <property name="use_stock">True</property> |
232 | + <signal name="clicked" handler="on_public_files_close_clicked"/> |
233 | + </object> |
234 | + <packing> |
235 | + <property name="expand">False</property> |
236 | + <property name="fill">False</property> |
237 | + <property name="position">0</property> |
238 | + </packing> |
239 | + </child> |
240 | + </object> |
241 | + <packing> |
242 | + <property name="expand">False</property> |
243 | + <property name="pack_type">end</property> |
244 | + <property name="position">0</property> |
245 | + </packing> |
246 | + </child> |
247 | + </object> |
248 | + </child> |
249 | + <action-widgets> |
250 | + <action-widget response="0">public_files_close</action-widget> |
251 | + </action-widgets> |
252 | + </object> |
253 | </interface> |
254 | |
255 | === modified file 'debian/changelog' |
256 | --- debian/changelog 2010-09-23 17:13:19 +0000 |
257 | +++ debian/changelog 2011-01-07 23:06:32 +0000 |
258 | @@ -1,3 +1,45 @@ |
259 | +magicicada (0.3.0-0ubuntu1) UNRELEASED; urgency=low |
260 | + |
261 | + * New upstream release (0.3.0): |
262 | + |
263 | + [ Natalia B. Bidart <natalia.bidart@canonical.com> ] |
264 | + - Added Public Files listing to the UI (LP: #568197). |
265 | + - The icon list is now set at creation time. This guarantees that proper |
266 | + icon sizes will be used (LP: #669947). |
267 | + - Default logging level is now INFO. |
268 | + - Volumes and metadata buttons are enabled when initial_data_ready |
269 | + callback is fired (LP: #612194). |
270 | + |
271 | + [ Facundo Batista <facundo@taniquetil.com.ar> ] |
272 | + - Backend code to accept a share. |
273 | + - Support the new "CQ changed" signal, with no data in it. |
274 | + - Log all the errors that happen inside a deferred. |
275 | + - Split "on initial data callback" in both online and offline ones. |
276 | + - Handle Public Files info. |
277 | + - Support GetDelta in the MQ (LP: #665680). |
278 | + - Leave the "don't ask while asking" machinery ok if error while asking. |
279 | + - Support an error when calling the MD (LP: #665674). |
280 | + - Don't ask a lot of times for updates to Syncdaemon on changes bursts |
281 | + (LP: #587020, #643195). |
282 | + |
283 | + * debian/control |
284 | + - Sorted Depends, dropped depends-indep |
285 | + - Added version depend restriction for python-ubuntuone-client (>= 1.5.2) |
286 | + - Added missing depend: python-xdg |
287 | + - Bumped Standards-Version to 3.9.1 (no changes needed) |
288 | + - Changed the description to match the new description in the project |
289 | + setup.py file |
290 | + |
291 | + * debian/copyright |
292 | + - Added year 2011 to copyright statement |
293 | + - Fixed email ocurrences of nataliabidart to consistently be nataliabidart |
294 | + at gmail dot com |
295 | + - Fixed formatting to be DEP5 compatible |
296 | + |
297 | + * Switched to debhelper, thanks Stefano Rivera for the patch. |
298 | + |
299 | + -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Fri, 07 Jan 2011 20:01:05 -0300 |
300 | + |
301 | magicicada (0.2-0ubuntu1) maverick; urgency=low |
302 | |
303 | [ Facundo Batista ] |
304 | |
305 | === modified file 'debian/control' |
306 | --- debian/control 2010-06-08 10:53:02 +0000 |
307 | +++ debian/control 2011-01-07 23:06:32 +0000 |
308 | @@ -1,24 +1,24 @@ |
309 | Source: magicicada |
310 | Section: python |
311 | Priority: extra |
312 | -Build-Depends: cdbs (>= 0.4.43), |
313 | - debhelper (>= 6), |
314 | - python, |
315 | - python-support (>= 0.6.4) |
316 | -Build-Depends-Indep: python-distutils-extra (>= 2.10) |
317 | -Maintainer: Elliot Murphy <elliot@ubuntu.com> |
318 | -Standards-Version: 3.8.4 |
319 | +Build-Depends: debhelper (>= 7), |
320 | + python-all (>= 2.6.5-13~), |
321 | + python-distutils-extra (>= 2.10) |
322 | +Maintainer: Natalia Bidart <nataliabidart@gmail.com> |
323 | +Standards-Version: 3.9.1 |
324 | +X-Python-Version: >= 2.5 |
325 | |
326 | Package: magicicada |
327 | Architecture: all |
328 | Depends: ${misc:Depends}, |
329 | - ${misc:Depends}, |
330 | ${python:Depends}, |
331 | + python-dbus, |
332 | python-gobject, |
333 | - python-dbus, |
334 | + python-gtk2, |
335 | + python-ubuntuone-client (>= 1.5.2), |
336 | python-twisted-core, |
337 | - python-gtk2, |
338 | - python-ubuntuone-client |
339 | + python-xdg |
340 | +Breaks: ${python:Breaks} |
341 | Description: A GTK+ frontend for Ubuntu One file sync. |
342 | - Displays diagnostic information about what the syncdaemon is doing |
343 | - on your computer. |
344 | + This application provides a GTK+ frontend to manage the file |
345 | + synchronisation service of Ubuntu One. |
346 | |
347 | === modified file 'debian/copyright' |
348 | --- debian/copyright 2010-06-08 10:53:02 +0000 |
349 | +++ debian/copyright 2011-01-07 23:06:32 +0000 |
350 | @@ -1,11 +1,19 @@ |
351 | Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat |
352 | Upstream-Name: magicicada |
353 | -Upstream-Maintainer: Natalia Bidart <natalia.bidart@ubuntu.com> |
354 | +Upstream-Maintainer: Natalia Bidart <nataliabidart@gmail.com> |
355 | Upstream-Source: https://launchpad.net/magicicada |
356 | |
357 | Files: * |
358 | -Copyright: (C) 2010 Facundo Batista <facundo@canonical.com> |
359 | -Copyright: (C) 2010 Natalia Bidart <natalia.bidart@gmail.com> |
360 | -License: GPL-3 |
361 | - The full text of the GPL is distributed in |
362 | - /usr/share/common-licenses/GPL-3 on Debian systems. |
363 | +Copyright 2010, 2011 Facundo Batista <facundo@canonical.com> Natalia Bidart <nataliabidart@gmail.com> |
364 | +License: GPL-3+ |
365 | + This program is free software; you can redistribute it and/or modify |
366 | + it under the terms of the GNU General Public License as published by |
367 | + the Free Software Foundation, version 3 of the License. |
368 | + . |
369 | + This program is distributed in the hope that it will be useful, |
370 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
371 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
372 | + General Public License for more details. |
373 | + . |
374 | + On Debian systems, the complete text of the GNU General Public License |
375 | + version 3 can be found in the /usr/share/common-licenses/GPL-3 file. |
376 | |
377 | === removed file 'debian/pycompat' |
378 | --- debian/pycompat 2010-06-08 10:53:02 +0000 |
379 | +++ debian/pycompat 1970-01-01 00:00:00 +0000 |
380 | @@ -1,1 +0,0 @@ |
381 | -2 |
382 | |
383 | === modified file 'debian/rules' |
384 | --- debian/rules 2010-09-23 17:13:19 +0000 |
385 | +++ debian/rules 2011-01-07 23:06:32 +0000 |
386 | @@ -1,5 +1,3 @@ |
387 | #!/usr/bin/make -f |
388 | -DEB_PYTHON_SYSTEM := pysupport |
389 | - |
390 | -include /usr/share/cdbs/1/rules/debhelper.mk |
391 | -include /usr/share/cdbs/1/class/python-distutils.mk |
392 | +%: |
393 | + dh $@ --with python2 |
394 | |
395 | === modified file 'magicicada/__init__.py' |
396 | --- magicicada/__init__.py 2010-08-21 18:54:31 +0000 |
397 | +++ magicicada/__init__.py 2011-01-07 23:06:32 +0000 |
398 | @@ -16,7 +16,7 @@ |
399 | # You should have received a copy of the GNU General Public License along |
400 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
401 | |
402 | -"""Magicicada.""" |
403 | +"""Magicicada GTK UI.""" |
404 | |
405 | import logging |
406 | import os |
407 | @@ -31,14 +31,15 @@ |
408 | # this shouldn't crash if not found as it is simply used for bug reporting |
409 | try: |
410 | import LaunchpadIntegration |
411 | - launchpad_available = True |
412 | + LAUNCHPAD_AVAILABLE = True |
413 | except ImportError: |
414 | - launchpad_available = False |
415 | + LAUNCHPAD_AVAILABLE = False |
416 | |
417 | from twisted.internet import gtk2reactor # for gtk-2.0 |
418 | gtk2reactor.install() |
419 | |
420 | -from magicicada import syncdaemon, dbusiface, logger as logger_helper |
421 | +from magicicada import syncdaemon, logger as logger_helper |
422 | +from magicicada.dbusiface import NOT_SYNCHED_PATH |
423 | from magicicada.helpers import humanize_bytes, get_data_file, get_builder, \ |
424 | log, NO_OP |
425 | |
426 | @@ -47,16 +48,11 @@ |
427 | UBUNTU_ONE_ROOT = os.path.expanduser('~/Ubuntu One') |
428 | |
429 | # set up the logging for all the project |
430 | +# Instance of 'RootLogger' has no 'set_up' member |
431 | +# pylint: disable=E1103 |
432 | logger_helper.set_up() |
433 | logger = logging.getLogger('magicicada.ui') |
434 | |
435 | -DEBUG = os.getenv('DEBUG') |
436 | -if DEBUG: |
437 | - console = logging.StreamHandler() |
438 | - console.setLevel(logging.DEBUG) |
439 | - logger.addHandler(console) |
440 | - |
441 | - |
442 | # Instance of 'A' has no 'y' member |
443 | # pylint: disable=E1101 |
444 | |
445 | @@ -81,7 +77,7 @@ |
446 | self.builder = get_builder('gui.glade') |
447 | self.builder.connect_signals(self) |
448 | |
449 | - if launchpad_available: |
450 | + if LAUNCHPAD_AVAILABLE: |
451 | # for more information about LaunchpadIntegration: |
452 | # wiki.ubuntu.com/UbuntuDevelopment/Internationalisation/Coding |
453 | helpmenu = self.builder.get_object('helpMenu') |
454 | @@ -105,6 +101,8 @@ |
455 | 'shares_to_others', 'shares_to_others_dialog', # shares_to_others |
456 | 'shares_to_others_view', 'shares_to_others_store', |
457 | 'shares_to_others_close', 'metadata', # metadata |
458 | + 'public_files', 'public_files_dialog', # public_files |
459 | + 'public_files_view', 'public_files_store', 'public_files_close', |
460 | 'is_started', 'is_connected', 'is_online', # status bar images |
461 | 'status_label', 'status_icon', # status label and systray icon |
462 | 'metaq_view', 'contentq_view', # queues tree views |
463 | @@ -122,18 +120,18 @@ |
464 | self._make_view_sortable('folders') |
465 | self._make_view_sortable('shares_to_me') |
466 | self._make_view_sortable('shares_to_others') |
467 | + self._make_view_sortable('public_files') |
468 | |
469 | self.metadata_dialogs = {} |
470 | self.volumes = (self.folders, self.shares_to_me, self.shares_to_others) |
471 | - self.windows = (self.main_window, self.about_dialog, |
472 | - self.folders_dialog, self.shares_to_me_dialog, |
473 | - self.shares_to_others_dialog) |
474 | |
475 | - icon_filename = get_data_file('media', 'logo-016.png') |
476 | - self._icon = gtk.gdk.pixbuf_new_from_file(icon_filename) |
477 | - self.status_icon.set_from_file(icon_filename) |
478 | - for w in self.windows: |
479 | - w.set_icon(self._icon) |
480 | + self._icons = {} |
481 | + for size in (16, 32, 48, 64, 128): |
482 | + icon_filename = get_data_file('media', 'logo-%.3i.png' % size) |
483 | + self._icons[size] = gtk.gdk.pixbuf_new_from_file(icon_filename) |
484 | + self.status_icon.set_from_pixbuf(self._icons[16]) |
485 | + self.main_window.set_icon_list(*self._icons.values()) |
486 | + gtk.window_set_default_icon_list(*self._icons.values()) |
487 | |
488 | about_fname = get_data_file('media', 'logo-128.png') |
489 | self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(about_fname)) |
490 | @@ -149,6 +147,9 @@ |
491 | self.sd.content_queue_changed_callback = self.on_content_queue_changed |
492 | self.sd.meta_queue_changed_callback = self.on_meta_queue_changed |
493 | self.sd.on_metadata_ready_callback = self.on_metadata_ready |
494 | + self.sd.on_initial_data_ready_callback = self.on_initial_data_ready |
495 | + self.sd.on_initial_online_data_ready_callback = \ |
496 | + self.on_initial_online_data_ready |
497 | |
498 | self.widget_is_visible = lambda w: w.get_property('visible') |
499 | self.widget_enabled = lambda w: self.widget_is_visible(w) and \ |
500 | @@ -160,6 +161,8 @@ |
501 | store = getattr(self, '%s_store' % view_name) |
502 | view = getattr(self, '%s_view' % view_name) |
503 | self._sorting_order[store] = {} |
504 | + # this enforces that the order that columns will be shown and sorted |
505 | + # matches the order in the underlying model |
506 | for i, col in enumerate(view.get_columns()): |
507 | col.set_clickable(True) |
508 | col.connect('clicked', self.on_store_sort_column_changed, i, store) |
509 | @@ -175,7 +178,6 @@ |
510 | dialog.set_border_width(10) |
511 | # gtk.WIN_POS_CENTER makes dialogs overlap |
512 | dialog.set_position(gtk.WIN_POS_MOUSE) |
513 | - dialog.set_icon(self._icon) |
514 | |
515 | close_button = dialog.action_area.get_children()[-1] |
516 | close_button.connect('clicked', self.on_metadata_close_clicked, path) |
517 | @@ -222,9 +224,10 @@ |
518 | |
519 | def on_stop_clicked(self, widget, data=None): |
520 | """Stop syncdaemon.""" |
521 | - for v in self.volumes: |
522 | - v.set_sensitive(False) |
523 | + for volume in self.volumes: |
524 | + volume.set_sensitive(False) |
525 | self.metadata.set_sensitive(False) |
526 | + self.public_files.set_sensitive(False) |
527 | |
528 | if self.widget_enabled(self.disconnect): |
529 | self.on_disconnect_clicked(self.disconnect) |
530 | @@ -331,6 +334,24 @@ |
531 | dialog.spinner.show() |
532 | dialog.show() |
533 | |
534 | + def on_public_files_close_clicked(self, widget, data=None): |
535 | + """Close the public_files dialog.""" |
536 | + self.public_files_dialog.response(gtk.RESPONSE_CLOSE) |
537 | + |
538 | + def on_public_files_clicked(self, widget, data=None): |
539 | + """Show information about public files.""" |
540 | + items = self.sd.public_files |
541 | + if items is None: |
542 | + items = {} |
543 | + |
544 | + self.public_files_store.clear() |
545 | + for item in items.itervalues(): |
546 | + row = (item.path, item.public_url, item.volume, item.node) |
547 | + self.public_files_store.append(row) |
548 | + |
549 | + self.public_files_dialog.run() |
550 | + self.public_files_dialog.hide() |
551 | + |
552 | def on_status_icon_activate(self, widget, data=None): |
553 | """Systray icon was clicked.""" |
554 | if self.widget_is_visible(self.main_window): |
555 | @@ -370,10 +391,6 @@ |
556 | self._activate_indicator(self.is_started) |
557 | self.connect.set_sensitive(True) |
558 | |
559 | - for v in self.volumes: |
560 | - v.set_sensitive(True) |
561 | - self.metadata.set_sensitive(True) |
562 | - |
563 | self._update_queues_and_status(self.sd.current_state) |
564 | |
565 | @log(logger) |
566 | @@ -482,13 +499,25 @@ |
567 | dialog = self.metadata_dialogs[path] |
568 | dialog.spinner.hide() |
569 | dialog.view.show() |
570 | - if metadata == dbusiface.NOT_SYNCHED_PATH: |
571 | + if metadata == NOT_SYNCHED_PATH: |
572 | # Metadata path doesn't exsit for syncdaemon |
573 | - text = dbusiface.NOT_SYNCHED_PATH |
574 | + text = NOT_SYNCHED_PATH |
575 | else: |
576 | text = '\n'.join('%s: %s' % i for i in metadata.iteritems()) |
577 | dialog.view.get_buffer().set_text(text) |
578 | |
579 | + @log(logger, level=logging.INFO) |
580 | + def on_initial_data_ready(self): |
581 | + """Initial data is now available in syncdaemon.""" |
582 | + for volume in self.volumes: |
583 | + volume.set_sensitive(True) |
584 | + self.metadata.set_sensitive(True) |
585 | + |
586 | + @log(logger, level=logging.INFO) |
587 | + def on_initial_online_data_ready(self): |
588 | + """Online initial data is now available in syncdaemon.""" |
589 | + self.public_files.set_sensitive(True) |
590 | + |
591 | # custom |
592 | |
593 | def _start_loading(self, what): |
594 | |
595 | === modified file 'magicicada/dbusiface.py' |
596 | --- magicicada/dbusiface.py 2010-08-23 16:48:07 +0000 |
597 | +++ magicicada/dbusiface.py 2011-01-07 23:06:32 +0000 |
598 | @@ -27,7 +27,17 @@ |
599 | from dbus.mainloop.glib import DBusGMainLoop |
600 | from twisted.internet import defer |
601 | |
602 | -from ubuntuone.syncdaemon.tools import SyncDaemonTool |
603 | +try: |
604 | + # yes, they can be imported! pylint: disable=F0401,E0611 |
605 | + from ubuntuone.platform.tools import SyncDaemonTool, DBusClient |
606 | + from ubuntuone.platform.dbus_interface import DBUS_IFACE_PUBLIC_FILES_NAME |
607 | +except ImportError: |
608 | + # support for old structure (pre-Natty) |
609 | + # yes, they can be imported! pylint: disable=F0401,E0611 |
610 | + from ubuntuone.syncdaemon.tools import SyncDaemonTool, DBusClient |
611 | + from ubuntuone.syncdaemon.dbus_interface import ( |
612 | + DBUS_IFACE_PUBLIC_FILES_NAME, |
613 | + ) |
614 | |
615 | # log! |
616 | logger = logging.getLogger('magicicada.dbusiface') |
617 | @@ -40,6 +50,8 @@ |
618 | ShareData = collections.namedtuple('ShareData', 'accepted access_level ' |
619 | 'free_bytes name node_id other_username ' |
620 | 'other_visible_name path volume_id') |
621 | +PublicFilesData = collections.namedtuple('PublicFilesData', |
622 | + 'volume node path public_url') |
623 | |
624 | # regular expressions for parsing MetaQueue data |
625 | RE_OP_LISTDIR = re.compile("(ListDir)\(share_id=(.*?), node_id=(.*?), .*") |
626 | @@ -61,6 +73,15 @@ |
627 | NOT_SYNCHED_PATH = "Not a valid path!" |
628 | |
629 | |
630 | +# some exceptions |
631 | +class ShareOperationError(Exception): |
632 | + """Error on an operation on a share.""" |
633 | + def __init__(self, share_id, error): |
634 | + self.share_id = share_id |
635 | + self.error = error |
636 | + Exception.__init__(self) |
637 | + |
638 | + |
639 | def _is_retry_exception(err): |
640 | """Check if the exception is a retry one.""" |
641 | if isinstance(err, dbus.exceptions.DBusException): |
642 | @@ -97,6 +118,7 @@ |
643 | # magicicada's syncdaemon |
644 | self.msd = msd |
645 | logger.info("DBus interface starting") |
646 | + self._public_files_deferred = None |
647 | |
648 | # set up dbus and related stuff |
649 | loop = DBusGMainLoop(set_as_default=True) |
650 | @@ -116,6 +138,9 @@ |
651 | (self._on_share_created, 'Shares', 'ShareCreated'), |
652 | (self._on_share_deleted, 'Shares', 'ShareDeleted'), |
653 | (self._on_share_changed, 'Shares', 'ShareChanged'), |
654 | + (self._on_public_files_list, 'PublicFiles', 'PublicFilesList'), |
655 | + (self._on_public_files_changed, 'PublicFiles', |
656 | + 'PublicAccessChanged'), |
657 | ] |
658 | self._dbus_matches = [] |
659 | for method, dbus_lastname, signal_name in _signals: |
660 | @@ -164,7 +189,7 @@ |
661 | data = self._process_status(state) |
662 | self.msd.on_sd_status_changed(*data) |
663 | |
664 | - def _on_content_queue_changed(self, _): |
665 | + def _on_content_queue_changed(self, _=None): |
666 | """Call the SD callback.""" |
667 | logger.info("Received Content Queue changed") |
668 | self.msd.on_sd_content_queue_changed() |
669 | @@ -223,6 +248,57 @@ |
670 | logger.info("Received Share changed") |
671 | self.msd.on_sd_shares_changed() |
672 | |
673 | + def _on_public_files_changed(self, data): |
674 | + """Call the SD callback.""" |
675 | + logger.debug("Received Public Files changed: %s", data) |
676 | + pf = PublicFilesData(volume=data['share_id'], node=data['node_id'], |
677 | + path=data['path'], public_url=data['public_url']) |
678 | + is_public = bool(data['is_public']) |
679 | + self.msd.on_sd_public_files_changed(pf, is_public) |
680 | + |
681 | + def _on_public_files_list(self, data): |
682 | + """Call the SD callback.""" |
683 | + logger.info("Received Public Files list (%d)", len(data)) |
684 | + processed = [] |
685 | + for d in data: |
686 | + logger.debug(" Public Files data: %s", d) |
687 | + p = PublicFilesData(volume=d['volume_id'], node=d['node_id'], |
688 | + path=d['path'], public_url=d['public_url']) |
689 | + processed.append(p) |
690 | + |
691 | + # trigger the deferred if have one, else call the msd with the data |
692 | + if self._public_files_deferred is None: |
693 | + self.msd.on_sd_public_files_list(processed) |
694 | + else: |
695 | + d = self._public_files_deferred |
696 | + self._public_files_deferred = None |
697 | + d.callback(processed) |
698 | + |
699 | + def get_public_files(self): |
700 | + """Ask the Public Files info to syncdaemon.""" |
701 | + client = DBusClient(self._bus, '/publicfiles', |
702 | + DBUS_IFACE_PUBLIC_FILES_NAME) |
703 | + |
704 | + # note that these callbacks do not come with the requested info, the |
705 | + # method just will return None, and the real info will come later |
706 | + # in a signal |
707 | + |
708 | + def call_done(result): |
709 | + """Call was succesful.""" |
710 | + logger.debug("Public files asked ok.") |
711 | + |
712 | + def call_error(error): |
713 | + """Call was not succesful.""" |
714 | + logger.error("Public files asked with error: %s", error) |
715 | + |
716 | + client.call_method('get_public_files', |
717 | + reply_handler=call_done, error_handler=call_error) |
718 | + |
719 | + # return a stored deferred that will be triggered later with the signal |
720 | + d = defer.Deferred() |
721 | + self._public_files_deferred = d |
722 | + return d |
723 | + |
724 | @retryable |
725 | def get_content_queue(self): |
726 | """Get the content queue from SDT.""" |
727 | @@ -293,7 +369,7 @@ |
728 | """Use MetaQueue dictionary and prepare command data.""" |
729 | if op in ('AccountInquiry', 'FreeSpaceInquiry', 'Query', 'ListShares', |
730 | 'GetPublicFiles', 'ListVolumes', 'ChangePublicAccess', |
731 | - 'AnswerShare'): |
732 | + 'AnswerShare', 'GetDelta'): |
733 | return QueueData(operation=op, path=None, node=None, share=None) |
734 | |
735 | if op in ('ListDir', 'Unlink'): |
736 | @@ -473,3 +549,19 @@ |
737 | d = self.sync_daemon_tool.get_metadata(path) |
738 | d.addCallbacks(process, fix_failure) |
739 | return d |
740 | + |
741 | + @retryable |
742 | + @defer.inlineCallbacks |
743 | + def accept_share(self, share_id): |
744 | + """Accept a share.""" |
745 | + logger.debug("Accept share %s started", share_id) |
746 | + try: |
747 | + result = yield self.sync_daemon_tool.accept_share(share_id) |
748 | + except dbus.exceptions.DBusException, e: |
749 | + error = str(e.args[0]) |
750 | + logger.debug("Accept share %s crashed: %s", share_id, error) |
751 | + raise ShareOperationError(share_id=share_id, error=error) |
752 | + |
753 | + logger.debug("Accept share %s finished: %s", share_id, result) |
754 | + if 'error' in result: |
755 | + raise ShareOperationError(share_id=share_id, error=result['error']) |
756 | |
757 | === modified file 'magicicada/helpers.py' |
758 | --- magicicada/helpers.py 2010-07-24 19:37:32 +0000 |
759 | +++ magicicada/helpers.py 2011-01-07 23:06:32 +0000 |
760 | @@ -1,21 +1,32 @@ |
761 | # -*- coding: utf-8 -*- |
762 | -### BEGIN LICENSE |
763 | -# This file is in the public domain |
764 | -### END LICENSE |
765 | +# |
766 | +# Author: Natalia Bidart <natalia.bidart@gmail.com> |
767 | +# |
768 | +# Copyright 2010 Chicharreros |
769 | +# |
770 | +# This program is free software: you can redistribute it and/or modify it |
771 | +# under the terms of the GNU General Public License version 3, as published |
772 | +# by the Free Software Foundation. |
773 | +# |
774 | +# This program is distributed in the hope that it will be useful, but |
775 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
776 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
777 | +# PURPOSE. See the GNU General Public License for more details. |
778 | +# |
779 | +# You should have received a copy of the GNU General Public License along |
780 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
781 | |
782 | """Helpers for an Ubuntu application.""" |
783 | |
784 | from __future__ import division |
785 | |
786 | -__all__ = [ |
787 | - 'make_window', |
788 | - ] |
789 | - |
790 | -import gtk |
791 | +import logging |
792 | import os |
793 | |
794 | from functools import wraps |
795 | |
796 | +import gtk |
797 | + |
798 | from magicicada.magicicadaconfig import get_data_file |
799 | |
800 | |
801 | @@ -39,7 +50,7 @@ |
802 | return builder |
803 | |
804 | |
805 | -def log(logger): |
806 | +def log(logger, level=logging.DEBUG): |
807 | """Log input/ouput info for 'f' using 'logger'.""" |
808 | |
809 | def decorator(f): |
810 | @@ -50,13 +61,14 @@ |
811 | """Wrap 'f', log input args and result using 'logger'.""" |
812 | name = f.__name__ |
813 | result = None |
814 | - logger.debug("Calling '%s' with args '%s' and kwargs '%s'.", |
815 | - name, args, kwargs) |
816 | + logger.log(level, "Calling '%s' with args '%s' and kwargs '%s'.", |
817 | + name, args, kwargs) |
818 | try: |
819 | result = f(*args, **kwargs) |
820 | except Exception: # pylint: disable=W0703 |
821 | logger.exception('%s failed with exception:', name) |
822 | - logger.debug("Returning from '%s' with result '%s'.", name, result) |
823 | + logger.log(level, "Returning from '%s' with result '%s'.", |
824 | + name, result) |
825 | return result |
826 | |
827 | return inner |
828 | @@ -91,11 +103,11 @@ |
829 | '1.3 GB' |
830 | """ |
831 | abbrevs = ( |
832 | - (1<<50L, 'PB'), |
833 | - (1<<40L, 'TB'), |
834 | - (1<<30L, 'GB'), |
835 | - (1<<20L, 'MB'), |
836 | - (1<<10L, 'kB'), |
837 | + (1 << 50, 'PB'), |
838 | + (1 << 40, 'TB'), |
839 | + (1 << 30, 'GB'), |
840 | + (1 << 20, 'MB'), |
841 | + (1 << 10, 'kB'), |
842 | (1, 'bytes')) |
843 | |
844 | if numbytes == 1: |
845 | |
846 | === modified file 'magicicada/logger.py' |
847 | --- magicicada/logger.py 2010-08-18 21:17:20 +0000 |
848 | +++ magicicada/logger.py 2011-01-07 23:06:32 +0000 |
849 | @@ -26,6 +26,7 @@ |
850 | |
851 | from logging.handlers import RotatingFileHandler |
852 | |
853 | +import twisted.python.log |
854 | import xdg.BaseDirectory |
855 | |
856 | |
857 | @@ -37,21 +38,35 @@ |
858 | self.doRollover() |
859 | |
860 | |
861 | +def deferror_handler(data): |
862 | + """Deferred error handler. |
863 | + |
864 | + We receive all stuff here, filter the errors and use correct info. Note |
865 | + that we don't send to stderr as Twisted already does that. |
866 | + """ |
867 | + try: |
868 | + failure = data['failure'] |
869 | + except KeyError: |
870 | + msg = data['message'] |
871 | + else: |
872 | + msg = failure.getTraceback() |
873 | + logger = logging.getLogger('magicicada') |
874 | + logger.error("Unhandled error in deferred!\n%s", msg) |
875 | + |
876 | + |
877 | def exception_handler(exc_type, exc_value, tb): |
878 | """Handle an unhandled exception.""" |
879 | - # stderr |
880 | exception = traceback.format_exception(exc_type, exc_value, tb) |
881 | - exception = "".join(exception) |
882 | - print >> sys.stderr, exception |
883 | + msg = "".join(exception) |
884 | + print >> sys.stderr, msg |
885 | |
886 | # log |
887 | logger = logging.getLogger('magicicada') |
888 | - logger.error("Unhandled exception!\n%s", exception) |
889 | + logger.error("Unhandled exception!\n%s", msg) |
890 | |
891 | |
892 | def set_up(): |
893 | """Set up the logging.""" |
894 | - |
895 | # choose the folder to store the logs |
896 | cache = xdg.BaseDirectory.xdg_cache_home |
897 | logfolder = os.path.join(cache, 'magicicada') |
898 | @@ -62,11 +77,20 @@ |
899 | logger = logging.getLogger('magicicada') |
900 | handler = CustomRotatingFH(logfile, maxBytes=1e6, backupCount=10) |
901 | logger.addHandler(handler) |
902 | - formatter = logging.Formatter("%(asctime)s %(name)-23s" |
903 | - "%(levelname)-8s %(message)s", |
904 | - '%Y-%m-%d %H:%M:%S') |
905 | + formatter = logging.Formatter("%(asctime)s %(name)-23s" |
906 | + "%(levelname)-8s %(message)s") |
907 | handler.setFormatter(formatter) |
908 | - logger.setLevel(logging.DEBUG) |
909 | + logger.setLevel(logging.INFO) |
910 | + |
911 | + if os.getenv('DEBUG'): |
912 | + console = logging.StreamHandler() |
913 | + console.setLevel(logging.DEBUG) |
914 | + console.setFormatter(formatter) |
915 | + logger.addHandler(console) |
916 | + logger.setLevel(logging.DEBUG) |
917 | |
918 | # hook the exception handler |
919 | sys.excepthook = exception_handler |
920 | + |
921 | + # hook the twisted observer |
922 | + twisted.python.log.addObserver(deferror_handler) |
923 | |
924 | === modified file 'magicicada/syncdaemon.py' |
925 | --- magicicada/syncdaemon.py 2010-09-02 18:23:02 +0000 |
926 | +++ magicicada/syncdaemon.py 2011-01-07 23:06:32 +0000 |
927 | @@ -23,13 +23,24 @@ |
928 | |
929 | from twisted.internet import defer, reactor |
930 | |
931 | -from magicicada.dbusiface import DBusInterface |
932 | +from magicicada.dbusiface import DBusInterface, ShareOperationError |
933 | from magicicada.helpers import NO_OP |
934 | |
935 | - |
936 | # log! |
937 | logger = logging.getLogger('magicicada.syncdaemon') |
938 | |
939 | +# states for MQ and CQ handling bursts |
940 | +ASKING_IDLE, ASKING_YES, ASKING_LATER = range(3) |
941 | + |
942 | + |
943 | +def mandatory_callback(function_name): |
944 | + """Log that the callback was not overwritten.""" |
945 | + def f(*a, **k): |
946 | + """Fake callback.""" |
947 | + logger.warning("Callback called but was not assigned! " |
948 | + "%r called with %s %s", function_name, a, k) |
949 | + return f |
950 | + |
951 | |
952 | class State(object): |
953 | """Hold the state of SD.""" |
954 | @@ -93,6 +104,7 @@ |
955 | self.shares_to_others = None |
956 | self.content_queue = None |
957 | self.meta_queue = None |
958 | + self.public_files = None |
959 | |
960 | # callbacks for GUI to hook in |
961 | self.status_changed_callback = NO_OP |
962 | @@ -107,8 +119,14 @@ |
963 | self.on_folders_changed_callback = NO_OP |
964 | self.on_shares_to_me_changed_callback = NO_OP |
965 | self.on_shares_to_others_changed_callback = NO_OP |
966 | - self.on_metadata_ready_callback = None # mandatory |
967 | + self.on_metadata_ready_callback = mandatory_callback( |
968 | + 'on_metadata_ready_callback') |
969 | self.on_initial_data_ready_callback = NO_OP |
970 | + self.on_initial_online_data_ready_callback = NO_OP |
971 | + self.on_share_op_success_callback = mandatory_callback( |
972 | + 'on_share_op_success_callback') |
973 | + self.on_share_op_error_callback = mandatory_callback( |
974 | + 'on_share_op_error_callback') |
975 | |
976 | # mq needs to (maybe) be polled to know progress |
977 | self._must_poll_mq = True |
978 | @@ -122,6 +140,10 @@ |
979 | else: |
980 | self.current_state.set(is_started=False) |
981 | |
982 | + # mq and cq burst handling |
983 | + self._cq_asking = ASKING_IDLE |
984 | + self._mq_asking = ASKING_IDLE |
985 | + |
986 | def shutdown(self): |
987 | """Shut down the SyncDaemon.""" |
988 | logger.info("SyncDaemon interface going down") |
989 | @@ -134,7 +156,7 @@ |
990 | @defer.inlineCallbacks |
991 | def _get_initial_data(self): |
992 | """Get the initial SD data.""" |
993 | - logger.info("Getting initial data") |
994 | + logger.info("Getting offline initial data") |
995 | |
996 | status_data = yield self.dbus.get_status() |
997 | self._send_status_changed(*status_data) |
998 | @@ -150,10 +172,34 @@ |
999 | self.shares_to_me = yield self.dbus.get_shares_to_me() |
1000 | self.shares_to_others = yield self.dbus.get_shares_to_others() |
1001 | |
1002 | - # let frontend know that we have all the initial data |
1003 | - logger.info("All initial data is ready") |
1004 | + # let frontend know that we have all the initial offline data |
1005 | + logger.info("All initial offline data is ready") |
1006 | self.on_initial_data_ready_callback() |
1007 | |
1008 | + logger.info("Getting online initial data") |
1009 | + pf_list = yield self.dbus.get_public_files() |
1010 | + self.on_sd_public_files_list(pf_list) |
1011 | + |
1012 | + # let frontend know that we have all the initial online data |
1013 | + logger.info("All initial online data is ready") |
1014 | + self.on_initial_online_data_ready_callback() |
1015 | + |
1016 | + def on_sd_public_files_list(self, data): |
1017 | + """Set a new Public Files list.""" |
1018 | + logger.info("Got new Public Files list (%d items)", len(data)) |
1019 | + self.public_files = dict((d.node, d) for d in data) |
1020 | + |
1021 | + def on_sd_public_files_changed(self, pf, is_public): |
1022 | + """Update the Public Files list.""" |
1023 | + logger.info("Change in Public Files list! is_public=%s (volume=%s " |
1024 | + "node=%s url=%s path=%r)", is_public, pf.volume, pf.node, |
1025 | + pf.public_url, pf.path) |
1026 | + if is_public: |
1027 | + self.public_files[pf.node] = pf |
1028 | + else: |
1029 | + # remove it if there |
1030 | + self.public_files.pop(pf.node, None) |
1031 | + |
1032 | @defer.inlineCallbacks |
1033 | def on_sd_shares_changed(self): |
1034 | """Shares changed, ask for new information.""" |
1035 | @@ -234,19 +280,47 @@ |
1036 | @defer.inlineCallbacks |
1037 | def on_sd_content_queue_changed(self): |
1038 | """Content Queue changed, ask for new information.""" |
1039 | - logger.info("SD Content Queue changed") |
1040 | - new_cq = yield self.dbus.get_content_queue() |
1041 | - if new_cq != self.content_queue: |
1042 | - logger.info("Content Queue info is new! %d items", len(new_cq)) |
1043 | - self.content_queue = new_cq |
1044 | - self.content_queue_changed_callback(new_cq) |
1045 | + if self._cq_asking != ASKING_IDLE: |
1046 | + self._cq_asking = ASKING_LATER |
1047 | + logger.info("SD Content Queue changed, but already asking") |
1048 | + return |
1049 | + |
1050 | + logger.info("SD Content Queue changed, asking for info") |
1051 | + self._cq_asking = ASKING_YES |
1052 | + while self._cq_asking != ASKING_IDLE: |
1053 | + new_cq = yield self.dbus.get_content_queue() |
1054 | + if new_cq != self.content_queue: |
1055 | + logger.info("Content Queue info is new! %d items", len(new_cq)) |
1056 | + self.content_queue = new_cq |
1057 | + self.content_queue_changed_callback(new_cq) |
1058 | + if self._cq_asking == ASKING_LATER: |
1059 | + self._cq_asking = ASKING_YES |
1060 | + else: |
1061 | + self._cq_asking = ASKING_IDLE |
1062 | |
1063 | @defer.inlineCallbacks |
1064 | def on_sd_meta_queue_changed(self): |
1065 | """Meta Queue changed, ask for new information.""" |
1066 | - logger.info("SD Meta Queue changed") |
1067 | + if self._mq_asking != ASKING_IDLE: |
1068 | + self._mq_asking = ASKING_LATER |
1069 | + logger.info("SD Meta Queue changed, but already asking") |
1070 | + return |
1071 | + |
1072 | + logger.info("SD Meta Queue changed, asking for info") |
1073 | self._must_poll_mq = False |
1074 | - yield self._get_mq_data() |
1075 | + self._mq_asking = ASKING_YES |
1076 | + while self._mq_asking != ASKING_IDLE: |
1077 | + try: |
1078 | + yield self._get_mq_data() |
1079 | + except Exception: |
1080 | + # on any error, leave the state sane |
1081 | + self._mq_asking = ASKING_IDLE |
1082 | + raise |
1083 | + |
1084 | + if self._mq_asking == ASKING_LATER: |
1085 | + self._mq_asking = ASKING_YES |
1086 | + else: |
1087 | + self._mq_asking = ASKING_IDLE |
1088 | |
1089 | @defer.inlineCallbacks |
1090 | def _get_mq_data(self): |
1091 | @@ -274,6 +348,8 @@ |
1092 | self._get_mq_data() |
1093 | |
1094 | if self._must_poll_mq: |
1095 | + # Module 'twisted.internet.reactor' has no 'callLater' member |
1096 | + # pylint: disable=E1101 |
1097 | self._mqcaller = reactor.callLater(self._mq_poll_time, |
1098 | self._check_mq) |
1099 | |
1100 | @@ -300,8 +376,27 @@ |
1101 | |
1102 | def get_metadata(self, path): |
1103 | """Get the metadata for given path.""" |
1104 | - if self.on_metadata_ready_callback is None: |
1105 | - raise ValueError("Missing the mandatory cback for get_metadata.") |
1106 | - |
1107 | d = self.dbus.get_metadata(os.path.realpath(path)) |
1108 | d.addCallback(lambda resp: self.on_metadata_ready_callback(path, resp)) |
1109 | + |
1110 | + def accept_share(self, share_id): |
1111 | + """Accept a share.""" |
1112 | + def error(failure): |
1113 | + """Operation failed.""" |
1114 | + if failure.check(ShareOperationError): |
1115 | + error = failure.value.error |
1116 | + logger.info("Accepting share %s finished with error: %s", |
1117 | + share_id, error) |
1118 | + self.on_share_op_error_callback(share_id, error) |
1119 | + else: |
1120 | + logger.error("Unexpected error when accepting share %s: %s %s", |
1121 | + share_id, failure.type, failure.value) |
1122 | + |
1123 | + def success(_): |
1124 | + """Operation finished ok.""" |
1125 | + logger.info("Accepting share %s finished successfully", share_id) |
1126 | + self.on_share_op_success_callback(share_id) |
1127 | + |
1128 | + logger.info("Accepting share %s started", share_id) |
1129 | + d = self.dbus.accept_share(share_id) |
1130 | + d.addCallbacks(success, error) |
1131 | |
1132 | === modified file 'magicicada/tests/helpers.py' |
1133 | --- magicicada/tests/helpers.py 2010-08-18 21:04:16 +0000 |
1134 | +++ magicicada/tests/helpers.py 2011-01-07 23:06:32 +0000 |
1135 | @@ -28,6 +28,7 @@ |
1136 | """Create the instance, and add a records attribute.""" |
1137 | logging.Handler.__init__(self, *args, **kwargs) |
1138 | self.records = [] |
1139 | + self.debug = False |
1140 | |
1141 | def emit(self, record): |
1142 | """Just add the record to self.records.""" |
1143 | @@ -36,18 +37,26 @@ |
1144 | def check(self, level, *msgs): |
1145 | """Check that something is logged.""" |
1146 | for rec in self.records: |
1147 | - if rec.levelname == level and all(m in rec.message for m in msgs): |
1148 | + if rec.levelno == level and all(m in rec.message for m in msgs): |
1149 | return True |
1150 | + if self.debug: |
1151 | + recorded = [(logging.getLevelName(r.levelno), r.message) |
1152 | + for r in self.records] |
1153 | + print "Memento messages:", recorded |
1154 | return False |
1155 | |
1156 | def check_error(self, *msgs): |
1157 | """Shortcut for ERROR check.""" |
1158 | - return self.check('ERROR', *msgs) |
1159 | + return self.check(logging.ERROR, *msgs) |
1160 | + |
1161 | + def check_warning(self, *msgs): |
1162 | + """Shortcut for WARNING check.""" |
1163 | + return self.check(logging.WARNING, *msgs) |
1164 | |
1165 | def check_info(self, *msgs): |
1166 | """Shortcut for INFO check.""" |
1167 | - return self.check('INFO', *msgs) |
1168 | + return self.check(logging.INFO, *msgs) |
1169 | |
1170 | def check_debug(self, *msgs): |
1171 | """Shortcut for DEBUG check.""" |
1172 | - return self.check('DEBUG', *msgs) |
1173 | + return self.check(logging.DEBUG, *msgs) |
1174 | |
1175 | === modified file 'magicicada/tests/test_dbusiface.py' |
1176 | --- magicicada/tests/test_dbusiface.py 2010-08-23 16:48:07 +0000 |
1177 | +++ magicicada/tests/test_dbusiface.py 2011-01-07 23:06:32 +0000 |
1178 | @@ -27,11 +27,38 @@ |
1179 | from magicicada import dbusiface |
1180 | from magicicada.tests.helpers import MementoHandler |
1181 | |
1182 | +try: |
1183 | + # yes, they can be imported! pylint: disable=F0401,E0611 |
1184 | + from ubuntuone.platform.dbus_interface import DBUS_IFACE_PUBLIC_FILES_NAME |
1185 | +except ImportError: |
1186 | + # support for old structure (pre-Natty) |
1187 | + # yes, they can be imported! pylint: disable=F0401,E0611 |
1188 | + from ubuntuone.syncdaemon.dbus_interface import ( |
1189 | + DBUS_IFACE_PUBLIC_FILES_NAME, |
1190 | + ) |
1191 | + |
1192 | |
1193 | # It's ok to access private data in the test suite |
1194 | # pylint: disable=W0212 |
1195 | |
1196 | |
1197 | +class FakeDBusClient(object): |
1198 | + """Fake DBusClient, but with different behaviour at instantiation time.""" |
1199 | + |
1200 | + def __init__(self, *args): |
1201 | + self.init_args = None |
1202 | + self.method_call_args = None |
1203 | + |
1204 | + def call_method(self, *args, **kwargs): |
1205 | + """Record the arguments of the method call.""" |
1206 | + self.method_call_args = args, kwargs |
1207 | + |
1208 | + def __call__(self, *args): |
1209 | + """Simulate instantiation.""" |
1210 | + self.init_args = args |
1211 | + return self |
1212 | + |
1213 | + |
1214 | class FakeSessionBus(object): |
1215 | """Fake Session Bus.""" |
1216 | |
1217 | @@ -59,6 +86,9 @@ |
1218 | else: |
1219 | raise self.fake_name_owner |
1220 | |
1221 | + def send_message_with_reply(self, *a, **k): |
1222 | + """Fake.""" |
1223 | + |
1224 | |
1225 | class CallLoguer(object): |
1226 | """Class that logs the methods called.""" |
1227 | @@ -104,6 +134,12 @@ |
1228 | |
1229 | def setUp(self): |
1230 | """Set up.""" |
1231 | + self.handler = MementoHandler() |
1232 | + self.handler.setLevel(logging.DEBUG) |
1233 | + logger = logging.getLogger('magicicada.dbusiface') |
1234 | + logger.addHandler(self.handler) |
1235 | + logger.setLevel(logging.DEBUG) |
1236 | + |
1237 | dbusiface.SessionBus = FakeSessionBus |
1238 | dbusiface.SyncDaemonTool = FakeSDTool |
1239 | self.fsd = FakeSyncDaemon() |
1240 | @@ -199,6 +235,17 @@ |
1241 | self.assertEqual(self._get_hooked('Shares', 'ShareChanged'), |
1242 | self.dbus._on_share_changed) |
1243 | |
1244 | + def test_public_files_list(self): |
1245 | + """Test public files list callback.""" |
1246 | + self.assertEqual(self._get_hooked('PublicFiles', 'PublicFilesList'), |
1247 | + self.dbus._on_public_files_list) |
1248 | + |
1249 | + def test_public_files_changed(self): |
1250 | + """Test public files changed callback.""" |
1251 | + self.assertEqual(self._get_hooked('PublicFiles', |
1252 | + 'PublicAccessChanged'), |
1253 | + self.dbus._on_public_files_changed) |
1254 | + |
1255 | |
1256 | class TestSimpleCalls(SafeTests): |
1257 | """Tests for some simple calls.""" |
1258 | @@ -263,13 +310,13 @@ |
1259 | self.dbus._on_name_owner_changed("foo", "bar", "baz") |
1260 | self.get_msd_called(None) |
1261 | |
1262 | - def test_name_owner_changed_yes_syncdaemon_TF(self): |
1263 | + def test_name_owner_changed_yes_syncdaemon_true_false(self): |
1264 | """Test name owner changed callback.""" |
1265 | self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "T", "") |
1266 | rcv, = self.get_msd_called("on_sd_name_owner_changed") |
1267 | self.assertEqual(rcv, False) |
1268 | |
1269 | - def test_name_owner_changed_yes_syncdaemon_FT(self): |
1270 | + def test_name_owner_changed_yes_syncdaemon_false_true(self): |
1271 | """Test name owner changed callback.""" |
1272 | self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "", "T") |
1273 | rcv, = self.get_msd_called("on_sd_name_owner_changed") |
1274 | @@ -279,11 +326,16 @@ |
1275 | class TestDataProcessingCQ(SafeTests): |
1276 | """Process CQ data before sending it to SyncDaemon.""" |
1277 | |
1278 | - def test_content_queue_changed_signal(self): |
1279 | - """Test content queue changed signal.""" |
1280 | + def test_content_queue_changed_signal_withsomething(self): |
1281 | + """Test content queue changed signal, old version with data.""" |
1282 | self.dbus._on_content_queue_changed(None) |
1283 | self.get_msd_called("on_sd_content_queue_changed") |
1284 | |
1285 | + def test_content_queue_changed_signal_nodata(self): |
1286 | + """Test content queue changed signal new version (just the signal).""" |
1287 | + self.dbus._on_content_queue_changed() |
1288 | + self.get_msd_called("on_sd_content_queue_changed") |
1289 | + |
1290 | @defer.inlineCallbacks |
1291 | def test_nodata(self): |
1292 | """Test with no data in the queue.""" |
1293 | @@ -371,7 +423,7 @@ |
1294 | self.assertEqual(data.node, None) |
1295 | |
1296 | @defer.inlineCallbacks |
1297 | - def test_GetPublicFiles_old(self): |
1298 | + def test_getpublicfiles_old(self): |
1299 | """Test meta with GetPublicFiles.""" |
1300 | cmd = 'GetPublicFiles' |
1301 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1302 | @@ -383,7 +435,7 @@ |
1303 | self.assertEqual(data.node, None) |
1304 | |
1305 | @defer.inlineCallbacks |
1306 | - def test_AccountInquiry_old(self): |
1307 | + def test_accountinquiry_old(self): |
1308 | """Test meta with AccountInquiry.""" |
1309 | cmd = 'AccountInquiry' |
1310 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1311 | @@ -395,7 +447,7 @@ |
1312 | self.assertEqual(data.node, None) |
1313 | |
1314 | @defer.inlineCallbacks |
1315 | - def test_FreeSpaceInquiry_old(self): |
1316 | + def test_freespaceinquiry_old(self): |
1317 | """Test meta with FreeSpaceInquiry.""" |
1318 | cmd = 'FreeSpaceInquiry' |
1319 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1320 | @@ -407,7 +459,7 @@ |
1321 | self.assertEqual(data.node, None) |
1322 | |
1323 | @defer.inlineCallbacks |
1324 | - def test_ListShares_old(self): |
1325 | + def test_listshares_old(self): |
1326 | """Test meta with ListShares.""" |
1327 | cmd = 'ListShares' |
1328 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1329 | @@ -419,7 +471,7 @@ |
1330 | self.assertEqual(data.node, None) |
1331 | |
1332 | @defer.inlineCallbacks |
1333 | - def test_ListVolumes_old(self): |
1334 | + def test_listvolumes_old(self): |
1335 | """Test meta with ListVolumes.""" |
1336 | cmd = 'ListVolumes' |
1337 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1338 | @@ -431,7 +483,7 @@ |
1339 | self.assertEqual(data.node, None) |
1340 | |
1341 | @defer.inlineCallbacks |
1342 | - def test_Query_old(self): |
1343 | + def test_query_old(self): |
1344 | """Test meta with Query.""" |
1345 | cmd = 'Query' |
1346 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1347 | @@ -443,7 +495,7 @@ |
1348 | self.assertEqual(data.node, None) |
1349 | |
1350 | @defer.inlineCallbacks |
1351 | - def test_ListDir_old(self): |
1352 | + def test_listdir_old(self): |
1353 | """Test meta with ListDir.""" |
1354 | cmd = 'ListDir(share_id=a, node_id=b, server_hash=c)' |
1355 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1356 | @@ -455,7 +507,7 @@ |
1357 | self.assertEqual(data.node, 'b') |
1358 | |
1359 | @defer.inlineCallbacks |
1360 | - def test_MakeDir_old(self): |
1361 | + def test_makedir_old(self): |
1362 | """Test meta with MakeDir.""" |
1363 | cmd = 'MakeDir(share_id=a, parent_id=b, name=c, marker=d)' |
1364 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1365 | @@ -467,7 +519,7 @@ |
1366 | self.assertEqual(data.node, None) |
1367 | |
1368 | @defer.inlineCallbacks |
1369 | - def test_MakeFile_old(self): |
1370 | + def test_makefile_old(self): |
1371 | """Test meta with MakeFile.""" |
1372 | cmd = 'MakeFile(share_id=a, parent_id=b, name=c, marker=d)' |
1373 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1374 | @@ -479,7 +531,7 @@ |
1375 | self.assertEqual(data.node, None) |
1376 | |
1377 | @defer.inlineCallbacks |
1378 | - def test_Unlink_old(self): |
1379 | + def test_unlink_old(self): |
1380 | """Test meta with Unlink.""" |
1381 | cmd = 'Unlink(share_id=a, node_id=b, server_hash=c)' |
1382 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1383 | @@ -491,7 +543,7 @@ |
1384 | self.assertEqual(data.node, 'b') |
1385 | |
1386 | @defer.inlineCallbacks |
1387 | - def test_Move_old(self): |
1388 | + def test_move_old(self): |
1389 | """Test meta with Move.""" |
1390 | cmd = 'Move(share_id=a, node_id=b, old_parent_id=c, '\ |
1391 | 'new_parent_id=d, new_name=e)' |
1392 | @@ -504,7 +556,7 @@ |
1393 | self.assertEqual(data.node, 'b') |
1394 | |
1395 | @defer.inlineCallbacks |
1396 | - def test_ChangePublicAccess_old(self): |
1397 | + def test_changepublicaccess_old(self): |
1398 | """Test meta with ChangePublicAccess.""" |
1399 | cmd = 'ChangePublicAccess' |
1400 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1401 | @@ -516,7 +568,7 @@ |
1402 | self.assertEqual(data.node, None) |
1403 | |
1404 | @defer.inlineCallbacks |
1405 | - def test_AnswerShare_old(self): |
1406 | + def test_answershare_old(self): |
1407 | """Test meta with AnswerShare.""" |
1408 | cmd = 'AnswerShare' |
1409 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1410 | @@ -528,7 +580,7 @@ |
1411 | self.assertEqual(data.node, None) |
1412 | |
1413 | @defer.inlineCallbacks |
1414 | - def test_GetPublicFiles_dict(self): |
1415 | + def test_getpublicfiles_dict(self): |
1416 | """Test meta with GetPublicFiles.""" |
1417 | cmd = ('GetPublicFiles', {}) |
1418 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1419 | @@ -540,7 +592,7 @@ |
1420 | self.assertEqual(data.node, None) |
1421 | |
1422 | @defer.inlineCallbacks |
1423 | - def test_AccountInquiry_dict(self): |
1424 | + def test_accountinquiry_dict(self): |
1425 | """Test meta with AccountInquiry.""" |
1426 | cmd = ('AccountInquiry', {}) |
1427 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1428 | @@ -552,7 +604,7 @@ |
1429 | self.assertEqual(data.node, None) |
1430 | |
1431 | @defer.inlineCallbacks |
1432 | - def test_FreeSpaceInquiry_dict(self): |
1433 | + def test_freespaceinquiry_dict(self): |
1434 | """Test meta with FreeSpaceInquiry.""" |
1435 | cmd = ('FreeSpaceInquiry', {}) |
1436 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1437 | @@ -564,7 +616,7 @@ |
1438 | self.assertEqual(data.node, None) |
1439 | |
1440 | @defer.inlineCallbacks |
1441 | - def test_ListShares_dict(self): |
1442 | + def test_listshares_dict(self): |
1443 | """Test meta with ListShares.""" |
1444 | cmd = ('ListShares', {}) |
1445 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1446 | @@ -576,7 +628,7 @@ |
1447 | self.assertEqual(data.node, None) |
1448 | |
1449 | @defer.inlineCallbacks |
1450 | - def test_ListVolumes_dict(self): |
1451 | + def test_listvolumes_dict(self): |
1452 | """Test meta with ListVolumes.""" |
1453 | cmd = ('ListVolumes', {}) |
1454 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1455 | @@ -588,7 +640,7 @@ |
1456 | self.assertEqual(data.node, None) |
1457 | |
1458 | @defer.inlineCallbacks |
1459 | - def test_Query_dict(self): |
1460 | + def test_query_dict(self): |
1461 | """Test meta with Query.""" |
1462 | cmd = ('Query', {}) |
1463 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1464 | @@ -600,7 +652,7 @@ |
1465 | self.assertEqual(data.node, None) |
1466 | |
1467 | @defer.inlineCallbacks |
1468 | - def test_ListDir_dict(self): |
1469 | + def test_listdir_dict(self): |
1470 | """Test meta with ListDir.""" |
1471 | cmd = ('ListDir', dict(share_id='a', node_id='b', |
1472 | server_hash='c', path='d')) |
1473 | @@ -613,7 +665,7 @@ |
1474 | self.assertEqual(data.node, 'b') |
1475 | |
1476 | @defer.inlineCallbacks |
1477 | - def test_MakeDir_dict(self): |
1478 | + def test_makedir_dict(self): |
1479 | """Test meta with MakeDir.""" |
1480 | cmd = ('MakeDir', dict(share_id='a', parent_id='b', |
1481 | name='c', marker='d')) |
1482 | @@ -626,7 +678,7 @@ |
1483 | self.assertEqual(data.node, None) |
1484 | |
1485 | @defer.inlineCallbacks |
1486 | - def test_MakeFile_dict(self): |
1487 | + def test_makefile_dict(self): |
1488 | """Test meta with MakeFile.""" |
1489 | cmd = ('MakeFile', dict(share_id='a', parent_id='b', |
1490 | name='c', marker='d')) |
1491 | @@ -639,7 +691,7 @@ |
1492 | self.assertEqual(data.node, None) |
1493 | |
1494 | @defer.inlineCallbacks |
1495 | - def test_Unlink_dict(self): |
1496 | + def test_unlink_dict(self): |
1497 | """Test meta with Unlink.""" |
1498 | cmd = ('Unlink', dict(share_id='a', node_id='b', |
1499 | server_hash='c', path='d')) |
1500 | @@ -652,7 +704,7 @@ |
1501 | self.assertEqual(data.node, 'b') |
1502 | |
1503 | @defer.inlineCallbacks |
1504 | - def test_Move_dict(self): |
1505 | + def test_move_dict(self): |
1506 | """Test meta with Move.""" |
1507 | cmd = ('Move', dict(share_id='a', node_id='b', old_parent_id='c', |
1508 | new_parent_id='d', new_name='e', path='f')) |
1509 | @@ -665,7 +717,7 @@ |
1510 | self.assertEqual(data.node, 'b') |
1511 | |
1512 | @defer.inlineCallbacks |
1513 | - def test_ChangePublicAccess_dict(self): |
1514 | + def test_changepublicaccess_dict(self): |
1515 | """Test meta with ChangePublicAccess.""" |
1516 | cmd = ('ChangePublicAccess', {}) |
1517 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1518 | @@ -677,7 +729,7 @@ |
1519 | self.assertEqual(data.node, None) |
1520 | |
1521 | @defer.inlineCallbacks |
1522 | - def test_AnswerShare_dict(self): |
1523 | + def test_answershare_dict(self): |
1524 | """Test meta with AnswerShare.""" |
1525 | cmd = ('AnswerShare', {}) |
1526 | self.fake_sdt_response('waiting_metadata', [cmd]) |
1527 | @@ -688,6 +740,18 @@ |
1528 | self.assertEqual(data.share, None) |
1529 | self.assertEqual(data.node, None) |
1530 | |
1531 | + @defer.inlineCallbacks |
1532 | + def test_getdelta(self): |
1533 | + """Test meta with AnswerShare.""" |
1534 | + cmd = ('GetDelta', {}) |
1535 | + self.fake_sdt_response('waiting_metadata', [cmd]) |
1536 | + rcv = yield self.dbus.get_meta_queue() |
1537 | + data = rcv[0] |
1538 | + self.assertEqual(data.operation, 'GetDelta') |
1539 | + self.assertEqual(data.path, None) |
1540 | + self.assertEqual(data.share, None) |
1541 | + self.assertEqual(data.node, None) |
1542 | + |
1543 | |
1544 | class TestDataProcessingFolders(SafeTests): |
1545 | """Process Folders data before sending it to SyncDaemon.""" |
1546 | @@ -910,6 +974,138 @@ |
1547 | self.assertEqual(share.volume_id, 'vol') |
1548 | |
1549 | |
1550 | +class TestPublicFiles(SafeTests): |
1551 | + """PublicFiles data handling and related services.""" |
1552 | + |
1553 | + def test_public_files_changed_yes(self): |
1554 | + """Call the changed callback.""" |
1555 | + d = dict(share_id='share', node_id='node', is_public='True', |
1556 | + public_url='url', path='path') |
1557 | + self.dbus._on_public_files_changed(d) |
1558 | + pf, is_public = self.get_msd_called("on_sd_public_files_changed") |
1559 | + self.assertEqual(pf.volume, 'share') |
1560 | + self.assertEqual(pf.node, 'node') |
1561 | + self.assertEqual(pf.public_url, 'url') |
1562 | + self.assertEqual(pf.path, 'path') |
1563 | + self.assertEqual(is_public, True) |
1564 | + |
1565 | + def test_public_files_changed_no(self): |
1566 | + """Call the changed callback.""" |
1567 | + d = dict(share_id='share', node_id='node', is_public='', |
1568 | + public_url='url', path='path') |
1569 | + self.dbus._on_public_files_changed(d) |
1570 | + pf, is_public = self.get_msd_called("on_sd_public_files_changed") |
1571 | + self.assertEqual(pf.volume, 'share') |
1572 | + self.assertEqual(pf.node, 'node') |
1573 | + self.assertEqual(pf.public_url, 'url') |
1574 | + self.assertEqual(pf.path, 'path') |
1575 | + self.assertEqual(is_public, False) |
1576 | + |
1577 | + def test_public_files_empty(self): |
1578 | + """Call the callback without info.""" |
1579 | + self.dbus._on_public_files_list([]) |
1580 | + res, = self.get_msd_called("on_sd_public_files_list") |
1581 | + self.assertEqual(res, []) |
1582 | + |
1583 | + def test_public_files_one(self): |
1584 | + """Call the callback with a public file.""" |
1585 | + d = dict(volume_id='volume', node_id='node', |
1586 | + public_url='url', path='path') |
1587 | + self.dbus._on_public_files_list([d]) |
1588 | + res, = self.get_msd_called("on_sd_public_files_list") |
1589 | + self.assertEqual(len(res), 1) |
1590 | + pf = res[0] |
1591 | + self.assertEqual(pf.volume, 'volume') |
1592 | + self.assertEqual(pf.node, 'node') |
1593 | + self.assertEqual(pf.public_url, 'url') |
1594 | + self.assertEqual(pf.path, 'path') |
1595 | + |
1596 | + def test_public_files_more(self): |
1597 | + """Call the callback with a mixed content.""" |
1598 | + d1 = dict(volume_id='volume1', node_id='node1', |
1599 | + public_url='url1', path='path1') |
1600 | + d2 = dict(volume_id='volume2', node_id='node2', |
1601 | + public_url='url2', path='path2') |
1602 | + d3 = dict(volume_id='volume3', node_id='node3', |
1603 | + public_url='url3', path='path3') |
1604 | + self.dbus._on_public_files_list([d1, d2, d3]) |
1605 | + res, = self.get_msd_called("on_sd_public_files_list") |
1606 | + self.assertEqual(len(res), 3) |
1607 | + pf = res[0] |
1608 | + self.assertEqual(pf.volume, 'volume1') |
1609 | + self.assertEqual(pf.node, 'node1') |
1610 | + self.assertEqual(pf.public_url, 'url1') |
1611 | + self.assertEqual(pf.path, 'path1') |
1612 | + pf = res[1] |
1613 | + self.assertEqual(pf.volume, 'volume2') |
1614 | + self.assertEqual(pf.node, 'node2') |
1615 | + self.assertEqual(pf.public_url, 'url2') |
1616 | + self.assertEqual(pf.path, 'path2') |
1617 | + pf = res[2] |
1618 | + self.assertEqual(pf.volume, 'volume3') |
1619 | + self.assertEqual(pf.node, 'node3') |
1620 | + self.assertEqual(pf.public_url, 'url3') |
1621 | + self.assertEqual(pf.path, 'path3') |
1622 | + |
1623 | + def test_getpublicfiles_deferred(self): |
1624 | + """The method should return a deferred and store it.""" |
1625 | + d = self.dbus.get_public_files() |
1626 | + self.assertTrue(isinstance(d, defer.Deferred)) |
1627 | + self.assertIdentical(d, self.dbus._public_files_deferred) |
1628 | + |
1629 | + @defer.inlineCallbacks |
1630 | + def test_public_files_when_asked(self): |
1631 | + """If we receive the signal and data was asked, change behaviour.""" |
1632 | + # set up stuff |
1633 | + d = defer.Deferred() |
1634 | + self.dbus._public_files_deferred = d |
1635 | + |
1636 | + # call the signal |
1637 | + self.dbus._on_public_files_list([]) |
1638 | + |
1639 | + # check that the deferred was callbacked, and not the normal SD method |
1640 | + result = yield d |
1641 | + self.assertEqual(result, []) |
1642 | + self.assertTrue(self.fsd._called_method[0] is None) |
1643 | + |
1644 | + def test_getpublicfiles_asktoclient_ok(self): |
1645 | + """The info was requested to the DBusClient, and was ok.""" |
1646 | + # inject a different DBusClient |
1647 | + fake_dbusclient = FakeDBusClient() |
1648 | + self.patch(dbusiface, 'DBusClient', fake_dbusclient) |
1649 | + |
1650 | + # call, and check init and method args |
1651 | + self.dbus.get_public_files() |
1652 | + self.assertEqual(fake_dbusclient.init_args, (self.dbus._bus, |
1653 | + '/publicfiles', DBUS_IFACE_PUBLIC_FILES_NAME)) |
1654 | + (method_name,), kwargs = fake_dbusclient.method_call_args |
1655 | + done_cback = kwargs['reply_handler'] |
1656 | + self.assertEqual(method_name, 'get_public_files') |
1657 | + |
1658 | + # trigger done callback and check logging |
1659 | + done_cback(None) |
1660 | + self.assertTrue(self.handler.check_debug("Public files asked ok.")) |
1661 | + |
1662 | + def test_getpublicfiles_asktoclient_error(self): |
1663 | + """The info was requested to the DBusClient, and was not ok.""" |
1664 | + # inject a different DBusClient |
1665 | + fake_dbusclient = FakeDBusClient() |
1666 | + self.patch(dbusiface, 'DBusClient', fake_dbusclient) |
1667 | + |
1668 | + # call, and check init and method args |
1669 | + self.dbus.get_public_files() |
1670 | + self.assertEqual(fake_dbusclient.init_args, (self.dbus._bus, |
1671 | + '/publicfiles', DBUS_IFACE_PUBLIC_FILES_NAME)) |
1672 | + (method_name,), kwargs = fake_dbusclient.method_call_args |
1673 | + error_cback = kwargs['error_handler'] |
1674 | + self.assertEqual(method_name, 'get_public_files') |
1675 | + |
1676 | + # trigger error callback and check logging |
1677 | + error_cback('foo') |
1678 | + self.assertTrue(self.handler.check_error( |
1679 | + "Public files asked with error: foo")) |
1680 | + |
1681 | + |
1682 | class TestToolActions(SafeTests): |
1683 | """Actions against SD.tools. |
1684 | |
1685 | @@ -944,8 +1140,10 @@ |
1686 | def setUp(self): |
1687 | """Set up.""" |
1688 | self.handler = MementoHandler() |
1689 | - logging.getLogger('magicicada.dbusiface').addHandler(self.handler) |
1690 | self.handler.setLevel(logging.DEBUG) |
1691 | + logger = logging.getLogger('magicicada.dbusiface') |
1692 | + logger.addHandler(self.handler) |
1693 | + logger.setLevel(logging.DEBUG) |
1694 | SafeTests.setUp(self) |
1695 | |
1696 | def test_instancing(self): |
1697 | @@ -1052,13 +1250,13 @@ |
1698 | self.assertTrue(self.handler.check_info("Received Name Owner changed")) |
1699 | self.assertTrue(self.handler.check_debug("Name Owner data: u'' u'T'")) |
1700 | |
1701 | - def test_name_owner_changed_yes_syncdaemon_TF(self): |
1702 | + def test_name_owner_changed_yes_syncdaemon_true_false(self): |
1703 | """Test name owner changed callback, SD value bad.""" |
1704 | self.dbus._on_name_owner_changed("com.ubuntuone.SyncDaemon", "F", "T") |
1705 | self.assertTrue(self.handler.check_info("Received Name Owner changed")) |
1706 | self.assertTrue(self.handler.check_debug("Name Owner data: u'F' u'T'")) |
1707 | - self.assertTrue(self.handler.check("ERROR", |
1708 | - "Name Owner invalid data: Same bool in old and new!")) |
1709 | + msg = "Name Owner invalid data: Same bool in old and new!" |
1710 | + self.assertTrue(self.handler.check_error(msg)) |
1711 | |
1712 | def test_folder_created_changed(self): |
1713 | """Test folder created changed callback.""" |
1714 | @@ -1091,12 +1289,32 @@ |
1715 | self.dbus._on_share_deleted("foo") |
1716 | self.assertTrue(self.handler.check_info("Received Share deleted")) |
1717 | |
1718 | - def test_share__changed(self): |
1719 | + def test_share_changed(self): |
1720 | """Test share changed callback.""" |
1721 | self.dbus._on_share_changed("foo") |
1722 | self.assertTrue(self.handler.check_info("Received Share changed")) |
1723 | |
1724 | @defer.inlineCallbacks |
1725 | + def test_public_files_list(self): |
1726 | + """Test public files list callback.""" |
1727 | + d = dict(volume_id='volume', node_id='node', |
1728 | + public_url='url', path='path') |
1729 | + yield self.dbus._on_public_files_list([d]) |
1730 | + self.assertTrue(self.handler.check_info( |
1731 | + "Received Public Files list (1)")) |
1732 | + self.assertTrue(self.handler.check_debug( |
1733 | + " Public Files data: %s" % d)) |
1734 | + |
1735 | + @defer.inlineCallbacks |
1736 | + def test_public_files_changed(self): |
1737 | + """Test public files changed callback.""" |
1738 | + d = dict(share_id='volume', node_id='node', is_public='', |
1739 | + public_url='url', path='path') |
1740 | + yield self.dbus._on_public_files_changed(d) |
1741 | + self.assertTrue(self.handler.check_debug( |
1742 | + "Received Public Files changed: %s" % d)) |
1743 | + |
1744 | + @defer.inlineCallbacks |
1745 | def test_content_queue_processing(self): |
1746 | """Test with one item in the queue.""" |
1747 | c = dict(operation='oper', path='path', share='share', node='node') |
1748 | @@ -1270,3 +1488,52 @@ |
1749 | d.addCallbacks(lambda _: deferred.errback(Exception()), |
1750 | lambda _: deferred.callback(True)) |
1751 | return deferred |
1752 | + |
1753 | + |
1754 | +class TestHandlingShares(SafeTests): |
1755 | + """Handle shares.""" |
1756 | + |
1757 | + @defer.inlineCallbacks |
1758 | + def test_accept_share_ok(self): |
1759 | + """Accepting share finishes ok.""" |
1760 | + d = dict(volume_id='foo', answer='bar') |
1761 | + self.fake_sdt_response('accept_share', d) |
1762 | + yield self.dbus.accept_share('foo') |
1763 | + self.assertTrue(self.handler.check_debug( |
1764 | + "Accept share foo started", "foo")) |
1765 | + self.assertTrue(self.handler.check_debug( |
1766 | + "Accept share foo finished", str(d))) |
1767 | + |
1768 | + @defer.inlineCallbacks |
1769 | + def test_accept_share_bad(self): |
1770 | + """Accepting share finishes bad.""" |
1771 | + d = dict(volume_id='foo', answer='bar', error='baz') |
1772 | + self.fake_sdt_response('accept_share', d) |
1773 | + try: |
1774 | + yield self.dbus.accept_share('foo') |
1775 | + except dbusiface.ShareOperationError, e: |
1776 | + self.assertEqual(e.share_id, 'foo') |
1777 | + self.assertEqual(e.error, 'baz') |
1778 | + else: |
1779 | + raise Exception("Test should have raised an exception") |
1780 | + self.assertTrue(self.handler.check_debug( |
1781 | + "Accept share foo started", "foo")) |
1782 | + self.assertTrue(self.handler.check_debug( |
1783 | + "Accept share foo finished", str(d))) |
1784 | + |
1785 | + @defer.inlineCallbacks |
1786 | + def test_accept_share_ugly_error(self): |
1787 | + """Accepting share went really bad.""" |
1788 | + e = dbus.exceptions.DBusException('ugly!') |
1789 | + self.fake_sdt_response('accept_share', e) |
1790 | + try: |
1791 | + yield self.dbus.accept_share('foo') |
1792 | + except dbusiface.ShareOperationError, e: |
1793 | + self.assertEqual(e.share_id, 'foo') |
1794 | + self.assertEqual(e.error, "ugly!") |
1795 | + else: |
1796 | + raise Exception("Test should have raised an exception") |
1797 | + self.assertTrue(self.handler.check_debug( |
1798 | + "Accept share foo started", "foo")) |
1799 | + self.assertTrue(self.handler.check_debug( |
1800 | + "Accept share foo crashed", "ugly!")) |
1801 | |
1802 | === modified file 'magicicada/tests/test_logger.py' |
1803 | --- magicicada/tests/test_logger.py 2010-08-18 21:17:20 +0000 |
1804 | +++ magicicada/tests/test_logger.py 2011-01-07 23:06:32 +0000 |
1805 | @@ -23,7 +23,9 @@ |
1806 | import sys |
1807 | import unittest |
1808 | |
1809 | -from magicicada.logger import exception_handler |
1810 | +from twisted.python import log, failure |
1811 | + |
1812 | +from magicicada.logger import exception_handler, deferror_handler |
1813 | from magicicada.tests.helpers import MementoHandler |
1814 | |
1815 | |
1816 | @@ -81,3 +83,35 @@ |
1817 | shown = fh.getvalue() |
1818 | self.assertTrue("Traceback" in shown) |
1819 | self.assertTrue("ZeroDivisionError" in shown) |
1820 | + |
1821 | + |
1822 | +class DeferredTests(unittest.TestCase): |
1823 | + """Error logging when it happened inside deferreds.""" |
1824 | + |
1825 | + def test_observer_added(self): |
1826 | + """Test that the observer was added to Twisted logging.""" |
1827 | + self.assertTrue(deferror_handler in log.theLogPublisher.observers) |
1828 | + |
1829 | + def test_noerror(self): |
1830 | + """No error, no action.""" |
1831 | + handler = MementoHandler() |
1832 | + handler.setLevel(logging.DEBUG) |
1833 | + deferror_handler(dict(isError=False, message='')) |
1834 | + self.assertFalse(handler.check_error("error")) |
1835 | + |
1836 | + def test_message(self): |
1837 | + """Just a message.""" |
1838 | + handler = MementoHandler() |
1839 | + handler.setLevel(logging.DEBUG) |
1840 | + deferror_handler(dict(isError=True, message="foobar")) |
1841 | + self.assertFalse(handler.check_error("Unhandled error in deferred", |
1842 | + "foobar")) |
1843 | + |
1844 | + def test_failure(self): |
1845 | + """Received a full failure.""" |
1846 | + handler = MementoHandler() |
1847 | + handler.setLevel(logging.DEBUG) |
1848 | + f = failure.Failure(ValueError('foobar')) |
1849 | + deferror_handler(dict(isError=True, failure=f, message='')) |
1850 | + self.assertFalse(handler.check_error("Unhandled error in deferred", |
1851 | + "ValueError", "foobar")) |
1852 | |
1853 | === modified file 'magicicada/tests/test_magicicada.py' |
1854 | --- magicicada/tests/test_magicicada.py 2010-08-21 16:58:46 +0000 |
1855 | +++ magicicada/tests/test_magicicada.py 2011-01-07 23:06:32 +0000 |
1856 | @@ -32,9 +32,9 @@ |
1857 | from twisted.trial.unittest import TestCase |
1858 | |
1859 | from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, \ |
1860 | - UBUNTU_ONE_ROOT, syncdaemon |
1861 | + NOT_SYNCHED_PATH, UBUNTU_ONE_ROOT, syncdaemon |
1862 | from magicicada.dbusiface import QueueData, FolderData, ShareData, \ |
1863 | - NOT_SYNCHED_PATH |
1864 | + PublicFilesData |
1865 | from magicicada.helpers import NO_OP, humanize_bytes, get_data_file |
1866 | from magicicada.tests.helpers import MementoHandler |
1867 | |
1868 | @@ -52,6 +52,25 @@ |
1869 | # pylint: disable=E1103 |
1870 | |
1871 | |
1872 | +SAMPLE_PUBLIC_FILES = { |
1873 | + u'4d0ffa01-5375-4e67-bc47-d993b721d8af': |
1874 | + PublicFilesData(volume=u'', |
1875 | + node=u'4d0ffa01-5375-4e67-bc47-d993b721d8af', |
1876 | + path=u'/home/user/Ubuntu One/test.png', |
1877 | + public_url=u'http://ubuntuone.com/p/CUG/'), |
1878 | + u'1be1ea29-426e-41b3-8853-5cce03b0c511': |
1879 | + PublicFilesData(volume=u'', |
1880 | + node=u'1be1ea29-426e-41b3-8853-5cce03b0c511', |
1881 | + path=u'/home/user/Ubuntu One/public/text.txt', |
1882 | + public_url=u'http://ubuntuone.com/p/6TX/'), |
1883 | + u'8dba0484-86e9-4505-8fba-db3db7cc551d': |
1884 | + PublicFilesData(volume=u'f5cc6b0d-85a0-4046-a72f-bd42c41538cb', |
1885 | + node=u'8dba0484-86e9-4505-8fba-db3db7cc551d', |
1886 | + path=u'/home/user/udf0/yadda.py', |
1887 | + public_url=u'http://ubuntuone.com/p/U4R/'), |
1888 | +} |
1889 | + |
1890 | + |
1891 | def process_gtk_pendings(): |
1892 | """Process all gtk pending events.""" |
1893 | while gtk.events_pending(): |
1894 | @@ -101,8 +120,13 @@ |
1895 | self.content_queue_changed_callback = NO_OP |
1896 | self.meta_queue_changed_callback = NO_OP |
1897 | self.on_metadata_ready_callback = None # mandatory |
1898 | + self.on_initial_data_ready_callback = NO_OP |
1899 | + self.on_initial_online_data_ready_callback = NO_OP |
1900 | self.shutdown = NO_OP |
1901 | |
1902 | + # Lambda may not be necessary |
1903 | + # pylint: disable=W0108 |
1904 | + |
1905 | self.start = lambda: setattr(self.current_state, 'is_started', True) |
1906 | self.quit = lambda: setattr(self.current_state, 'is_started', False) |
1907 | self.connect = lambda: setattr(self.current_state, |
1908 | @@ -116,6 +140,7 @@ |
1909 | """UI test cases for Magicicada UI.""" |
1910 | |
1911 | TEST_FILE = get_data_file('tests', 'metadata-test.txt') |
1912 | + data_type = mapping = store = view = None |
1913 | |
1914 | def setUp(self): |
1915 | """Init.""" |
1916 | @@ -155,30 +180,6 @@ |
1917 | else: |
1918 | return TestCase.__getattribute__(self, name) |
1919 | |
1920 | - if self._failed_test: |
1921 | - # no, I'm not raising a bool. pylint: disable=E0702 |
1922 | - raise self._failed_test |
1923 | - |
1924 | - def __getattribute__(self, name): |
1925 | - """Overwrite the assert methods with safer ones. |
1926 | - |
1927 | - This way if a test called by gobject in the future fails, it |
1928 | - makes the whole test suite fail. |
1929 | - """ |
1930 | - if name.startswith('assert') and hasattr(TestCase, name): |
1931 | - |
1932 | - def proxy(*args, **kwargs): |
1933 | - """Function that will call the real assert.""" |
1934 | - real_assert = getattr(TestCase, name) |
1935 | - try: |
1936 | - real_assert(self, *args, **kwargs) |
1937 | - except Exception, e: |
1938 | - self._failed_test = e |
1939 | - raise |
1940 | - return proxy |
1941 | - else: |
1942 | - return TestCase.__getattribute__(self, name) |
1943 | - |
1944 | def do_start(self): |
1945 | """Simulate that start fully happened.""" |
1946 | self.ui.on_start_clicked(self.ui.start) |
1947 | @@ -190,41 +191,56 @@ |
1948 | self.ui.on_connect_clicked(self.ui.connect) |
1949 | self.ui.on_connected() |
1950 | |
1951 | - def build_some_data(self, data_type, limit=5): |
1952 | + def build_some_data(self, limit=5): |
1953 | """Build some data using named_tuple 'data_type'.""" |
1954 | - attrs = data_type._fields |
1955 | + assert self.data_type is not None, 'class muct provide a data_type' |
1956 | + |
1957 | + attrs = self.data_type._fields |
1958 | result = [] |
1959 | for i in xrange(limit): |
1960 | kwargs = dict([(attr, '%s %i' % (attr, i)) for attr in attrs]) |
1961 | - result.append(data_type(**kwargs)) |
1962 | + # self.data_type is not callable, pylint: disable=E1102 |
1963 | + result.append(self.data_type(**kwargs)) |
1964 | return result |
1965 | |
1966 | - def assert_store_correct(self, store, items, mapping, markup=None): |
1967 | - """Test that 'store' has 'items' as content.""" |
1968 | + def assert_store_correct(self, items, markup=None): |
1969 | + """Test that 'self.store' has 'items' as content.""" |
1970 | + assert self.mapping is not None, 'class must provide a mapping' |
1971 | + assert self.store is not None, 'class must provide a store' |
1972 | + |
1973 | msg = 'amount of rows for %s must be %s (got %s).' |
1974 | - self.assertEqual(len(store), len(items), |
1975 | - msg % (store, len(items), len(store))) |
1976 | + self.assertEqual(len(self.store), len(items), |
1977 | + msg % (self.store, len(items), len(self.store))) |
1978 | |
1979 | # assert rows content equal to items content |
1980 | - tree_iter = store.get_iter_root() |
1981 | + tree_iter = self.store.get_iter_root() |
1982 | tmp = list(reversed(items)) |
1983 | do_markup = markup is not None |
1984 | msg = "column %i ('%s') must be '%s' (got '%s' instead)" |
1985 | while tree_iter is not None: |
1986 | head = tmp.pop() |
1987 | - for i, field in mapping: |
1988 | - actual, = store.get(tree_iter, i) |
1989 | + for i, field in self.mapping: |
1990 | + actual, = self.store.get(tree_iter, i) |
1991 | expected = getattr(head, field) |
1992 | - if store.get_column_type(i).name == 'gboolean': |
1993 | + if self.store.get_column_type(i).name == 'gboolean': |
1994 | expected = bool(expected) |
1995 | elif do_markup: |
1996 | expected = markup(expected) |
1997 | self.assertEqual(expected, actual, |
1998 | msg % (i, field, expected, actual)) |
1999 | |
2000 | - tree_iter = store.iter_next(tree_iter) |
2001 | + tree_iter = self.store.iter_next(tree_iter) |
2002 | do_markup = False # only for first row |
2003 | |
2004 | + def debug_store(self): |
2005 | + """Print the whole content of a store.""" |
2006 | + store_iter = self.store.get_iter_root() |
2007 | + columns = self.store.get_n_columns() |
2008 | + print '\nShowing contents of store:', self.store |
2009 | + while store_iter is not None: |
2010 | + print self.store.get(store_iter, *range(columns)) |
2011 | + store_iter = self.store.iter_next(store_iter) |
2012 | + |
2013 | def assert_indicator_disabled(self, indicator): |
2014 | """Test that 'indicator' is not sensitive.""" |
2015 | self.assertFalse(indicator.is_sensitive(), |
2016 | @@ -283,7 +299,41 @@ |
2017 | msg = '%s must not skip taskbar.' |
2018 | self.assertFalse(dialog.get_skip_taskbar_hint(), msg % dialog_name) |
2019 | |
2020 | - self.assertEqual(dialog.get_icon(), self.ui._icon) |
2021 | + def assert_sort_order_correct(self, column, idx, expected_order): |
2022 | + """Check that sort order is correctly set for 'self.store'.""" |
2023 | + assert self.store is not None, 'class must provide a store' |
2024 | + |
2025 | + msg0 = 'Store sort id must be %r (got %r instead).' |
2026 | + msg1 = 'Store sort order must be %r (got %r instead).' |
2027 | + msg3 = 'Column sort order must be %r (got %r instead).' |
2028 | + |
2029 | + actual_id, actual_order = self.store.get_sort_column_id() |
2030 | + |
2031 | + # store sort column id and order |
2032 | + self.assertEqual(idx, actual_id, msg0 % (idx, actual_id)) |
2033 | + self.assertEqual(expected_order, actual_order, |
2034 | + msg1 % (expected_order, actual_order)) |
2035 | + |
2036 | + # column sort order |
2037 | + actual_order = column.get_sort_order() |
2038 | + self.assertEqual(expected_order, actual_order, |
2039 | + msg3 % (expected_order, actual_order)) |
2040 | + |
2041 | + def assert_sort_indicator_correct(self, column): |
2042 | + """Check that sort indicator is correctly set.""" |
2043 | + assert self.view is not None, 'class must provide a view' |
2044 | + |
2045 | + msg = 'Column %s must have sort indicator %s.' |
2046 | + colname = column.get_name() |
2047 | + # column sort indicator |
2048 | + self.assertTrue(column.get_sort_indicator(), msg % (colname, 'on')) |
2049 | + |
2050 | + # all the other columns must not have the sort indicator on |
2051 | + for other_column in self.view.get_columns(): |
2052 | + if other_column.get_name() == colname: |
2053 | + continue |
2054 | + self.assertFalse(other_column.get_sort_indicator(), |
2055 | + msg % (other_column.get_name(), 'off')) |
2056 | |
2057 | |
2058 | class MagicicadaUIBasicTestCase(MagicicadaUITestCase): |
2059 | @@ -305,10 +355,16 @@ |
2060 | """UI can be created and main_window is visible.""" |
2061 | self.assertTrue(self.ui.widget_is_visible(self.ui.main_window)) |
2062 | |
2063 | - def test_windows_have_correct_icon(self): |
2064 | + def test_main_window_have_correct_icon_list(self): |
2065 | """Every window has the icon set.""" |
2066 | - for w in self.ui.windows: |
2067 | - self.assertEqual(w.get_icon(), self.ui._icon) |
2068 | + self.assertEqual(len(self.ui.main_window.get_icon_list()), |
2069 | + len(self.ui._icons.values())) |
2070 | + |
2071 | + def test_every_window_has_correct_list(self): |
2072 | + """The default icon list is set.""" |
2073 | + self.patch(gtk, 'window_set_default_icon_list', self._set_called) |
2074 | + MagicicadaUI(syncdaemon_class=FakedSyncdaemon) |
2075 | + self.assertEqual(len(self._called[0]), len(self.ui._icons.values())) |
2076 | |
2077 | def test_start_connect_are_visible(self): |
2078 | """Start and Connect buttons are visible.""" |
2079 | @@ -456,6 +512,8 @@ |
2080 | """Abstratc UI test cases for queue tree views.""" |
2081 | |
2082 | name = None |
2083 | + data_type = QueueData |
2084 | + mapping = enumerate(['operation', 'path', 'share', 'node']) |
2085 | |
2086 | def setUp(self): |
2087 | """Init.""" |
2088 | @@ -468,15 +526,8 @@ |
2089 | 'on_%s_queue_changed' % self.name) |
2090 | self.set_sd_queue = lambda q: \ |
2091 | setattr(self.ui.sd, '%s_queue' % self.name, q) |
2092 | - self.queue_store = getattr(self.ui, '%sq_store' % self.name) |
2093 | - self.queue_view = getattr(self.ui, '%sq_view' % self.name) |
2094 | - |
2095 | - def build_some_data(self, limit=5): |
2096 | - """Build some data to act as queue data.""" |
2097 | - kwargs = dict(data_type=QueueData, limit=limit) |
2098 | - res = super(_MagicicadaUIQueueTestCase, self).build_some_data(**kwargs) |
2099 | - # operation path share node |
2100 | - return res |
2101 | + self.store = getattr(self.ui, '%sq_store' % self.name) |
2102 | + self.view = getattr(self.ui, '%sq_view' % self.name) |
2103 | |
2104 | def expected_markup(self, value): |
2105 | """Return the markup for row at index 'i'.""" |
2106 | @@ -491,10 +542,9 @@ |
2107 | if must_highlight and value is not None else value |
2108 | return result |
2109 | |
2110 | - def assert_store_correct(self, store, items): |
2111 | + def assert_store_correct(self, items): |
2112 | """Test that 'store' has 'items' as content.""" |
2113 | - mapping = enumerate(['operation', 'path', 'share', 'node']) |
2114 | - args = (store, items, mapping, self.expected_markup) |
2115 | + args = (items, self.expected_markup) |
2116 | super(_MagicicadaUIQueueTestCase, self).assert_store_correct(*args) |
2117 | |
2118 | def assert_current_processing_row_is_different(self): |
2119 | @@ -508,8 +558,8 @@ |
2120 | markup = self.expected_markup |
2121 | expected = tuple(markup(getattr(item, attr)) for attr in attrs) |
2122 | |
2123 | - iter_root = self.queue_store.get_iter_root() |
2124 | - actual = self.queue_store.get(iter_root, *xrange(len(attrs))) |
2125 | + iter_root = self.store.get_iter_root() |
2126 | + actual = self.store.get(iter_root, *xrange(len(attrs))) |
2127 | |
2128 | msg = 'first row for %s queue must be %s (got %s instead)' % \ |
2129 | (self.name, expected, actual) |
2130 | @@ -524,30 +574,30 @@ |
2131 | @skip_abstract_class |
2132 | def test_model_is_binded(self): |
2133 | """List store is binded.""" |
2134 | - actual = self.queue_view.get_model() |
2135 | + actual = self.view.get_model() |
2136 | msg = 'model for view %s differs from %s' |
2137 | - self.assertEqual(self.queue_store, actual, |
2138 | - msg % (self.name, self.queue_store)) |
2139 | + self.assertEqual(self.store, actual, |
2140 | + msg % (self.name, self.store)) |
2141 | |
2142 | @skip_abstract_class |
2143 | def test_on_queue_changed_updates_view(self): |
2144 | """On queue changed the view is updated.""" |
2145 | items = self.build_some_data() |
2146 | self.sd_changed(items) |
2147 | - self.assert_store_correct(self.queue_store, items) |
2148 | + self.assert_store_correct(items) |
2149 | |
2150 | @skip_abstract_class |
2151 | def test_on_queue_changed_handles_none(self): |
2152 | """On queue changed handles None as items.""" |
2153 | self.sd_changed(None) |
2154 | - self.assert_store_correct(self.queue_store, []) |
2155 | + self.assert_store_correct([]) |
2156 | |
2157 | @skip_abstract_class |
2158 | def test_on_queue_changed_handles_an_item_none(self): |
2159 | """On queue changed handles None as items.""" |
2160 | items = [QueueData(operation='Test', path='', share=None, node=None)] |
2161 | self.sd_changed(items) |
2162 | - self.assert_store_correct(self.queue_store, items) |
2163 | + self.assert_store_correct(items) |
2164 | |
2165 | @skip_abstract_class |
2166 | def test_model_is_cleared_before_updating(self): |
2167 | @@ -557,17 +607,17 @@ |
2168 | |
2169 | items = self.build_some_data() |
2170 | self.sd_changed(items) |
2171 | - self.assertEqual(len(self.queue_store), len(items)) |
2172 | + self.assertEqual(len(self.store), len(items)) |
2173 | |
2174 | @skip_abstract_class |
2175 | def test_view_is_enabled_if_disabled_on_changed(self): |
2176 | """The tree view is enabled on changed if it was disabled.""" |
2177 | - self.assertFalse(self.queue_view.is_sensitive(), |
2178 | + self.assertFalse(self.view.is_sensitive(), |
2179 | 'Tree view must be disabled by default.') |
2180 | items = self.build_some_data() |
2181 | self.sd_changed(items) |
2182 | |
2183 | - self.assertTrue(self.queue_view.is_sensitive(), |
2184 | + self.assertTrue(self.view.is_sensitive(), |
2185 | 'Tree view must be enabled on changed.') |
2186 | |
2187 | @skip_abstract_class |
2188 | @@ -578,7 +628,7 @@ |
2189 | |
2190 | self.ui.update() |
2191 | |
2192 | - self.assert_store_correct(self.queue_store, data) |
2193 | + self.assert_store_correct(data) |
2194 | |
2195 | @skip_abstract_class |
2196 | def test_on_stopped_updates_queue(self): |
2197 | @@ -594,7 +644,7 @@ |
2198 | label = '%sq_label' % self.name |
2199 | actual = getattr(self.ui, label).get_text() |
2200 | expected = '%s Queue (%i)' % (self.name.capitalize(), |
2201 | - len(self.queue_store)) |
2202 | + len(self.store)) |
2203 | msg = '%s should be %s (got %s instead)' |
2204 | self.assertEqual(expected, actual, msg % (label, expected, actual)) |
2205 | |
2206 | @@ -608,13 +658,13 @@ |
2207 | items = self.build_some_data() |
2208 | self.set_sd_queue(items) |
2209 | self.do_start() |
2210 | - self.assert_store_correct(self.queue_store, items) |
2211 | + self.assert_store_correct(items) |
2212 | |
2213 | items = items[:len(items) / 2] |
2214 | self.set_sd_queue(items) |
2215 | self.ui.on_stop_clicked(self.ui.stop) |
2216 | self.ui.on_stopped() |
2217 | - self.assert_store_correct(self.queue_store, items) |
2218 | + self.assert_store_correct(items) |
2219 | |
2220 | @skip_abstract_class |
2221 | def test_current_processing_row_is_different_if_online(self): |
2222 | @@ -945,74 +995,40 @@ |
2223 | if self.name is None: |
2224 | return |
2225 | self.volume = getattr(self.ui, self.name) |
2226 | - self.volume_store = getattr(self.ui, '%s_store' % self.name) |
2227 | - self.volume_view = getattr(self.ui, '%s_view' % self.name) |
2228 | + self.store = getattr(self.ui, '%s_store' % self.name) |
2229 | + self.view = getattr(self.ui, '%s_view' % self.name) |
2230 | self.volume_dialog_name = '%s_dialog' % self.name |
2231 | self.volume_dialog = getattr(self.ui, self.volume_dialog_name) |
2232 | self.on_volume_clicked = getattr(self.ui, 'on_%s_clicked' % self.name) |
2233 | self.volume_close = getattr(self.ui, '%s_close' % self.name) |
2234 | |
2235 | - def build_some_data(self, limit=5): |
2236 | - """Build some data to act as volume.""" |
2237 | - kwargs = dict(data_type=self.data_type, limit=limit) |
2238 | - r = super(_MagicicadaUIVolumeTestCase, self).build_some_data(**kwargs) |
2239 | - return r |
2240 | - |
2241 | - def assert_store_correct(self, store, items): |
2242 | - """Test that 'store' has 'items' as content.""" |
2243 | - args = (store, items, self.mapping) |
2244 | - super(_MagicicadaUIVolumeTestCase, self).assert_store_correct(*args) |
2245 | - |
2246 | def assert_widget_availability(self, enabled=True): |
2247 | """Check volume availability according to 'enabled'.""" |
2248 | s = super(_MagicicadaUIVolumeTestCase, self) |
2249 | s.assert_widget_availability(self.name, enabled) |
2250 | |
2251 | - def assert_sort_order_correct(self, column, i, expected_order): |
2252 | - """Check that sort order is correctly set.""" |
2253 | - msg0 = 'Store sort id must be %r (got %r instead).' |
2254 | - msg1 = 'Store sort order must be %r (got %r instead).' |
2255 | - msg3 = 'Column sort order must be %r (got %r instead).' |
2256 | - |
2257 | - actual_id, actual_order = self.volume_store.get_sort_column_id() |
2258 | - |
2259 | - # store sort column id and order |
2260 | - self.assertEqual(i, actual_id, msg0 % (i, actual_id)) |
2261 | - self.assertEqual(expected_order, actual_order, |
2262 | - msg1 % (expected_order, actual_order)) |
2263 | - |
2264 | - # column sort order |
2265 | - actual_order = column.get_sort_order() |
2266 | - self.assertEqual(expected_order, actual_order, |
2267 | - msg3 % (expected_order, actual_order)) |
2268 | - |
2269 | - def assert_sort_indicator_correct(self, column): |
2270 | - """Check that sort indicator is correctly set.""" |
2271 | - msg = 'Column %s must have sort indicator %s.' |
2272 | - colname = column.get_name() |
2273 | - # column sort indicator |
2274 | - self.assertTrue(column.get_sort_indicator(), msg % (colname, 'on')) |
2275 | - |
2276 | - # all the other columns must not have the sort indicator on |
2277 | - for other_column in self.volume_view.get_columns(): |
2278 | - if other_column.get_name() == colname: |
2279 | - continue |
2280 | - self.assertFalse(other_column.get_sort_indicator(), |
2281 | - msg % (other_column.get_name(), 'off')) |
2282 | - |
2283 | - @skip_abstract_class |
2284 | - def test_volume_are_disabled_until_started(self): |
2285 | - """Folders and shares are disabled until online.""" |
2286 | + @skip_abstract_class |
2287 | + def test_initial_data_ready_callback_connected(self): |
2288 | + """The callback 'on_initial_data_ready' is connected to SD.""" |
2289 | + self.assertEqual(self.ui.sd.on_initial_data_ready_callback, |
2290 | + self.ui.on_initial_data_ready, |
2291 | + "on_initial_data_ready should be connected.") |
2292 | + |
2293 | + @skip_abstract_class |
2294 | + def test_volume_are_disabled_until_initial_data_ready(self): |
2295 | + """Folders and shares are disabled until data ready.""" |
2296 | # disabled at startup |
2297 | self.assert_widget_availability(enabled=False) |
2298 | |
2299 | - # enabled when started |
2300 | - self.do_start() |
2301 | + # enabled when initial data ready |
2302 | + self.ui.on_initial_data_ready() |
2303 | self.assert_widget_availability(enabled=True) |
2304 | |
2305 | @skip_abstract_class |
2306 | def test_volume_are_enabled_until_stopped(self): |
2307 | """Folders and shares are enabled until offline.""" |
2308 | + self.ui.on_initial_data_ready() |
2309 | + |
2310 | self.do_connect() |
2311 | self.assert_widget_availability(enabled=True) |
2312 | |
2313 | @@ -1057,7 +1073,7 @@ |
2314 | """Perform the test per se before closing the dialog.""" |
2315 | self.assertTrue(self.ui.widget_is_visible(self.volume_dialog), |
2316 | '%s should be visible.' % self.volume_dialog_name) |
2317 | - self.assert_store_correct(self.volume_store, items) |
2318 | + self.assert_store_correct(items) |
2319 | |
2320 | items = self.build_some_data() |
2321 | setattr(self.ui.sd, self.name, items) |
2322 | @@ -1077,7 +1093,7 @@ |
2323 | """Perform the test per se before closing the dialog.""" |
2324 | self.assertTrue(self.ui.widget_is_visible(self.volume_dialog), |
2325 | '%s should be visible.' % self.volume_dialog_name) |
2326 | - self.assert_store_correct(self.volume_store, items) |
2327 | + self.assert_store_correct(items) |
2328 | |
2329 | items = self.build_some_data() |
2330 | setattr(self.ui.sd, self.name, items) |
2331 | @@ -1094,7 +1110,7 @@ |
2332 | def test_on_volume_clicked_handles_none(self): |
2333 | """On volume clicked handles None as items.""" |
2334 | setattr(self.ui.sd, self.name, None) |
2335 | - test = lambda: self.assert_store_correct(self.volume_store, []) |
2336 | + test = lambda: self.assert_store_correct([]) |
2337 | gobject.timeout_add(100, close_dialog, |
2338 | (self.volume_dialog, test)) |
2339 | self.on_volume_clicked(self.volume) |
2340 | @@ -1110,36 +1126,36 @@ |
2341 | def test_volume_columns_not_sorted_at_start(self): |
2342 | """Test volume columns are not sorted at start.""" |
2343 | msg = 'Column %s must not have the sort indicator on.' |
2344 | - for col in self.volume_view.get_columns(): |
2345 | + for col in self.view.get_columns(): |
2346 | self.assertFalse(col.get_sort_indicator(), msg % col.get_name()) |
2347 | |
2348 | @skip_abstract_class |
2349 | def test_volume_columns_are_clickable(self): |
2350 | """Test volume columns are clickable.""" |
2351 | msg = 'Column %s must be clickable.' |
2352 | - for col in self.volume_view.get_columns(): |
2353 | + for col in self.view.get_columns(): |
2354 | self.assertTrue(col.get_clickable(), msg % col.get_name()) |
2355 | |
2356 | @skip_abstract_class |
2357 | def test_volume_columns_clicked_signal(self): |
2358 | """Test volume columns clicks signal is properly connected.""" |
2359 | msg = 'Column %s must be connected to on_store_sort_column_changed.' |
2360 | - for col in self.volume_view.get_columns(): |
2361 | + for col in self.view.get_columns(): |
2362 | self.assertTrue(col.get_clickable(), msg % col.get_name()) |
2363 | |
2364 | @skip_abstract_class |
2365 | def test_volume_sorting(self): |
2366 | """Test volume panel can be re-sorted.""" |
2367 | - for i, col in enumerate(self.volume_view.get_columns()): |
2368 | + for idx, col in enumerate(self.view.get_columns()): |
2369 | col.clicked() # click on the column |
2370 | - self.assert_sort_order_correct(col, i, gtk.SORT_ASCENDING) |
2371 | + self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING) |
2372 | self.assert_sort_indicator_correct(col) |
2373 | |
2374 | col.clicked() # click on the column, sort order must change |
2375 | - self.assert_sort_order_correct(col, i, gtk.SORT_DESCENDING) |
2376 | + self.assert_sort_order_correct(col, idx, gtk.SORT_DESCENDING) |
2377 | |
2378 | col.clicked() # click again, sort order must be the first one |
2379 | - self.assert_sort_order_correct(col, i, gtk.SORT_ASCENDING) |
2380 | + self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING) |
2381 | |
2382 | |
2383 | class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase): |
2384 | @@ -1173,7 +1189,7 @@ |
2385 | i = int(kwargs['free_bytes']) |
2386 | kwargs['free_bytes'] = humanize_bytes(i, precision=2) |
2387 | item = self.data_type(**kwargs) |
2388 | - self.assert_store_correct(self.volume_store, [item]) |
2389 | + self.assert_store_correct([item]) |
2390 | |
2391 | gobject.timeout_add(100, close_dialog, |
2392 | (self.volume_dialog, test)) |
2393 | @@ -1266,17 +1282,19 @@ |
2394 | msg = 'buffer content must be %s (got %s instead).' |
2395 | self.assertEqual(actual, expected, msg % (expected, actual)) |
2396 | |
2397 | - def test_metadata_are_disabled_until_started(self): |
2398 | - """Metadata button is disabled until online.""" |
2399 | + def test_metadata_are_disabled_until_initial_data_ready(self): |
2400 | + """Metadata button is disabled until initial data.""" |
2401 | # disabled at startup |
2402 | self.assert_widget_availability(enabled=False) |
2403 | |
2404 | - # enabled when started |
2405 | - self.do_start() |
2406 | + # enabled when initial data ready |
2407 | + self.ui.on_initial_data_ready() |
2408 | self.assert_widget_availability(enabled=True) |
2409 | |
2410 | def test_metadata_are_enabled_until_stopped(self): |
2411 | """Metadata button is enabled until offline.""" |
2412 | + self.ui.on_initial_data_ready() |
2413 | + |
2414 | self.do_connect() |
2415 | self.assert_widget_availability(enabled=True) |
2416 | |
2417 | @@ -1515,11 +1533,12 @@ |
2418 | self.memento.setLevel(logging.DEBUG) |
2419 | logger = logging.getLogger('magicicada.ui') |
2420 | logger.addHandler(self.memento) |
2421 | + logger.setLevel(logging.DEBUG) |
2422 | |
2423 | - def assert_function_logs(self, func, *args, **kwargs): |
2424 | - """Check 'funcion' logs its inputs as DEBUG.""" |
2425 | + def assert_function_logs(self, level, func, *args, **kwargs): |
2426 | + """Check 'funcion' logs its inputs as 'level'.""" |
2427 | name = func.__name__ |
2428 | - msg = '%s must be logged as DEBUG' |
2429 | + msg = '%s must be logged with level %r' |
2430 | try: |
2431 | func(*args, **kwargs) |
2432 | except Exception: # pylint: disable=E0501, W0703 |
2433 | @@ -1528,33 +1547,201 @@ |
2434 | 'function (%s) must be logged as ERROR' % name) |
2435 | self.assertTrue(self.memento.check_error(exc), |
2436 | 'sys.exc_info (%s) must be logged as ERROR' % exc) |
2437 | - self.assertTrue(self.memento.check_debug(name), msg % name) |
2438 | + |
2439 | + memento_func = getattr(self.memento, 'check_%s' % level.lower()) |
2440 | + self.assertTrue(memento_func(name), msg % (name, level)) |
2441 | for arg in args: |
2442 | - self.assertTrue(self.memento.check_debug(str(arg)), msg % arg) |
2443 | + self.assertTrue(memento_func(str(arg)), msg % (arg, level)) |
2444 | for key, val in kwargs.iteritems(): |
2445 | arg = "'%s': %r" % (key, val) |
2446 | - self.assertTrue(self.memento.check_debug(arg), msg % arg) |
2447 | + self.assertTrue(memento_func(arg), msg % (arg, level)) |
2448 | |
2449 | def test_on_shares_clicked_logs(self): |
2450 | """Check _on_shares_clicked logs properly.""" |
2451 | args = ([0, object(), 'test', {}], object()) |
2452 | kwargs = dict(dialog=object()) |
2453 | - self.assert_function_logs(self.ui._on_shares_clicked, *args, **kwargs) |
2454 | + self.assert_function_logs(logging.getLevelName(logging.DEBUG), |
2455 | + self.ui._on_shares_clicked, *args, **kwargs) |
2456 | |
2457 | def test_on_status_changed_logs(self): |
2458 | """Check _on_status_changed logs properly.""" |
2459 | args = ('test status', 'status description', True, False, True) |
2460 | kwargs = dict(queues='bla', connection=None) |
2461 | - self.assert_function_logs(self.ui.on_status_changed, *args, **kwargs) |
2462 | + self.assert_function_logs(logging.getLevelName(logging.DEBUG), |
2463 | + self.ui.on_status_changed, *args, **kwargs) |
2464 | |
2465 | def test_on_queue_changed_logs(self): |
2466 | """Check _on_queue_changed logs properly.""" |
2467 | args = ('meta',) |
2468 | kwargs = dict(items=[0, object(), 'test', {}], must_highlight=True) |
2469 | - self.assert_function_logs(self.ui._on_queue_changed, *args, **kwargs) |
2470 | + self.assert_function_logs(logging.getLevelName(logging.DEBUG), |
2471 | + self.ui._on_queue_changed, *args, **kwargs) |
2472 | |
2473 | def test_on_metadata_ready_logs(self): |
2474 | """Check on_metadata_ready logs properly.""" |
2475 | args = () |
2476 | kwargs = dict(path='test', metadata=True) |
2477 | - self.assert_function_logs(self.ui.on_metadata_ready, *args, **kwargs) |
2478 | + self.assert_function_logs(logging.getLevelName(logging.DEBUG), |
2479 | + self.ui.on_metadata_ready, *args, **kwargs) |
2480 | + |
2481 | + def test_on_initial_data_ready_logs(self): |
2482 | + """Check on_initial_data_ready logs properly.""" |
2483 | + args = () |
2484 | + kwargs = dict(path='test', metadata=True) |
2485 | + self.assert_function_logs(logging.getLevelName(logging.INFO), |
2486 | + self.ui.on_initial_data_ready, |
2487 | + *args, **kwargs) |
2488 | + |
2489 | + |
2490 | +class PublicFilesTestCase(MagicicadaUITestCase): |
2491 | + """UI test cases for public files.""" |
2492 | + |
2493 | + name = 'public_files' |
2494 | + data_type = PublicFilesData |
2495 | + mapping = enumerate(['path', 'public_url', 'volume', 'node']) |
2496 | + |
2497 | + def setUp(self): |
2498 | + """Init.""" |
2499 | + super(PublicFilesTestCase, self).setUp() |
2500 | + self.store = self.ui.public_files_store |
2501 | + self.view = self.ui.public_files_view |
2502 | + |
2503 | + def assert_widget_availability(self, enabled=True, msg=None): |
2504 | + """Check widget availability according to 'enabled'.""" |
2505 | + widget = self.ui.public_files |
2506 | + self.assertTrue(self.ui.widget_is_visible(widget), 'must be visible') |
2507 | + |
2508 | + sensitive = widget.is_sensitive() |
2509 | + if msg is None: |
2510 | + msg = 'must %sbe sensitive' % ('' if enabled else 'not ',) |
2511 | + self.assertTrue(sensitive if enabled else not sensitive, msg) |
2512 | + |
2513 | + def test_initial_data_ready_callback_connected(self): |
2514 | + """The callback 'on_initial_online_data_ready' is connected to SD.""" |
2515 | + self.assertEqual(self.ui.sd.on_initial_online_data_ready_callback, |
2516 | + self.ui.on_initial_online_data_ready, |
2517 | + "on_initial_online_data_ready should be connected.") |
2518 | + |
2519 | + def test_disabled_until_initial_online_data_ready(self): |
2520 | + """Widget is disabled until initial online data ready.""" |
2521 | + self.assert_widget_availability(enabled=False, |
2522 | + msg='must be disabled at startup') |
2523 | + |
2524 | + self.ui.on_initial_online_data_ready() |
2525 | + msg = 'must be enabled when initial data ready' |
2526 | + self.assert_widget_availability(enabled=True, msg=msg) |
2527 | + |
2528 | + def test_enabled_until_stopped(self): |
2529 | + """Widget is enabled until stopped.""" |
2530 | + self.ui.on_initial_online_data_ready() |
2531 | + |
2532 | + self.do_connect() |
2533 | + self.assert_widget_availability(enabled=True) |
2534 | + |
2535 | + self.ui.on_online() |
2536 | + self.assert_widget_availability(enabled=True) |
2537 | + |
2538 | + self.ui.on_offline() |
2539 | + self.assert_widget_availability(enabled=True) |
2540 | + |
2541 | + self.ui.on_stopped() |
2542 | + self.assert_widget_availability(enabled=False) |
2543 | + |
2544 | + def test_public_files_close_emits_response_close(self): |
2545 | + """Test close button emits RESPONSE_CLOSE when clicked.""" |
2546 | + self.patch(self.ui.public_files_dialog, 'response', self._set_called) |
2547 | + self.ui.public_files_close.clicked() |
2548 | + self.assertEqual(self._called, ((gtk.RESPONSE_CLOSE,), {}), |
2549 | + 'close button should emit RESPONSE_CLOSE.') |
2550 | + |
2551 | + def test_on_public_files_clicked_runs_the_dialog(self): |
2552 | + """Test on_public_files_clicked run the public_files_dialog.""" |
2553 | + self.patch(self.ui.public_files_dialog, 'run', self._set_called) |
2554 | + self.ui.sd.public_files = {} |
2555 | + |
2556 | + visible = self.ui.widget_is_visible(self.ui.public_files_dialog) |
2557 | + self.assertFalse(visible, 'dialog should not be visible.') |
2558 | + |
2559 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2560 | + |
2561 | + self.assertEqual(self._called, ((), {})) |
2562 | + |
2563 | + def test_on_public_files_clicked_hides_the_dialog(self): |
2564 | + """Test on_public_files_clicked hides the public_files_dialog.""" |
2565 | + self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run |
2566 | + self.patch(self.ui.public_files_dialog, 'hide', self._set_called) |
2567 | + self.ui.sd.public_files = {} |
2568 | + |
2569 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2570 | + |
2571 | + self.assertEqual(self._called, ((), {})) |
2572 | + |
2573 | + def test_on_public_files_clicked_grabs_info_from_backend(self): |
2574 | + """Test on_public_files_clicked.""" |
2575 | + self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run |
2576 | + self.ui.sd.public_files = SAMPLE_PUBLIC_FILES |
2577 | + |
2578 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2579 | + |
2580 | + self.assert_store_correct(SAMPLE_PUBLIC_FILES.values()) |
2581 | + |
2582 | + def test_on_public_files_clicked_twice(self): |
2583 | + """Test on_public_files_clicked twice.""" |
2584 | + self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run |
2585 | + self.ui.sd.public_files = SAMPLE_PUBLIC_FILES |
2586 | + |
2587 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2588 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2589 | + |
2590 | + self.assert_store_correct(SAMPLE_PUBLIC_FILES.values()) |
2591 | + |
2592 | + def test_on_public_files_clicked_handles_none(self): |
2593 | + """On public_files clicked handles None as items.""" |
2594 | + self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run |
2595 | + self.ui.sd.public_files = None |
2596 | + |
2597 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2598 | + |
2599 | + self.assert_store_correct([]) |
2600 | + |
2601 | + def test_dialog_properties(self): |
2602 | + """The dialog has correct properties.""" |
2603 | + title = self.name.replace('_', ' ').capitalize() |
2604 | + self.assert_dialog_properties(dialog_name='public_files_dialog', |
2605 | + title=title) |
2606 | + |
2607 | + def test_columns_not_sorted_at_start(self): |
2608 | + """Test columns are not sorted at start.""" |
2609 | + msg = 'Column %s must not have the sort indicator on.' |
2610 | + for col in self.ui.public_files_view.get_columns(): |
2611 | + self.assertFalse(col.get_sort_indicator(), msg % col.get_name()) |
2612 | + |
2613 | + def test_columns_are_clickable(self): |
2614 | + """Test columns are clickable.""" |
2615 | + msg = 'Column %s must be clickable.' |
2616 | + for col in self.ui.public_files_view.get_columns(): |
2617 | + self.assertTrue(col.get_clickable(), msg % col.get_name()) |
2618 | + |
2619 | + def test_columns_clicked_signal(self): |
2620 | + """Test columns clicked signal is properly connected.""" |
2621 | + msg = 'Column %s must be connected to on_store_sort_column_changed.' |
2622 | + for col in self.ui.public_files_view.get_columns(): |
2623 | + self.assertTrue(col.get_clickable(), msg % col.get_name()) |
2624 | + |
2625 | + def test_sorting(self): |
2626 | + """Test public files panel can be re-sorted.""" |
2627 | + self.patch(self.ui.public_files_dialog, 'run', lambda: None) # no run |
2628 | + self.ui.sd.public_files = SAMPLE_PUBLIC_FILES |
2629 | + |
2630 | + self.ui.on_public_files_clicked(self.ui.public_files) |
2631 | + |
2632 | + for idx, col in enumerate(self.ui.public_files_view.get_columns()): |
2633 | + col.clicked() # click on the column |
2634 | + self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING) |
2635 | + self.assert_sort_indicator_correct(col) |
2636 | + |
2637 | + col.clicked() # click on the column, sort order must change |
2638 | + self.assert_sort_order_correct(col, idx, gtk.SORT_DESCENDING) |
2639 | + |
2640 | + col.clicked() # click again, sort order must be the first one |
2641 | + self.assert_sort_order_correct(col, idx, gtk.SORT_ASCENDING) |
2642 | |
2643 | === modified file 'magicicada/tests/test_syncdaemon.py' |
2644 | --- magicicada/tests/test_syncdaemon.py 2010-09-02 18:23:02 +0000 |
2645 | +++ magicicada/tests/test_syncdaemon.py 2011-01-07 23:06:32 +0000 |
2646 | @@ -22,7 +22,13 @@ |
2647 | import os |
2648 | import unittest |
2649 | |
2650 | -from magicicada.syncdaemon import SyncDaemon, State |
2651 | +from magicicada.dbusiface import PublicFilesData, ShareOperationError |
2652 | +from magicicada.syncdaemon import ( |
2653 | + ASKING_IDLE, |
2654 | + mandatory_callback, |
2655 | + State, |
2656 | + SyncDaemon, |
2657 | +) |
2658 | from magicicada.tests.helpers import MementoHandler |
2659 | |
2660 | from twisted.trial.unittest import TestCase as TwistedTestCase |
2661 | @@ -32,18 +38,23 @@ |
2662 | # It's ok to access private data in the test suite |
2663 | # pylint: disable=W0212 |
2664 | |
2665 | +# Lambda may not be necessary |
2666 | +# pylint: disable=W0108 |
2667 | + |
2668 | |
2669 | class FakeDBusInterface(object): |
2670 | """Fake DBus Interface, for SD to not use dbus at all during tests.""" |
2671 | |
2672 | fake_sd_started = False |
2673 | + fake_pf_data = PublicFilesData(volume='v', node='n', |
2674 | + path='p', public_url='u') |
2675 | + fake_share_response = None |
2676 | |
2677 | def __init__(self, sd): |
2678 | pass |
2679 | |
2680 | def shutdown(self): |
2681 | """Fake shutdown.""" |
2682 | - pass |
2683 | |
2684 | def get_status(self): |
2685 | """Fake status.""" |
2686 | @@ -58,10 +69,18 @@ |
2687 | start = quit = connect = disconnect = get_folders |
2688 | get_shares_to_me = get_shares_to_others = get_folders |
2689 | |
2690 | + def get_public_files(self): |
2691 | + """Fake public files.""" |
2692 | + return defer.succeed([self.fake_pf_data]) |
2693 | + |
2694 | def is_sd_started(self): |
2695 | """Fake response.""" |
2696 | return self.fake_sd_started |
2697 | |
2698 | + def accept_share(self, share_id): |
2699 | + """Fake accept share.""" |
2700 | + return self.fake_share_response |
2701 | + |
2702 | |
2703 | class BaseTest(TwistedTestCase): |
2704 | """Base test with a SD.""" |
2705 | @@ -70,6 +89,11 @@ |
2706 | |
2707 | def setUp(self): |
2708 | """Set up.""" |
2709 | + self.hdlr = MementoHandler() |
2710 | + self.hdlr.setLevel(logging.DEBUG) |
2711 | + logger = logging.getLogger('magicicada.syncdaemon') |
2712 | + logger.addHandler(self.hdlr) |
2713 | + logger.setLevel(logging.DEBUG) |
2714 | self.sd = SyncDaemon(FakeDBusInterface) |
2715 | |
2716 | def tearDown(self): |
2717 | @@ -77,9 +101,44 @@ |
2718 | self.sd.shutdown() |
2719 | |
2720 | |
2721 | +class MandatoryCallbackTests(BaseTest): |
2722 | + """Tests for the mandatory callback generic function.""" |
2723 | + |
2724 | + def test_log_function_name(self): |
2725 | + """Log the function name.""" |
2726 | + some_function = mandatory_callback('bar') |
2727 | + some_function() |
2728 | + self.assertTrue(self.hdlr.check_warning( |
2729 | + "Callback called but was not assigned", "bar")) |
2730 | + |
2731 | + def test_log_args(self): |
2732 | + """Log the arguments.""" |
2733 | + some_function = mandatory_callback('bar') |
2734 | + some_function(1, 2, b=45) |
2735 | + self.hdlr.debug = True |
2736 | + self.assertTrue(self.hdlr.check_warning( |
2737 | + "Callback called but was not assigned", |
2738 | + "1", "2", "'b': 45")) |
2739 | + |
2740 | + |
2741 | class InitialDataTests(unittest.TestCase): |
2742 | """Tests for initial data gathering.""" |
2743 | |
2744 | + def setUp(self): |
2745 | + """Set up the test.""" |
2746 | + self.sd = SyncDaemon(FakeDBusInterface) |
2747 | + |
2748 | + self.offline_called = False |
2749 | + self.sd.on_initial_data_ready_callback = lambda: setattr(self, |
2750 | + 'offline_called', True) |
2751 | + self.online_called = False |
2752 | + self.sd.on_initial_online_data_ready_callback = lambda: setattr(self, |
2753 | + 'online_called', True) |
2754 | + |
2755 | + def tearDown(self): |
2756 | + """Tear down the test.""" |
2757 | + self.sd.shutdown() |
2758 | + |
2759 | def test_called_by_start(self): |
2760 | """Check that start calls get initial data.""" |
2761 | sd = SyncDaemon(FakeDBusInterface) |
2762 | @@ -135,6 +194,68 @@ |
2763 | sd._get_initial_data() |
2764 | self.assertEqual(len(called), 3) |
2765 | |
2766 | + def test_public_files_info(self): |
2767 | + """Check we get the public files info at start.""" |
2768 | + sd = SyncDaemon(FakeDBusInterface) |
2769 | + fake_data = FakeDBusInterface.fake_pf_data |
2770 | + sd._get_initial_data() |
2771 | + self.assertEqual(sd.public_files[fake_data.node], fake_data) |
2772 | + |
2773 | + def test_all_ready(self): |
2774 | + """All data is ready.""" |
2775 | + self.sd._get_initial_data() |
2776 | + self.assertTrue(self.offline_called) |
2777 | + self.assertTrue(self.online_called) |
2778 | + |
2779 | + def test_no_public_files(self): |
2780 | + """Initial gathering is stuck in public files.""" |
2781 | + self.sd.dbus.get_public_files = lambda: defer.Deferred() |
2782 | + self.sd._get_initial_data() |
2783 | + self.assertTrue(self.offline_called) |
2784 | + self.assertFalse(self.online_called) |
2785 | + |
2786 | + def test_no_shares_to_others(self): |
2787 | + """Initial gathering is stuck in shares to others.""" |
2788 | + self.sd.dbus.get_shares_to_others = lambda: defer.Deferred() |
2789 | + self.sd._get_initial_data() |
2790 | + self.assertFalse(self.offline_called) |
2791 | + self.assertFalse(self.online_called) |
2792 | + |
2793 | + def test_no_shares_to_me(self): |
2794 | + """Initial gathering is stuck in shares to me.""" |
2795 | + self.sd.dbus.get_shares_to_me = lambda: defer.Deferred() |
2796 | + self.sd._get_initial_data() |
2797 | + self.assertFalse(self.offline_called) |
2798 | + self.assertFalse(self.online_called) |
2799 | + |
2800 | + def test_no_folders(self): |
2801 | + """Initial gathering is stuck in folders.""" |
2802 | + self.sd.dbus.get_folders = lambda: defer.Deferred() |
2803 | + self.sd._get_initial_data() |
2804 | + self.assertFalse(self.offline_called) |
2805 | + self.assertFalse(self.online_called) |
2806 | + |
2807 | + def test_no_meta_queue(self): |
2808 | + """Initial gathering is stuck in meta queue.""" |
2809 | + self.sd.dbus.get_meta_queue = lambda: defer.Deferred() |
2810 | + self.sd._get_initial_data() |
2811 | + self.assertFalse(self.offline_called) |
2812 | + self.assertFalse(self.online_called) |
2813 | + |
2814 | + def test_no_content_queue(self): |
2815 | + """Initial gathering is stuck in content queue.""" |
2816 | + self.sd.dbus.get_content_queue = lambda: defer.Deferred() |
2817 | + self.sd._get_initial_data() |
2818 | + self.assertFalse(self.offline_called) |
2819 | + self.assertFalse(self.online_called) |
2820 | + |
2821 | + def test_no_status(self): |
2822 | + """Initial gathering is stuck in status.""" |
2823 | + self.sd.dbus.get_status = lambda: defer.Deferred() |
2824 | + self.sd._get_initial_data() |
2825 | + self.assertFalse(self.offline_called) |
2826 | + self.assertFalse(self.online_called) |
2827 | + |
2828 | |
2829 | class StatusChangedTests(BaseTest): |
2830 | """Simple signals checking.""" |
2831 | @@ -175,7 +296,7 @@ |
2832 | False, 'queues', 'connection') |
2833 | return deferred |
2834 | |
2835 | - def test_status_changed_affects_cuurent_status(self): |
2836 | + def test_status_changed_affects_current_status(self): |
2837 | """Make changes to see how status are reflected.""" |
2838 | # one set of values |
2839 | self.sd.on_sd_status_changed('name1', 'description1', False, True, |
2840 | @@ -322,24 +443,115 @@ |
2841 | self.sd.on_sd_content_queue_changed() |
2842 | self.assertEqual(called, [['foo']]) |
2843 | |
2844 | - def test_CQ_state_nothing(self): |
2845 | + def test_cq_state_nothing(self): |
2846 | """Check the ContentQueue info, being nothing.""" |
2847 | self.sd.dbus.get_content_queue = lambda: defer.succeed([]) |
2848 | self.sd.on_sd_content_queue_changed() |
2849 | self.assertEqual(self.sd.content_queue, []) |
2850 | |
2851 | - def test_CQ_state_one(self): |
2852 | + def test_cq_state_one(self): |
2853 | """Check the ContentQueue info, being one.""" |
2854 | self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo']) |
2855 | self.sd.on_sd_content_queue_changed() |
2856 | self.assertEqual(self.sd.content_queue, ['foo']) |
2857 | |
2858 | - def test_CQ_state_two(self): |
2859 | + def test_cq_state_two(self): |
2860 | """Check the ContentQueue info, two.""" |
2861 | self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo', 'bar']) |
2862 | self.sd.on_sd_content_queue_changed() |
2863 | self.assertEqual(self.sd.content_queue, ['foo', 'bar']) |
2864 | |
2865 | + def test_cq_multiple_without_response_having_two(self): |
2866 | + """Behave correctly on two-changes burst.""" |
2867 | + d1 = defer.Deferred() |
2868 | + d2 = defer.Deferred() |
2869 | + called = [] |
2870 | + |
2871 | + def fake(): |
2872 | + """Fake.""" |
2873 | + d = d2 if called else d1 |
2874 | + called.append(None) |
2875 | + return d |
2876 | + self.sd.dbus.get_content_queue = fake |
2877 | + |
2878 | + # call twice |
2879 | + self.sd.on_sd_content_queue_changed() |
2880 | + self.sd.on_sd_content_queue_changed() |
2881 | + |
2882 | + # dbus function should be called once |
2883 | + self.assertEqual(len(called), 1) |
2884 | + |
2885 | + # first call returns |
2886 | + d1.callback(['foo']) |
2887 | + d2.callback(['bar']) |
2888 | + |
2889 | + # it should be called once more only |
2890 | + self.assertEqual(len(called), 2) |
2891 | + |
2892 | + def test_cq_multiple_without_response_having_three(self): |
2893 | + """Behave correctly on three-changes burst.""" |
2894 | + d1 = defer.Deferred() |
2895 | + d2 = defer.Deferred() |
2896 | + called = [] |
2897 | + |
2898 | + def fake(): |
2899 | + """Fake.""" |
2900 | + d = d2 if called else d1 |
2901 | + called.append(None) |
2902 | + return d |
2903 | + self.sd.dbus.get_content_queue = fake |
2904 | + |
2905 | + # call thrice |
2906 | + self.sd.on_sd_content_queue_changed() |
2907 | + self.sd.on_sd_content_queue_changed() |
2908 | + self.sd.on_sd_content_queue_changed() |
2909 | + |
2910 | + # dbus function should be called once |
2911 | + self.assertEqual(len(called), 1) |
2912 | + |
2913 | + # first call returns |
2914 | + d1.callback(['foo']) |
2915 | + d2.callback(['bar']) |
2916 | + |
2917 | + # it should be called once more only |
2918 | + self.assertEqual(len(called), 2) |
2919 | + |
2920 | + def test_cq_multiple_without_response_having_more_later(self): |
2921 | + """Behave correctly on changes burst delayed.""" |
2922 | + d1 = defer.Deferred() |
2923 | + d2 = defer.Deferred() |
2924 | + d3 = defer.Deferred() |
2925 | + defs = [d1, d2, d3] |
2926 | + called = [] |
2927 | + |
2928 | + def fake(): |
2929 | + """Fake.""" |
2930 | + d = defs[len(called)] |
2931 | + called.append(None) |
2932 | + return d |
2933 | + self.sd.dbus.get_content_queue = fake |
2934 | + |
2935 | + # call some times, dbus function should be called once |
2936 | + self.sd.on_sd_content_queue_changed() |
2937 | + self.sd.on_sd_content_queue_changed() |
2938 | + self.sd.on_sd_content_queue_changed() |
2939 | + self.assertEqual(len(called), 1) |
2940 | + |
2941 | + # first call returns |
2942 | + d1.callback(['foo']) |
2943 | + |
2944 | + # while calling second time, generate more requests |
2945 | + self.sd.on_sd_content_queue_changed() |
2946 | + self.sd.on_sd_content_queue_changed() |
2947 | + self.sd.on_sd_content_queue_changed() |
2948 | + |
2949 | + # second call returns |
2950 | + d2.callback(['foo']) |
2951 | + d3.callback(['foo']) |
2952 | + |
2953 | + # it should be called three times total |
2954 | + self.assertEqual(len(called), 3) |
2955 | + |
2956 | |
2957 | class MetaQueueChangedTests(BaseTest): |
2958 | """Check the MetaQueueChanged handling.""" |
2959 | @@ -526,6 +738,8 @@ |
2960 | |
2961 | def test_mq_caller_is_reset_last_time(self): |
2962 | """When MQ is polled last time, the caller should be back to None.""" |
2963 | + # Module 'twisted.internet.reactor' has no 'callLater' member |
2964 | + # pylint: disable=E1101 |
2965 | self.sd._mqcaller = reactor.callLater(100, lambda: None) |
2966 | self.sd.current_state.set(name='QUEUE_MANAGER', |
2967 | queues='WORKING_ON_CONTENT') |
2968 | @@ -550,6 +764,8 @@ |
2969 | 'WORKING_ON_CONTENT', 'connect') |
2970 | |
2971 | # allow time to see if a mistaken call happens |
2972 | + # Module 'twisted.internet.reactor' has no 'callLater' member |
2973 | + # pylint: disable=E1101 |
2974 | reactor.callLater(.5, deferred.callback, True) |
2975 | elif len(calls) == 4: |
2976 | pass # last call after state changed |
2977 | @@ -566,24 +782,132 @@ |
2978 | deferred = defer.Deferred() |
2979 | return deferred |
2980 | |
2981 | - def test_MQ_state_nothing(self): |
2982 | + def test_mq_state_nothing(self): |
2983 | """Check the MetaQueue info, being nothing.""" |
2984 | self.sd.dbus.get_meta_queue = lambda: defer.succeed([]) |
2985 | self.sd._check_mq() |
2986 | self.assertEqual(self.sd.meta_queue, []) |
2987 | |
2988 | - def test_MQ_state_one(self): |
2989 | + def test_mq_state_one(self): |
2990 | """Check the MetaQueue info, being one.""" |
2991 | self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo']) |
2992 | self.sd._check_mq() |
2993 | self.assertEqual(self.sd.meta_queue, ['foo']) |
2994 | |
2995 | - def test_MQ_state_two(self): |
2996 | + def test_mq_state_two(self): |
2997 | """Check the MetaQueue info, two.""" |
2998 | self.sd.dbus.get_meta_queue = lambda: defer.succeed(['foo', 'bar']) |
2999 | self.sd._check_mq() |
3000 | self.assertEqual(self.sd.meta_queue, ['foo', 'bar']) |
3001 | |
3002 | + def test_mq_multiple_without_response_having_two(self): |
3003 | + """Behave correctly on two-changes burst.""" |
3004 | + d1 = defer.Deferred() |
3005 | + d2 = defer.Deferred() |
3006 | + called = [] |
3007 | + |
3008 | + def fake(): |
3009 | + """Fake.""" |
3010 | + d = d2 if called else d1 |
3011 | + called.append(None) |
3012 | + return d |
3013 | + self.sd.dbus.get_meta_queue = fake |
3014 | + |
3015 | + # call twice |
3016 | + self.sd.on_sd_meta_queue_changed() |
3017 | + self.sd.on_sd_meta_queue_changed() |
3018 | + |
3019 | + # dbus function should be called once |
3020 | + self.assertEqual(len(called), 1) |
3021 | + |
3022 | + # first call returns |
3023 | + d1.callback(['foo']) |
3024 | + d2.callback(['bar']) |
3025 | + |
3026 | + # it should be called once more only |
3027 | + self.assertEqual(len(called), 2) |
3028 | + |
3029 | + def test_mq_multiple_without_response_having_three(self): |
3030 | + """Behave correctly on three-changes burst.""" |
3031 | + d1 = defer.Deferred() |
3032 | + d2 = defer.Deferred() |
3033 | + called = [] |
3034 | + |
3035 | + def fake(): |
3036 | + """Fake.""" |
3037 | + d = d2 if called else d1 |
3038 | + called.append(None) |
3039 | + return d |
3040 | + self.sd.dbus.get_meta_queue = fake |
3041 | + |
3042 | + # call thrice |
3043 | + self.sd.on_sd_meta_queue_changed() |
3044 | + self.sd.on_sd_meta_queue_changed() |
3045 | + self.sd.on_sd_meta_queue_changed() |
3046 | + |
3047 | + # dbus function should be called once |
3048 | + self.assertEqual(len(called), 1) |
3049 | + |
3050 | + # first call returns |
3051 | + d1.callback(['foo']) |
3052 | + d2.callback(['bar']) |
3053 | + |
3054 | + # it should be called once more only |
3055 | + self.assertEqual(len(called), 2) |
3056 | + |
3057 | + def test_mq_multiple_without_response_having_more_later(self): |
3058 | + """Behave correctly on changes burst delayed.""" |
3059 | + d1 = defer.Deferred() |
3060 | + d2 = defer.Deferred() |
3061 | + d3 = defer.Deferred() |
3062 | + defs = [d1, d2, d3] |
3063 | + called = [] |
3064 | + |
3065 | + def fake(): |
3066 | + """Fake.""" |
3067 | + d = defs[len(called)] |
3068 | + called.append(None) |
3069 | + return d |
3070 | + self.sd.dbus.get_meta_queue = fake |
3071 | + |
3072 | + # call some times, dbus function should be called once |
3073 | + self.sd.on_sd_meta_queue_changed() |
3074 | + self.sd.on_sd_meta_queue_changed() |
3075 | + self.sd.on_sd_meta_queue_changed() |
3076 | + self.assertEqual(len(called), 1) |
3077 | + |
3078 | + # first call returns |
3079 | + d1.callback(['foo']) |
3080 | + |
3081 | + # while calling second time, generate more requests |
3082 | + self.sd.on_sd_meta_queue_changed() |
3083 | + self.sd.on_sd_meta_queue_changed() |
3084 | + self.sd.on_sd_meta_queue_changed() |
3085 | + |
3086 | + # second call returns |
3087 | + d2.callback(['foo']) |
3088 | + d3.callback(['foo']) |
3089 | + |
3090 | + # it should be called three times total |
3091 | + self.assertEqual(len(called), 3) |
3092 | + |
3093 | + @defer.inlineCallbacks |
3094 | + def test_mq_multiple_support_error(self): |
3095 | + """Leave the state ok even with an error in the underlying call.""" |
3096 | + def fake(): |
3097 | + """Fake.""" |
3098 | + raise ValueError('foo') |
3099 | + self.sd.dbus.get_meta_queue = fake |
3100 | + |
3101 | + # call it, and "absorb" the error we just generated |
3102 | + try: |
3103 | + yield self.sd.on_sd_meta_queue_changed() |
3104 | + except ValueError: |
3105 | + pass |
3106 | + |
3107 | + # it should be in IDLE |
3108 | + self.assertEqual(self.sd._mq_asking, ASKING_IDLE) |
3109 | + |
3110 | |
3111 | class StateTests(unittest.TestCase): |
3112 | """Test State class.""" |
3113 | @@ -732,13 +1056,6 @@ |
3114 | False, 'queues', 'connection') |
3115 | self.assertTrue(self.called) |
3116 | |
3117 | - @defer.inlineCallbacks |
3118 | - def test_on_initial_data_ready(self): |
3119 | - """Called when SD gets all the initial data.""" |
3120 | - self.flag_called(self.sd, 'on_initial_data_ready_callback') |
3121 | - yield self.sd._get_initial_data() |
3122 | - self.assertTrue(self.called) |
3123 | - |
3124 | |
3125 | class TestLogs(unittest.TestCase): |
3126 | """Test logging.""" |
3127 | @@ -746,8 +1063,10 @@ |
3128 | def setUp(self): |
3129 | """Set up.""" |
3130 | self.hdlr = MementoHandler() |
3131 | - logging.getLogger('magicicada.syncdaemon').addHandler(self.hdlr) |
3132 | self.hdlr.setLevel(logging.DEBUG) |
3133 | + logger = logging.getLogger('magicicada.syncdaemon') |
3134 | + logger.addHandler(self.hdlr) |
3135 | + logger.setLevel(logging.DEBUG) |
3136 | self.sd = SyncDaemon(FakeDBusInterface) |
3137 | |
3138 | def tearDown(self): |
3139 | @@ -768,8 +1087,12 @@ |
3140 | def test_initial_value(self): |
3141 | """Log the initial filling.""" |
3142 | yield self.sd._get_initial_data() |
3143 | - self.assertTrue(self.hdlr.check_info("Getting initial data")) |
3144 | - self.assertTrue(self.hdlr.check_info("All initial data is ready")) |
3145 | + self.assertTrue(self.hdlr.check_info("Getting offline initial data")) |
3146 | + self.assertTrue(self.hdlr.check_info( |
3147 | + "All initial offline data is ready")) |
3148 | + self.assertTrue(self.hdlr.check_info("Getting online initial data")) |
3149 | + self.assertTrue(self.hdlr.check_info( |
3150 | + "All initial online data is ready")) |
3151 | |
3152 | def test_start(self): |
3153 | """Log the call to start.""" |
3154 | @@ -823,7 +1146,7 @@ |
3155 | self.assertTrue(self.hdlr.check_info( |
3156 | "SD Meta Queue info is new! 1 items")) |
3157 | |
3158 | - def test_content_queue_changed(self): |
3159 | + def test_meta_queue_changed(self): |
3160 | """Log that process_cq has new data.""" |
3161 | self.sd.dbus.get_content_queue = lambda: defer.succeed(['foo']) |
3162 | self.sd.on_sd_content_queue_changed() |
3163 | @@ -857,13 +1180,32 @@ |
3164 | self.sd.on_sd_name_owner_changed(True) |
3165 | self.assertTrue(self.hdlr.check_info("SD Name Owner changed: True")) |
3166 | |
3167 | + def test_on_public_files_list(self): |
3168 | + """Log we got a new public files list.""" |
3169 | + pf1 = PublicFilesData(volume='v', node='n1', path='p', public_url='u') |
3170 | + pf2 = PublicFilesData(volume='v', node='n2', path='p', public_url='u') |
3171 | + self.sd.on_sd_public_files_list([pf1, pf2]) |
3172 | + self.assertTrue(self.hdlr.check_info( |
3173 | + "Got new Public Files list (2 items)")) |
3174 | + |
3175 | + def test_on_public_files_changed(self): |
3176 | + """Log we got a change in the public files list.""" |
3177 | + self.sd.public_files = {} |
3178 | + pf = PublicFilesData(volume='volume', node='node', |
3179 | + path='path', public_url='url') |
3180 | + self.sd.on_sd_public_files_changed(pf, True) |
3181 | + self.assertTrue(self.hdlr.check_info( |
3182 | + "Change in Public Files list! is_public=True", |
3183 | + "node=node", "path=u'path'")) |
3184 | + |
3185 | |
3186 | class MetadataTests(BaseTest): |
3187 | """Get Metadata info.""" |
3188 | |
3189 | def test_get_metadata_no_callback_set(self): |
3190 | """It's mandatory to set the callback for this response.""" |
3191 | - self.assertRaises(ValueError, self.sd.get_metadata, 'path') |
3192 | + self.sd.on_metadata_ready_callback() |
3193 | + self.assertTrue(self.hdlr.check_warning('on_metadata_ready_callback')) |
3194 | |
3195 | def test_get_metadata_ok(self): |
3196 | """Get the metadata for given path.""" |
3197 | @@ -988,3 +1330,139 @@ |
3198 | |
3199 | # test |
3200 | self.assertEqual(cal, [2]) |
3201 | + |
3202 | + |
3203 | +class PublicFilesTests(BaseTest): |
3204 | + """PublicFiles checking.""" |
3205 | + |
3206 | + def test_signal_updates_value(self): |
3207 | + """Test that when signal arrives it updates the internal attribute.""" |
3208 | + pf = PublicFilesData(volume='volume', node='node', |
3209 | + path='path', public_url='url') |
3210 | + self.sd.on_sd_public_files_list([pf]) |
3211 | + self.assertEqual(len(self.sd.public_files), 1) |
3212 | + pf = self.sd.public_files['node'] |
3213 | + self.assertEqual(pf.volume, 'volume') |
3214 | + self.assertEqual(pf.node, 'node') |
3215 | + self.assertEqual(pf.public_url, 'url') |
3216 | + self.assertEqual(pf.path, 'path') |
3217 | + |
3218 | + def test_public_files_changed_added_noprevious(self): |
3219 | + """Test that it adds the new public file.""" |
3220 | + # reset and add |
3221 | + self.sd.public_files = {} |
3222 | + pf = PublicFilesData(volume='volume', node='node', |
3223 | + path='path', public_url='url') |
3224 | + self.sd.on_sd_public_files_changed(pf, True) |
3225 | + |
3226 | + # check is there |
3227 | + pf = self.sd.public_files['node'] |
3228 | + self.assertEqual(pf.volume, 'volume') |
3229 | + self.assertEqual(pf.node, 'node') |
3230 | + self.assertEqual(pf.public_url, 'url') |
3231 | + self.assertEqual(pf.path, 'path') |
3232 | + |
3233 | + def test_public_files_changed_added_previous(self): |
3234 | + """Test that it updates the public file.""" |
3235 | + # add one |
3236 | + pf = PublicFilesData(volume='volume', node='node', |
3237 | + path='path', public_url='url1') |
3238 | + self.sd.on_sd_public_files_changed(pf, True) |
3239 | + |
3240 | + # update it |
3241 | + pf = PublicFilesData(volume='volume', node='node', |
3242 | + path='path', public_url='url2') |
3243 | + self.sd.on_sd_public_files_changed(pf, True) |
3244 | + |
3245 | + # check is updated |
3246 | + pf = self.sd.public_files['node'] |
3247 | + self.assertEqual(pf.public_url, 'url2') |
3248 | + |
3249 | + def test_public_files_changed_removed_previous(self): |
3250 | + """Test that it deletes the public file.""" |
3251 | + # add one |
3252 | + pf = PublicFilesData(volume='volume', node='node', |
3253 | + path='path', public_url='url') |
3254 | + self.sd.on_sd_public_files_changed(pf, True) |
3255 | + |
3256 | + # remove it |
3257 | + self.sd.on_sd_public_files_changed(pf, False) |
3258 | + |
3259 | + # check is no longer there |
3260 | + self.assertFalse('node' in self.sd.public_files) |
3261 | + |
3262 | + def test_public_files_changed_removed_noprevious(self): |
3263 | + """Test that support the deletion of anything.""" |
3264 | + # reset and remove |
3265 | + self.sd.public_files = {} |
3266 | + pf = PublicFilesData(volume='volume', node='node', |
3267 | + path='path', public_url='url') |
3268 | + self.sd.on_sd_public_files_changed(pf, False) |
3269 | + |
3270 | + # check that still is not there |
3271 | + self.assertFalse('node' in self.sd.public_files) |
3272 | + |
3273 | + |
3274 | +class HandlingSharesTests(BaseTest): |
3275 | + """Handling shares checking.""" |
3276 | + |
3277 | + def test_on_share_op_success_callback_set(self): |
3278 | + """It's mandatory to set the callback for this response.""" |
3279 | + self.sd.on_share_op_success_callback() |
3280 | + self.assertTrue(self.hdlr.check_warning( |
3281 | + 'on_share_op_success_callback')) |
3282 | + |
3283 | + def test_on_share_op_error_callback_set(self): |
3284 | + """It's mandatory to set the callback for this response.""" |
3285 | + self.sd.on_share_op_error_callback() |
3286 | + self.assertTrue(self.hdlr.check_warning('on_share_op_error_callback')) |
3287 | + |
3288 | + def test_accept_share_ok(self): |
3289 | + """Accepting a share finishes ok.""" |
3290 | + # monkeypatch |
3291 | + called = [] |
3292 | + self.sd.dbus.fake_share_response = defer.succeed(None) |
3293 | + self.sd.on_share_op_success_callback = lambda sid: called.append(sid) |
3294 | + self.sd.on_share_op_error_callback = lambda *a: None |
3295 | + |
3296 | + # execute and test |
3297 | + self.sd.accept_share('share_id') |
3298 | + self.assertEqual(called, ['share_id']) |
3299 | + self.assertTrue(self.hdlr.check_info("Accepting share", "share_id", |
3300 | + "started")) |
3301 | + self.assertTrue(self.hdlr.check_info("Accepting share", "share_id", |
3302 | + "finished successfully")) |
3303 | + |
3304 | + def test_accept_share_failure(self): |
3305 | + """Accepting a share finishes bad.""" |
3306 | + # monkeypatch |
3307 | + called = [] |
3308 | + e = ShareOperationError(share_id='foo', error='bar') |
3309 | + self.sd.dbus.fake_share_response = defer.fail(e) |
3310 | + self.sd.on_share_op_error_callback = \ |
3311 | + lambda sid, e: called.append((sid, e)) |
3312 | + self.sd.on_share_op_success_callback = lambda *a: None |
3313 | + |
3314 | + # execute and test |
3315 | + self.sd.accept_share('share_id') |
3316 | + self.assertEqual(called, [('share_id', 'bar')]) |
3317 | + self.assertTrue(self.hdlr.check_info("Accepting share", "share_id", |
3318 | + "started")) |
3319 | + self.assertTrue(self.hdlr.check_info("Accepting share", "share_id", |
3320 | + "finished with error", "bar")) |
3321 | + |
3322 | + def test_accept_share_error(self): |
3323 | + """Really bad error when accepting a share.""" |
3324 | + # monkeypatch |
3325 | + e = ValueError('unexpected failure') |
3326 | + self.sd.dbus.fake_share_response = defer.fail(e) |
3327 | + self.sd.on_share_op_error_callback = lambda *a: None |
3328 | + self.sd.on_share_op_success_callback = lambda *a: None |
3329 | + |
3330 | + # execute and test |
3331 | + self.sd.accept_share('share_id') |
3332 | + self.assertTrue(self.hdlr.check_info("Accepting share", "share_id", |
3333 | + "started")) |
3334 | + self.assertTrue(self.hdlr.check_error( |
3335 | + "Unexpected error when accepting share", "share_id", |
3336 | + "ValueError", "unexpected failure")) |
3337 | |
3338 | === modified file 'setup.py' (properties changed: -x to +x) |
3339 | --- setup.py 2010-09-02 19:26:08 +0000 |
3340 | +++ setup.py 2011-01-07 23:06:32 +0000 |
3341 | @@ -1,13 +1,21 @@ |
3342 | #!/usr/bin/env python |
3343 | -# -*- coding: utf-8 -*- |
3344 | -### BEGIN LICENSE |
3345 | -# This file is in the public domain |
3346 | -### END LICENSE |
3347 | + |
3348 | +# Copyright 2010 Chicharreros |
3349 | +# |
3350 | +# This program is free software: you can redistribute it and/or modify it |
3351 | +# under the terms of the GNU General Public License version 3, as published |
3352 | +# by the Free Software Foundation. |
3353 | +# |
3354 | +# This program is distributed in the hope that it will be useful, but |
3355 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
3356 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3357 | +# PURPOSE. See the GNU General Public License for more details. |
3358 | +# |
3359 | +# You should have received a copy of the GNU General Public License along |
3360 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
3361 | |
3362 | """Build tar.gz and related for magicicada.""" |
3363 | |
3364 | -################# DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ################# |
3365 | - |
3366 | import os |
3367 | import sys |
3368 | |
3369 | @@ -80,17 +88,14 @@ |
3370 | update_data_path(self.prefix, previous_value) |
3371 | |
3372 | |
3373 | -############################################################################## |
3374 | -#################### YOU SHOULD MODIFY ONLY WHAT IS BELOW #################### |
3375 | -############################################################################## |
3376 | - |
3377 | DistUtilsExtra.auto.setup( |
3378 | name='magicicada', |
3379 | - version='0.2', |
3380 | + version='0.3.0', |
3381 | license='GPL-3', |
3382 | author='Natalia Bidart', |
3383 | - author_email='natalia.bidart@ubuntu.com', |
3384 | - description='A GTK+ frontend for the "Chicharra" part of Ubuntu One.', |
3385 | - #long_description='Here a longer description', |
3386 | + author_email='nataliabidart@gmail.com', |
3387 | + description='A GTK+ frontend for Ubuntu One.', |
3388 | + long_description='This application provides a GTK frontend to manage ' \ |
3389 | + 'the file synchronisation service of Ubuntu One.', |
3390 | url='https://launchpad.net/magicicada', |
3391 | cmdclass={'install': InstallAndUpdateDataDirectory}) |
Hi looks ok, but a couple of small things pile up: ubuntu- one-client (>= foo), depend on python-xdg
* 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-
* 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.