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

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

Description of the change

  * New upstream release:

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

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

  * debian/control
    - updated depedency list

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

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

review: Needs Fixing
9. By Natalia Bidart

Depending on freshly released ubuntuone-client 1.5.2.

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

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

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

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

Fixed as per suggestion.

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

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

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

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

> * No mention of the description change.

Added.

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

Added.

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

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

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

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

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

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

Thanks a lot for the feedback!

10. By Natalia Bidart

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

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

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

Stefano, I forgot to as about:

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

what would be a better formatting?

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

Thanks, that was quick.

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

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

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

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

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

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

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

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

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

> what would be a better formatting?
Wrapping like this

* this is a multilne
  bulleted point.

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

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

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

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

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

Thanks for the pointer, all fixed.

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

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

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

Fixed!

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

Also fixed :-)

Thanks again!

11. By Natalia Bidart

* debian/copyright
    - Fixed formatting to be DEP5 compatible

12. By Natalia Bidart

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

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

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

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

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

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'
30Binary 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 &lt;natalia.bidart@gmail.com&gt;
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})

Subscribers

People subscribed via source and target branches