Merge lp:~dobey/ubuntu/oneiric/ubuntuone-control-panel/release-113 into lp:ubuntu/oneiric/ubuntuone-control-panel

Proposed by dobey
Status: Merged
Merged at revision: 29
Proposed branch: lp:~dobey/ubuntu/oneiric/ubuntuone-control-panel/release-113
Merge into: lp:ubuntu/oneiric/ubuntuone-control-panel
Diff against target: 5305 lines (+1530/-1352)
42 files modified
PKG-INFO (+2/-2)
bin/ubuntuone-control-panel-gtk (+3/-3)
bin/ubuntuone-control-panel-qt (+3/-3)
data/qt/account.ui (+2/-2)
data/qt/controlpanel.ui (+42/-39)
data/qt/device.ui (+3/-0)
data/qt/filesyncstatus.ui (+4/-2)
data/qt/folders.ui (+8/-5)
data/qt/images.qrc (+2/-0)
data/qt/mainwindow.ui (+1/-1)
data/qt/preferences.ui (+4/-1)
data/qt/ubuntuone.qss (+63/-46)
debian/changelog (+7/-0)
po/ubuntuone-control-panel.pot (+1/-1)
run-tests (+4/-2)
run-tests.bat (+1/-1)
setup.py (+13/-7)
ubuntuone/controlpanel/backend.py (+65/-57)
ubuntuone/controlpanel/dbus_service.py (+12/-4)
ubuntuone/controlpanel/dbustests/test_dbus_service.py (+1/-1)
ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py (+2/-1)
ubuntuone/controlpanel/gui/gtk/gui.py (+48/-77)
ubuntuone/controlpanel/gui/gtk/tests/__init__.py (+12/-14)
ubuntuone/controlpanel/gui/gtk/tests/test_gui.py (+24/-23)
ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py (+126/-81)
ubuntuone/controlpanel/gui/qt/filesyncstatus.py (+8/-0)
ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py (+12/-0)
ubuntuone/controlpanel/gui/qt/ui/account_ui.py (+3/-3)
ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py (+24/-12)
ubuntuone/controlpanel/gui/qt/ui/device_ui.py (+2/-1)
ubuntuone/controlpanel/gui/qt/ui/devices_ui.py (+1/-1)
ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py (+2/-1)
ubuntuone/controlpanel/gui/qt/ui/folders_ui.py (+3/-4)
ubuntuone/controlpanel/gui/qt/ui/images_rc.py (+459/-447)
ubuntuone/controlpanel/gui/qt/ui/loadingoverlay_ui.py (+1/-1)
ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py (+2/-2)
ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py (+3/-2)
ubuntuone/controlpanel/login_client.py (+18/-5)
ubuntuone/controlpanel/sd_client/__init__.py (+176/-211)
ubuntuone/controlpanel/tests/test_backend.py (+205/-168)
ubuntuone/controlpanel/tests/test_login_client.py (+48/-6)
ubuntuone/controlpanel/tests/test_sd_client.py (+110/-115)
To merge this branch: bzr merge lp:~dobey/ubuntu/oneiric/ubuntuone-control-panel/release-113
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+72945@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2011-08-12 19:12:08 +0000
3+++ PKG-INFO 2011-08-25 19:40:18 +0000
4@@ -1,12 +1,12 @@
5 Metadata-Version: 1.1
6 Name: ubuntuone-control-panel
7-Version: 1.1.2
8+Version: 1.1.3
9 Summary: Ubuntu One Control Panel
10 Home-page: https://launchpad.net/ubuntuone-control-panel
11 Author: Natalia Bidart
12 Author-email: natalia.bidart@canonical.com
13 License: GPL v3
14-Description: Application to manage a Ubuntu One account. Provides aDBus service to query/modify all the Ubuntu One bits.
15+Description: Application to manage a Ubuntu One account. Provides a DBus service to query/modify all the Ubuntu One bits.
16 Platform: UNKNOWN
17 Requires: PyQt4
18 Requires: apt
19
20=== modified file 'bin/ubuntuone-control-panel-gtk'
21--- bin/ubuntuone-control-panel-gtk 2011-07-22 21:26:48 +0000
22+++ bin/ubuntuone-control-panel-gtk 2011-08-25 19:40:18 +0000
23@@ -39,12 +39,12 @@
24 result = OptionParser(usage=usage)
25 result.add_option("", "--switch-to", dest="switch_to", type="string",
26 metavar="PANEL_NAME", default="",
27- help="Start the Ubuntu One Control Panel (GTK) in the "
28+ help="Start Ubuntu One in the "
29 "PANEL_NAME tab. Possible values are: "
30 "dashboard, volumes, devices, applications")
31 result.add_option("-a", "--alert", dest="alert", action="store_true",
32- default=False, help="Start the Ubuntu One Control Panel "
33- "(GTK) alerting the user to its presence.")
34+ default=False, help="Start Ubuntu One "
35+ "alerting the user to its presence.")
36 return result
37
38
39
40=== modified file 'bin/ubuntuone-control-panel-qt'
41--- bin/ubuntuone-control-panel-qt 2011-07-22 21:26:48 +0000
42+++ bin/ubuntuone-control-panel-qt 2011-08-25 19:40:18 +0000
43@@ -39,12 +39,12 @@
44 result = OptionParser(usage=usage)
45 result.add_option("", "--switch-to", dest="switch_to", type="string",
46 metavar="PANEL_NAME", default="",
47- help="Start the Ubuntu One Control Panel (QT) in the "
48+ help="Start Ubuntu One in the "
49 "PANEL_NAME tab. Possible values are: "
50 "dashboard, volumes, devices, applications")
51 result.add_option("-a", "--alert", dest="alert", action="store_true",
52- default=False, help="Start the Ubuntu One Control Panel "
53- "(QT) alerting the user to its presence.")
54+ default=False, help="Start Ubuntu One "
55+ "alerting the user to its presence.")
56 return result
57
58
59
60=== modified file 'data/facebook.png'
61Binary files data/facebook.png 2011-01-25 19:08:59 +0000 and data/facebook.png 2011-08-25 19:40:18 +0000 differ
62=== modified file 'data/qt/account.ui'
63--- data/qt/account.ui 2011-08-12 19:12:08 +0000
64+++ data/qt/account.ui 2011-08-25 19:40:18 +0000
65@@ -22,7 +22,7 @@
66 <property name="verticalSpacing">
67 <number>30</number>
68 </property>
69- <item row="2" column="2">
70+ <item row="0" column="2">
71 <widget class="GoToWebButton" name="edit_profile_button">
72 <property name="text">
73 <string>Edit personal details online</string>
74@@ -85,7 +85,7 @@
75 </layout>
76 </widget>
77 </item>
78- <item row="0" column="2">
79+ <item row="2" column="2">
80 <widget class="GoToWebButton" name="edit_services_button">
81 <property name="text">
82 <string>Edit your services online</string>
83
84=== modified file 'data/qt/controlpanel.ui'
85--- data/qt/controlpanel.ui 2011-08-12 19:12:08 +0000
86+++ data/qt/controlpanel.ui 2011-08-25 19:40:18 +0000
87@@ -6,8 +6,8 @@
88 <rect>
89 <x>0</x>
90 <y>0</y>
91- <width>387</width>
92- <height>203</height>
93+ <width>367</width>
94+ <height>142</height>
95 </rect>
96 </property>
97 <property name="sizePolicy">
98@@ -221,7 +221,7 @@
99 </widget>
100 <widget class="PreferencesPanel" name="preferences_tab">
101 <attribute name="title">
102- <string>Preferences</string>
103+ <string>Settings</string>
104 </attribute>
105 </widget>
106 <widget class="AccountPanel" name="account_tab">
107@@ -233,22 +233,25 @@
108 </item>
109 <item>
110 <widget class="QFrame" name="frame_footer">
111+ <property name="sizePolicy">
112+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
113+ <horstretch>0</horstretch>
114+ <verstretch>0</verstretch>
115+ </sizepolicy>
116+ </property>
117+ <property name="maximumSize">
118+ <size>
119+ <width>16777215</width>
120+ <height>30</height>
121+ </size>
122+ </property>
123 <layout class="QHBoxLayout" name="horizontalLayout">
124 <property name="spacing">
125 <number>5</number>
126 </property>
127- <property name="leftMargin">
128- <number>3</number>
129- </property>
130- <property name="topMargin">
131+ <property name="margin">
132 <number>0</number>
133 </property>
134- <property name="rightMargin">
135- <number>3</number>
136- </property>
137- <property name="bottomMargin">
138- <number>3</number>
139- </property>
140 <item>
141 <widget class="GoToWebButton" name="help_button">
142 <property name="text">
143@@ -282,49 +285,49 @@
144 </widget>
145 </item>
146 <item>
147- <widget class="QToolButton" name="twitter_button">
148+ <widget class="QPushButton" name="twitter_button">
149+ <property name="sizePolicy">
150+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
151+ <horstretch>0</horstretch>
152+ <verstretch>0</verstretch>
153+ </sizepolicy>
154+ </property>
155+ <property name="maximumSize">
156+ <size>
157+ <width>16</width>
158+ <height>16</height>
159+ </size>
160+ </property>
161 <property name="cursor">
162 <cursorShape>PointingHandCursor</cursorShape>
163 </property>
164- <property name="styleSheet">
165- <string notr="true">border: 0;</string>
166- </property>
167- <property name="text">
168- <string/>
169- </property>
170 <property name="icon">
171 <iconset resource="images.qrc">
172 <normaloff>:/twitter.png</normaloff>:/twitter.png</iconset>
173 </property>
174- <property name="iconSize">
175- <size>
176- <width>22</width>
177- <height>22</height>
178- </size>
179- </property>
180 </widget>
181 </item>
182 <item>
183- <widget class="QToolButton" name="facebook_button">
184+ <widget class="QPushButton" name="facebook_button">
185+ <property name="sizePolicy">
186+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
187+ <horstretch>0</horstretch>
188+ <verstretch>0</verstretch>
189+ </sizepolicy>
190+ </property>
191+ <property name="maximumSize">
192+ <size>
193+ <width>16</width>
194+ <height>16</height>
195+ </size>
196+ </property>
197 <property name="cursor">
198 <cursorShape>PointingHandCursor</cursorShape>
199 </property>
200- <property name="styleSheet">
201- <string notr="true">border: 0;</string>
202- </property>
203- <property name="text">
204- <string/>
205- </property>
206 <property name="icon">
207 <iconset resource="images.qrc">
208 <normaloff>:/facebook.png</normaloff>:/facebook.png</iconset>
209 </property>
210- <property name="iconSize">
211- <size>
212- <width>22</width>
213- <height>22</height>
214- </size>
215- </property>
216 </widget>
217 </item>
218 </layout>
219
220=== modified file 'data/qt/device.ui'
221--- data/qt/device.ui 2011-08-12 19:12:08 +0000
222+++ data/qt/device.ui 2011-08-25 19:40:18 +0000
223@@ -46,6 +46,9 @@
224 <property name="text">
225 <string>Remove</string>
226 </property>
227+ <property name="secondary" stdset="0">
228+ <bool>true</bool>
229+ </property>
230 </widget>
231 </item>
232 </layout>
233
234=== modified file 'data/qt/filesyncstatus.ui'
235--- data/qt/filesyncstatus.ui 2011-08-12 19:12:08 +0000
236+++ data/qt/filesyncstatus.ui 2011-08-25 19:40:18 +0000
237@@ -20,8 +20,7 @@
238 <item>
239 <layout class="QHBoxLayout" name="horizontalLayout">
240 <item>
241- <widget class="QLabel" name="sync_status_icon">
242- </widget>
243+ <widget class="QLabel" name="sync_status_icon"/>
244 </item>
245 <item>
246 <widget class="QLabel" name="sync_status_label">
247@@ -40,6 +39,9 @@
248 </item>
249 <item>
250 <widget class="QPushButton" name="sync_status_button">
251+ <property name="secondary" stdset="0">
252+ <bool>true</bool>
253+ </property>
254 </widget>
255 </item>
256 </layout>
257
258=== modified file 'data/qt/folders.ui'
259--- data/qt/folders.ui 2011-08-12 19:12:08 +0000
260+++ data/qt/folders.ui 2011-08-25 19:40:18 +0000
261@@ -13,9 +13,6 @@
262 <property name="windowTitle">
263 <string notr="true">Form</string>
264 </property>
265- <property name="styleSheet">
266- <string notr="true">padding: 0px;</string>
267- </property>
268 <layout class="QVBoxLayout" name="verticalLayout">
269 <property name="spacing">
270 <number>0</number>
271@@ -38,7 +35,7 @@
272 <item>
273 <widget class="GoToWebButton" name="share_publish_button">
274 <property name="sizePolicy">
275- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
276+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
277 <horstretch>0</horstretch>
278 <verstretch>0</verstretch>
279 </sizepolicy>
280@@ -136,6 +133,9 @@
281 <property name="orientation">
282 <enum>Qt::Horizontal</enum>
283 </property>
284+ <property name="sizeType">
285+ <enum>QSizePolicy::Expanding</enum>
286+ </property>
287 <property name="sizeHint" stdset="0">
288 <size>
289 <width>53</width>
290@@ -147,7 +147,7 @@
291 <item>
292 <widget class="AddFolderButton" name="add_folder_button">
293 <property name="sizePolicy">
294- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
295+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
296 <horstretch>0</horstretch>
297 <verstretch>0</verstretch>
298 </sizepolicy>
299@@ -162,6 +162,9 @@
300 <property name="orientation">
301 <enum>Qt::Horizontal</enum>
302 </property>
303+ <property name="sizeType">
304+ <enum>QSizePolicy::Expanding</enum>
305+ </property>
306 <property name="sizeHint" stdset="0">
307 <size>
308 <width>40</width>
309
310=== modified file 'data/qt/images.qrc'
311--- data/qt/images.qrc 2011-07-22 21:26:48 +0000
312+++ data/qt/images.qrc 2011-08-25 19:40:18 +0000
313@@ -10,6 +10,8 @@
314 <file>../computer.png</file>
315 <file>../phone.png</file>
316 <file>../twitter.png</file>
317+ <file>../twitter.png</file>
318+ <file>../facebook.png</file>
319 <file>../facebook.png</file>
320 <file>../external_icon_white.png</file>
321 <file>../Ubuntu-R.ttf</file>
322
323=== modified file 'data/qt/mainwindow.ui'
324--- data/qt/mainwindow.ui 2011-07-22 21:26:48 +0000
325+++ data/qt/mainwindow.ui 2011-08-25 19:40:18 +0000
326@@ -23,7 +23,7 @@
327 </size>
328 </property>
329 <property name="windowTitle">
330- <string>Ubuntu One Control Panel</string>
331+ <string>Ubuntu One</string>
332 </property>
333 <property name="windowIcon">
334 <iconset resource="images.qrc">
335
336=== modified file 'data/qt/preferences.ui'
337--- data/qt/preferences.ui 2011-08-12 19:12:08 +0000
338+++ data/qt/preferences.ui 2011-08-25 19:40:18 +0000
339@@ -23,7 +23,7 @@
340 <item>
341 <widget class="QGroupBox" name="bandwidth_settings">
342 <property name="title">
343- <string>Bandwidth settings</string>
344+ <string>Bandwidth Settings</string>
345 </property>
346 <layout class="QGridLayout" name="gridLayout">
347 <property name="margin">
348@@ -202,6 +202,9 @@
349 <property name="text">
350 <string>Default settings</string>
351 </property>
352+ <property name="secondary" stdset="0">
353+ <bool>true</bool>
354+ </property>
355 </widget>
356 </item>
357 </layout>
358
359=== modified file 'data/qt/ubuntuone.qss'
360--- data/qt/ubuntuone.qss 2011-08-12 19:12:08 +0000
361+++ data/qt/ubuntuone.qss 2011-08-25 19:40:18 +0000
362@@ -4,6 +4,8 @@
363
364 QWidget {
365 font-family: "Ubuntu";
366+ font-size: 13px;
367+ color: #333333;
368 }
369
370 QFrame {
371@@ -42,59 +44,82 @@
372 border-bottom-width: 1px;
373 }
374
375+QFrame#frm_box { /*The Loading Overlay frame.*/
376+ background: #ffffff;
377+ border-radius: 5px;
378+ border-style: solid;
379+ border-color: #939389;
380+ border-width: 1px;
381+ color: white;
382+ min-height: 100px;
383+}
384+
385+QFrame#frm_box > QLabel {
386+ font-size: 24px;
387+}
388+
389+QPushButton {
390+ border-radius: 5px;
391+ border-style: solid;
392+ padding: 6px;
393+ padding-left: 20px;
394+ padding-right: 20px;
395+ border-width: 1px;
396+}
397+
398 QPushButton[enabled="true"] {
399 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
400- stop: 0 #fecfc2, stop: 1.0 #e44e19);
401- border-radius: 5px;
402- border-style: solid;
403- padding: 6px;
404+ stop: 0 #fe9e84,stop: 1.0 #dd4814);
405 color: white;
406- border-color: #939389;
407- border-width: 1px;
408- height: 14px;
409+ border-color: #999999;
410 }
411
412 QPushButton:hover[enabled="true"] {
413 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
414- stop: 0 #fedad1,stop: 1.0 #e47a55);
415- border-radius: 5px;
416- border-style: solid;
417- padding: 6px;
418+ stop: 0 #ffb19c,stop: 1.0 #dd4814);
419 color: white;
420- border-color: #939389;
421- border-width: 1px;
422- height: 12px;
423+ border-color: #999999;
424 }
425
426 QPushButton:pressed[enabled="true"] {
427 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
428- stop: 0 #e44e19,stop: 1.0 #fecfc2);
429- border-radius: 5px;
430- border-style: solid;
431- padding: 6px;
432+ stop: 0 #b93f14,stop: 1.0 #dd4814);
433 color: white;
434- border-color: #939389;
435- border-width: 1px;
436- height: 12px;
437+ border-color: #999999;
438+}
439+
440+QPushButton[secondary="true"] {
441+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
442+ stop: 0 #ffffff,stop: 1.0 #e6e6e6);
443+ color: #333333;
444+ border-color: #999999;
445+}
446+
447+QPushButton:hover[secondary="true"] {
448+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
449+ stop: 0 #ffffff,stop: 1.0 #ededed);
450+ color: #333333;
451+ border-color: #999999;
452+}
453+
454+QPushButton:pressed[secondary="true"] {
455+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
456+ stop: 0 #d9d9d9,stop: 1.0 #fefefe);
457+ color: #333333;
458+ border-color: #999999;
459 }
460
461 QPushButton[enabled="false"] {
462 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
463 stop: 0 #eaeaea, stop: 1.0 #cacaca);
464- border-radius: 5px;
465- border-style: solid;
466- padding: 6px;
467 color: #595959;
468 border-color: #939389;
469- border-width: 1px;
470- height: 12px;
471 }
472
473 QPushButton#help_button {
474 background: transparent;
475 border: none;
476 color: white;
477- height: 20px;
478 text-decoration: underline;
479 padding: 0px;
480 }
481@@ -103,10 +128,13 @@
482 border: none;
483 background: none;
484 color: #595959;
485+ padding-left: 10px;
486+ padding-right: 10px;
487 }
488
489-QPushButton#add_folder_button {
490- padding: 5px;
491+QPushButton#twitter_button,
492+QPushButton#facebook_button {
493+ border: none;
494 }
495
496 GoToWebButton#share_publish_button {
497@@ -158,9 +186,7 @@
498 }
499
500 QTabBar::tab:hover {
501- background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
502- stop: 0 #fafafa, stop: 0.4 #f4f4f4,
503- stop: 0.5 #e7e7e7, stop: 1.0 #fafafa);
504+ background: #f6f6f6;
505 text-decoration: underline;
506 }
507
508@@ -223,20 +249,11 @@
509 color: white;
510 }
511
512-QFrame#frm_box { /* the loadingoverlay frame */
513- background: #ffffff;
514- border-radius: 5px;
515- border-style: solid;
516- border-color: #939389;
517- border-width: 1px;
518- color: white;
519- min-height: 100px;
520-}
521-
522 QAbstractItemView {
523 border-style: solid;
524- border-color: #333333;
525- border-width: 1px;
526- alternate-background-color: #efedec;
527- background: #f7f6f5;
528+ border-color: #898989;
529+ border-top-width: 1px;
530+ border-bottom-width: 1px;
531+ alternate-background-color: #f7f6f5;
532+ background: #efedec;
533 }
534
535=== modified file 'data/twitter.png'
536Binary files data/twitter.png 2011-01-25 19:08:59 +0000 and data/twitter.png 2011-08-25 19:40:18 +0000 differ
537=== modified file 'debian/changelog'
538--- debian/changelog 2011-08-12 19:13:51 +0000
539+++ debian/changelog 2011-08-25 19:40:18 +0000
540@@ -1,3 +1,10 @@
541+ubuntuone-control-panel (1.1.3-0ubuntu1) oneiric; urgency=low
542+
543+ * New upstream release.
544+ - Work correctly with static and GI bindings of gobject (LP: #829186)
545+
546+ -- Rodney Dawes <rodney.dawes@ubuntu.com> Thu, 25 Aug 2011 15:37:15 -0400
547+
548 ubuntuone-control-panel (1.1.2-0ubuntu1) oneiric; urgency=low
549
550 * New upstream release.
551
552=== modified file 'po/ubuntuone-control-panel.pot'
553--- po/ubuntuone-control-panel.pot 2011-08-12 19:12:08 +0000
554+++ po/ubuntuone-control-panel.pot 2011-08-25 19:40:18 +0000
555@@ -8,7 +8,7 @@
556 msgstr ""
557 "Project-Id-Version: PACKAGE VERSION\n"
558 "Report-Msgid-Bugs-To: \n"
559-"POT-Creation-Date: 2011-08-12 15:07-0400\n"
560+"POT-Creation-Date: 2011-08-25 15:26-0400\n"
561 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
562 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
563 "Language-Team: LANGUAGE <LL@li.org>\n"
564
565=== modified file 'run-tests'
566--- run-tests 2011-07-22 21:26:48 +0000
567+++ run-tests 2011-08-25 19:40:18 +0000
568@@ -37,14 +37,16 @@
569 fi
570
571 style_check() {
572- u1lint --ignore ubuntuone/controlpanel/gui/qt/ui ubuntuone/
573+ u1lint --ignore ubuntuone/controlpanel/gui/qt/ui
574 if [ -x `which pep8` ]; then
575- pep8 --exclude '.svn,CVS,.bzr,.hg,.git,*_ui.py,*_rc.py' --repeat bin/ $MODULE
576+ pep8 --exclude '.svn,CVS,.bzr,.hg,.git,*_ui.py,*_rc.py' --repeat .
577 else
578 echo "Please install the 'pep8' package."
579 fi
580 }
581
582+unset GTK_MODULES
583+
584 ./setup.py build
585 echo "Running test suite for ""$MODULE"
586 if [ "$USE_QT" -eq 0 ]; then
587
588=== modified file 'run-tests.bat'
589--- run-tests.bat 2011-07-22 21:26:48 +0000
590+++ run-tests.bat 2011-08-25 19:40:18 +0000
591@@ -30,7 +30,7 @@
592 ECHO Cleaning the generated code before running the style checks...
593 "%PYTHONEXEPATH%\python.exe" setup.py clean
594 ECHO Performing style checks...
595-"%PYTHONEXEPATH%\Scripts\u1lint.exe" --ignore ubuntuone\controlpanel\gui\qt\ui "%MODULE%"
596+"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1lint" --ignore ubuntuone\controlpanel\gui\qt\ui "%MODULE%"
597 "%PYTHONEXEPATH%\Scripts\pep8.exe" --exclude ".svn,CVS,.bzr,.hg,.git,*_ui.py,*_rc.py" --repeat .
598 :: Delete the temp folders
599 RMDIR /s /q _trial_temp
600
601=== modified file 'setup.py'
602--- setup.py 2011-08-12 19:12:08 +0000
603+++ setup.py 2011-08-25 19:40:18 +0000
604@@ -46,6 +46,7 @@
605 CLEANFILES = [
606 SERVICE_FILE, GUI_SERVICE_FILE, MESSAGE_ENTRY, CONSTANTS, POT_FILE,
607 'MANIFEST']
608+QT_UI_DIR = os.path.join('ubuntuone', 'controlpanel', 'gui', 'qt', 'ui')
609
610
611 def replace_prefix(prefix):
612@@ -64,7 +65,7 @@
613 def run(self):
614 """Do the install.
615
616- Read from *.service.in and generate .service files with reeplacing
617+ Read from *.service.in and generate .service files by replacing
618 @prefix@ by self.prefix.
619
620 """
621@@ -75,7 +76,6 @@
622 class ControlPanelBuild(build_extra.build_extra):
623 """Build PyQt (.ui) files and resources."""
624
625- QT_UI_DIR = os.path.join('ubuntuone', 'controlpanel', 'gui', 'qt', 'ui')
626 description = "build PyQt GUIs (.ui) and resources (.qrc)"
627
628 def compile_ui(self, ui_file, py_file=None):
629@@ -86,7 +86,7 @@
630 # python file in the qt moodule
631 py_file = os.path.split(ui_file)[1]
632 py_file = os.path.splitext(py_file)[0] + '_ui.py'
633- py_file = os.path.join(self.QT_UI_DIR, py_file)
634+ py_file = os.path.join(QT_UI_DIR, py_file)
635 # we indeed want to catch Exception, is ugly but we need it
636 # pylint: disable=W0703
637 try:
638@@ -112,7 +112,7 @@
639 if py_file is None:
640 py_file = os.path.split(qrc_file)[1]
641 py_file = os.path.splitext(py_file)[0] + '_rc.py'
642- py_file = os.path.join(self.QT_UI_DIR, py_file)
643+ py_file = os.path.join(QT_UI_DIR, py_file)
644 path = os.getenv('PATH')
645 os.putenv('PATH', path + os.path.pathsep + os.path.join(
646 os.path.dirname(PyQt4.__file__), 'bin'))
647@@ -218,18 +218,24 @@
648 if os.path.exists(built_file):
649 os.unlink(built_file)
650
651+ for dirpath, _, filenames in os.walk(os.path.join(QT_UI_DIR)):
652+ for current_file in filenames:
653+ if current_file.endswith('_ui.py') or\
654+ current_file.endswith('_rc.py'):
655+ os.unlink(os.path.join(dirpath, current_file))
656+
657 DistUtilsExtra.auto.clean_build_tree.run(self)
658
659
660 DistUtilsExtra.auto.setup(
661 name='ubuntuone-control-panel',
662- version='1.1.2',
663+ version='1.1.3',
664 license='GPL v3',
665 author='Natalia Bidart',
666 author_email='natalia.bidart@canonical.com',
667 description='Ubuntu One Control Panel',
668- long_description='Application to manage a Ubuntu One account. Provides a'\
669- 'DBus service to query/modify all the Ubuntu One bits.',
670+ long_description='Application to manage a Ubuntu One account. Provides' \
671+ ' a DBus service to query/modify all the Ubuntu One bits.',
672 url='https://launchpad.net/ubuntuone-control-panel',
673 packages=[
674 'ubuntuone', 'ubuntuone.controlpanel', 'ubuntuone.controlpanel.gui',
675
676=== modified file 'ubuntuone/controlpanel/backend.py'
677--- ubuntuone/controlpanel/backend.py 2011-08-12 19:12:08 +0000
678+++ ubuntuone/controlpanel/backend.py 2011-08-25 19:40:18 +0000
679@@ -27,11 +27,12 @@
680
681 from twisted.internet.defer import inlineCallbacks, returnValue
682 # No name 'is_link' in module 'ubuntuone.platform'
683-# pylint: disable=E0611
684+# pylint: disable=E0611, F0401
685 from ubuntuone.platform import is_link
686-# pylint: enable=E0611
687+from ubuntuone.platform.credentials import CredentialsManagementTool
688+# pylint: enable=E0611, F0401
689
690-from ubuntuone.controlpanel import login_client, sd_client, replication_client
691+from ubuntuone.controlpanel import sd_client, replication_client
692 from ubuntuone.controlpanel.logger import setup_logging, log_call
693 # pylint: disable=W0611
694 from ubuntuone.controlpanel.web_client import (UnauthorizedError,
695@@ -92,13 +93,13 @@
696
697 @inlineCallbacks
698 @wraps(f)
699- def inner(*args, **kwargs):
700+ def inner(instance, *args, **kwargs):
701 """Handle UnauthorizedError and clear credentials."""
702 try:
703- result = yield f(*args, **kwargs)
704+ result = yield f(instance, *args, **kwargs)
705 except UnauthorizedError, e:
706 logger.exception('process_unauthorized (clearing credentials):')
707- yield login_client.clear_credentials()
708+ yield instance.login_client.clear_credentials()
709 raise e
710
711 returnValue(result)
712@@ -126,13 +127,17 @@
713
714 def __init__(self, shutdown_func=None):
715 """Initialize the web_client."""
716- self.shutdown_func = shutdown_func
717- self.wc = web_client_factory(login_client.get_credentials)
718 self._status_changed_handler = None
719-
720+ self._credentials = None
721 self._volumes = {} # cache last known volume info
722+
723+ self.shutdown_func = shutdown_func
724 self.file_sync_disabled = False
725
726+ self.login_client = CredentialsManagementTool()
727+ self.sd_client = sd_client.SyncDaemonClient()
728+ self.wc = web_client_factory(self.get_credentials)
729+
730 def _process_file_sync_status(self, status):
731 """Process raw file sync status into custom format.
732
733@@ -194,11 +199,7 @@
734 handler(result)
735
736 self._status_changed_handler = handler
737-
738- # XXX: since windows version of sd_client still don't have a way of
739- # setting a status changed handler, we'll be robust by doing this check
740- if getattr(sd_client, 'set_status_changed_handler', None) is not None:
741- sd_client.set_status_changed_handler(process_and_callback)
742+ self.sd_client.set_status_changed_handler(process_and_callback)
743
744 def _get_status_changed_handler(self):
745 """Return the handler to be called when file sync status changes."""
746@@ -263,7 +264,7 @@
747 If all the file sync settings are None, do not attach that info.
748
749 """
750- credentials = yield login_client.get_credentials()
751+ credentials = yield self.get_credentials()
752
753 local_device = {}
754 local_device["type"] = DEVICE_TYPE_COMPUTER
755@@ -294,9 +295,16 @@
756 return result
757
758 @inlineCallbacks
759+ def get_credentials(self):
760+ """Find credentials."""
761+ if self._credentials is None:
762+ self._credentials = yield self.login_client.find_credentials()
763+ returnValue(self._credentials)
764+
765+ @inlineCallbacks
766 def get_token(self):
767 """Return the token from the credentials."""
768- credentials = yield login_client.get_credentials()
769+ credentials = yield self.get_credentials()
770 returnValue(credentials["token"])
771
772 @inlineCallbacks
773@@ -344,25 +352,25 @@
774 autoconnect = show_notifs = None
775 share_autosubscribe = udf_autosubscribe = None
776
777- enabled = yield sd_client.files_sync_enabled()
778+ enabled = yield self.sd_client.files_sync_enabled()
779 enabled = bool(enabled)
780 if enabled:
781- sd_res = yield sd_client.autoconnect_enabled()
782+ sd_res = yield self.sd_client.autoconnect_enabled()
783 autoconnect = bool(sd_res)
784
785- sd_res = yield sd_client.show_all_notifications_enabled()
786+ sd_res = yield self.sd_client.show_all_notifications_enabled()
787 show_notifs = bool(sd_res)
788
789- sd_res = yield sd_client.share_autosubscribe_enabled()
790+ sd_res = yield self.sd_client.share_autosubscribe_enabled()
791 share_autosubscribe = bool(sd_res)
792
793- sd_res = yield sd_client.udf_autosubscribe_enabled()
794+ sd_res = yield self.sd_client.udf_autosubscribe_enabled()
795 udf_autosubscribe = bool(sd_res)
796
797- sd_res = yield sd_client.bandwidth_throttling_enabled()
798+ sd_res = yield self.sd_client.bandwidth_throttling_enabled()
799 limit_bw = bool(sd_res)
800
801- limits = yield sd_client.get_throttling_limits()
802+ limits = yield self.sd_client.get_throttling_limits()
803
804 logger.debug('devices_info: file sync enabled? %s limit_bw %s, limits '
805 '%s, autoconnect %s, show_notifs %s, '
806@@ -432,19 +440,19 @@
807
808 if is_local and SHOW_ALL_NOTIFICATIONS_KEY in settings:
809 if not settings[SHOW_ALL_NOTIFICATIONS_KEY]:
810- yield sd_client.disable_show_all_notifications()
811+ yield self.sd_client.disable_show_all_notifications()
812 else:
813- yield sd_client.enable_show_all_notifications()
814+ yield self.sd_client.enable_show_all_notifications()
815
816 if is_local and LIMIT_BW_KEY in settings:
817 if not settings[LIMIT_BW_KEY]:
818- yield sd_client.disable_bandwidth_throttling()
819+ yield self.sd_client.disable_bandwidth_throttling()
820 else:
821- yield sd_client.enable_bandwidth_throttling()
822+ yield self.sd_client.enable_bandwidth_throttling()
823
824 if is_local and (UPLOAD_KEY in settings or
825 DOWNLOAD_KEY in settings):
826- current_limits = yield sd_client.get_throttling_limits()
827+ current_limits = yield self.sd_client.get_throttling_limits()
828 limits = {
829 "download": current_limits["download"],
830 "upload": current_limits["upload"],
831@@ -453,7 +461,7 @@
832 limits["upload"] = settings[UPLOAD_KEY]
833 if DOWNLOAD_KEY in settings:
834 limits["download"] = settings[DOWNLOAD_KEY]
835- sd_client.set_throttling_limits(limits)
836+ self.sd_client.set_throttling_limits(limits)
837
838 # still pending: more work on the settings dict (LP: #673674)
839 returnValue(device_id)
840@@ -472,7 +480,7 @@
841 if is_local:
842 logger.warning('remove_device: device is local! removing and '
843 'clearing credentials.')
844- yield login_client.clear_credentials()
845+ yield self.login_client.clear_credentials()
846
847 returnValue(device_id)
848
849@@ -480,9 +488,9 @@
850 @inlineCallbacks
851 def file_sync_status(self):
852 """Return the status of the file sync service."""
853- enabled = yield sd_client.files_sync_enabled()
854+ enabled = yield self.sd_client.files_sync_enabled()
855 if enabled:
856- status = yield sd_client.get_current_status()
857+ status = yield self.sd_client.get_current_status()
858 else:
859 status = {}
860 returnValue(self._process_file_sync_status(status))
861@@ -491,46 +499,46 @@
862 @inlineCallbacks
863 def enable_files(self):
864 """Enable the files service."""
865- yield sd_client.set_files_sync_enabled(True)
866+ yield self.sd_client.set_files_sync_enabled(True)
867 self.file_sync_disabled = False
868
869 @log_call(logger.debug)
870 @inlineCallbacks
871 def disable_files(self):
872 """Enable the files service."""
873- yield sd_client.set_files_sync_enabled(False)
874+ yield self.sd_client.set_files_sync_enabled(False)
875 self.file_sync_disabled = True
876
877 @log_call(logger.debug)
878 @inlineCallbacks
879 def connect_files(self):
880 """Connect the files service."""
881- yield sd_client.connect_file_sync()
882+ yield self.sd_client.connect_file_sync()
883
884 @log_call(logger.debug)
885 @inlineCallbacks
886 def disconnect_files(self):
887 """Disconnect the files service."""
888- yield sd_client.disconnect_file_sync()
889+ yield self.sd_client.disconnect_file_sync()
890
891 @log_call(logger.debug)
892 @inlineCallbacks
893 def restart_files(self):
894 """restart the files service."""
895- yield sd_client.stop_file_sync()
896- yield sd_client.start_file_sync()
897+ yield self.sd_client.stop_file_sync()
898+ yield self.sd_client.start_file_sync()
899
900 @log_call(logger.debug)
901 @inlineCallbacks
902 def start_files(self):
903 """start the files service."""
904- yield sd_client.start_file_sync()
905+ yield self.sd_client.start_file_sync()
906
907 @log_call(logger.debug)
908 @inlineCallbacks
909 def stop_files(self):
910 """stop the files service."""
911- yield sd_client.stop_file_sync()
912+ yield self.sd_client.stop_file_sync()
913
914 @log_call(logger.debug)
915 @inlineCallbacks
916@@ -547,11 +555,11 @@
917 else:
918 free_bytes = account['quota_total'] - account['quota_used']
919
920- root_dir = yield sd_client.get_root_dir()
921- shares_dir = yield sd_client.get_shares_dir()
922- shares_dir_link = yield sd_client.get_shares_dir_link()
923- folders = yield sd_client.get_folders()
924- shares = yield sd_client.get_shares()
925+ root_dir = yield self.sd_client.get_root_dir()
926+ shares_dir = yield self.sd_client.get_shares_dir()
927+ shares_dir_link = yield self.sd_client.get_shares_dir_link()
928+ folders = yield self.sd_client.get_folders()
929+ shares = yield self.sd_client.get_shares()
930
931 root_volume = {u'volume_id': u'', u'path': root_dir,
932 u'subscribed': True, u'type': self.ROOT_TYPE,
933@@ -630,23 +638,23 @@
934 def subscribe_volume(self, volume_id):
935 """Subscribe to 'volume_id'."""
936 if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
937- yield sd_client.subscribe_folder(volume_id)
938+ yield self.sd_client.subscribe_folder(volume_id)
939 elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
940- yield sd_client.subscribe_share(volume_id)
941+ yield self.sd_client.subscribe_share(volume_id)
942
943 @inlineCallbacks
944 def unsubscribe_volume(self, volume_id):
945 """Unsubscribe from 'volume_id'."""
946 if self._volumes[volume_id][u'type'] == self.FOLDER_TYPE:
947- yield sd_client.unsubscribe_folder(volume_id)
948+ yield self.sd_client.unsubscribe_folder(volume_id)
949 elif self._volumes[volume_id][u'type'] == self.SHARE_TYPE:
950- yield sd_client.unsubscribe_share(volume_id)
951+ yield self.sd_client.unsubscribe_share(volume_id)
952
953 @log_call(logger.debug)
954 @inlineCallbacks
955 def create_folder(self, folder_path):
956 """Create a new User Defined Folder pointing to 'folder_path'."""
957- yield sd_client.create_folder(path=folder_path)
958+ yield self.sd_client.create_folder(path=folder_path)
959
960 @log_call(logger.debug)
961 @inlineCallbacks
962@@ -728,11 +736,11 @@
963
964 for name in (AUTOCONNECT_KEY, SHOW_ALL_NOTIFICATIONS_KEY,
965 SHARE_AUTOSUBSCRIBE_KEY, UDF_AUTOSUBSCRIBE_KEY):
966- sd_method = getattr(sd_client, '%s_enabled' % name)
967+ sd_method = getattr(self.sd_client, '%s_enabled' % name)
968 value = yield sd_method()
969 result[name] = bool(value)
970
971- limits = yield sd_client.get_throttling_limits()
972+ limits = yield self.sd_client.get_throttling_limits()
973 result[DOWNLOAD_KEY] = limits['download']
974 result[UPLOAD_KEY] = limits['upload']
975
976@@ -744,7 +752,7 @@
977 if setting_name in settings:
978 new_value = settings[setting_name]
979 sd_method_name = 'enable_%s' if new_value else 'disable_%s'
980- sd_method = getattr(sd_client, sd_method_name % setting_name)
981+ sd_method = getattr(self.sd_client, sd_method_name % setting_name)
982 yield sd_method()
983
984 @log_call(logger.info)
985@@ -756,7 +764,7 @@
986 yield self._change_boolean_file_sync_setting(name, settings)
987
988 if DOWNLOAD_KEY in settings or UPLOAD_KEY in settings:
989- current_limits = yield sd_client.get_throttling_limits()
990+ current_limits = yield self.sd_client.get_throttling_limits()
991 limits = {
992 "download": current_limits["download"],
993 "upload": current_limits["upload"],
994@@ -765,13 +773,13 @@
995 limits["upload"] = settings[UPLOAD_KEY]
996 if DOWNLOAD_KEY in settings:
997 limits["download"] = settings[DOWNLOAD_KEY]
998- yield sd_client.set_throttling_limits(limits)
999+ yield self.sd_client.set_throttling_limits(limits)
1000
1001 throttling_disabled = sum(limits.itervalues()) == -2
1002 if throttling_disabled:
1003- yield sd_client.disable_bandwidth_throttling()
1004+ yield self.sd_client.disable_bandwidth_throttling()
1005 else:
1006- yield sd_client.enable_bandwidth_throttling()
1007+ yield self.sd_client.enable_bandwidth_throttling()
1008
1009 @log_call(logger.info)
1010 @inlineCallbacks
1011
1012=== modified file 'ubuntuone/controlpanel/dbus_service.py'
1013--- ubuntuone/controlpanel/dbus_service.py 2011-07-22 21:26:48 +0000
1014+++ ubuntuone/controlpanel/dbus_service.py 2011-08-25 19:40:18 +0000
1015@@ -20,9 +20,17 @@
1016 """Export the control backend thru DBus."""
1017
1018 from functools import wraps
1019+import sys
1020
1021 import dbus.service
1022-import gobject
1023+# pylint: disable=E0611
1024+# pylint: disable=W0404
1025+if 'gobject' in sys.modules:
1026+ import gobject as GObject
1027+else:
1028+ from gi.repository import GObject
1029+# pylint: enable=W0404
1030+# pylint: enable=E0611
1031
1032 from dbus.mainloop.glib import DBusGMainLoop
1033 from dbus.service import method, signal
1034@@ -608,9 +616,9 @@
1035
1036
1037 def run_mainloop(loop=None):
1038- """Run the gobject main loop."""
1039+ """Run the GObject main loop."""
1040 if loop is None:
1041- loop = gobject.MainLoop()
1042+ loop = GObject.MainLoop()
1043 loop.run()
1044
1045
1046@@ -643,7 +651,7 @@
1047 """Hook the DBus listeners and start the main loop."""
1048 init_mainloop()
1049 if register_service():
1050- loop = gobject.MainLoop()
1051+ loop = GObject.MainLoop()
1052 publish_backend(shutdown_func=loop.quit)
1053 run_mainloop(loop=loop)
1054 else:
1055
1056=== modified file 'ubuntuone/controlpanel/dbustests/test_dbus_service.py'
1057--- ubuntuone/controlpanel/dbustests/test_dbus_service.py 2011-07-22 21:26:48 +0000
1058+++ ubuntuone/controlpanel/dbustests/test_dbus_service.py 2011-08-25 19:40:18 +0000
1059@@ -107,7 +107,7 @@
1060 rs()
1061 self.mocker.result(True)
1062
1063- mainloop = "ubuntuone.controlpanel.dbus_service.gobject.MainLoop"
1064+ mainloop = "ubuntuone.controlpanel.dbus_service.GObject.MainLoop"
1065 mainloop = self.mocker.replace(mainloop)
1066 mainloop()
1067 loop = self.mocker.mock()
1068
1069=== modified file 'ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py'
1070--- ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py 2011-07-22 21:26:48 +0000
1071+++ ubuntuone/controlpanel/dbustests/test_sd_client/test_linux.py 2011-08-25 19:40:18 +0000
1072@@ -82,7 +82,8 @@
1073
1074 def test_set_status_changed_handler(self):
1075 """A proper callback can be connected to StatusChanged signal."""
1076- _, sig = sd_client.set_status_changed_handler(self._set_called)
1077+ client = sd_client.SyncDaemonClient()
1078+ _, sig = client.set_status_changed_handler(self._set_called)
1079
1080 self.assertEqual(sig._handler, self._set_called)
1081 self.assertEqual(sig._member, 'StatusChanged')
1082
1083=== modified file 'ubuntuone/controlpanel/gui/gtk/gui.py'
1084--- ubuntuone/controlpanel/gui/gtk/gui.py 2011-07-22 21:26:48 +0000
1085+++ ubuntuone/controlpanel/gui/gtk/gui.py 2011-08-25 19:40:18 +0000
1086@@ -28,17 +28,14 @@
1087 import dbus
1088 import gtk
1089 import gobject
1090-import ubuntu_sso
1091
1092 from dbus.mainloop.glib import DBusGMainLoop
1093 from ubuntu_sso import networkstate
1094-from ubuntu_sso.credentials import (TC_URL_KEY, HELP_TEXT_KEY, WINDOW_ID_KEY,
1095- PING_URL_KEY)
1096-# No name 'clientdefs' in module 'ubuntuone'
1097 # pylint: disable=E0611,F0401
1098-from gi.repository import GLib
1099-from ubuntuone.clientdefs import (APP_NAME as U1_APP_NAME, TC_URL as U1_TC_URL,
1100- PING_URL as U1_PING_URL, DESCRIPTION as U1_DESCRIPTION)
1101+from ubuntuone.platform.credentials import (
1102+ APP_NAME as U1_APP_NAME,
1103+ CredentialsManagementTool,
1104+)
1105 # pylint: enable=E0611,F0401
1106
1107 # Wildcard import ubuntuone.controlpanel.gui
1108@@ -123,21 +120,6 @@
1109 gtk.main()
1110
1111
1112-def filter_by_app_name(f):
1113- """Excecute 'f' filtering by app_name."""
1114-
1115- @wraps(f)
1116- def filter_by_app_name_inner(instance, app_name, *args, **kwargs):
1117- """Execute 'f' only if 'app_name' matches 'U1_APP_NAME'."""
1118- if app_name == U1_APP_NAME:
1119- return f(instance, app_name, *args, **kwargs)
1120- else:
1121- logger.info('%s: ignoring call since received app_name '\
1122- '"%s" (expected "%s")',
1123- f.__name__, app_name, U1_APP_NAME)
1124- return filter_by_app_name_inner
1125-
1126-
1127 def on_size_allocate(widget, allocation, label):
1128 """Resize labels according to who 'widget' is being resized."""
1129 label.set_size_request(allocation.width - 2, -1)
1130@@ -188,10 +170,6 @@
1131
1132 logger.debug('%s: started.', self.__class__.__name__)
1133
1134- def humanize(self, int_bytes):
1135- """Return a human readble string of 'int_bytes'."""
1136- return GLib.format_size_for_display(int_bytes)
1137-
1138 def _set_warning(self, message, label):
1139 """Set 'message' as warning in 'label'."""
1140 label.set_markup(WARNING_MARKUP % message)
1141@@ -272,23 +250,9 @@
1142
1143 def __init__(self, main_window):
1144 GreyableBin.__init__(self)
1145-
1146- sso_backend = None
1147- bus = dbus.SessionBus()
1148- try:
1149- obj = bus.get_object(ubuntu_sso.DBUS_BUS_NAME,
1150- ubuntu_sso.DBUS_CREDENTIALS_PATH,
1151- follow_name_owner_changes=True)
1152- iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE
1153- sso_backend = dbus.Interface(obj, dbus_interface=iface)
1154- except dbus.exceptions.DBusException:
1155- logger.exception('Can not connect to DBus at %r',
1156- (ubuntu_sso.DBUS_BUS_NAME,
1157- ubuntu_sso.DBUS_CREDENTIALS_PATH))
1158- raise
1159-
1160+ creds_backend = CredentialsManagementTool()
1161 ControlPanelMixin.__init__(self, filename='overview.ui',
1162- backend_instance=sso_backend)
1163+ backend_instance=creds_backend)
1164 self.add(self.itself)
1165 self.banner.set_from_file(get_data_file(OVERVIEW_BANNER))
1166 self.files_icon.set_from_file(get_data_file(FILES_ICON))
1167@@ -305,15 +269,6 @@
1168 self._credentials_are_new = False
1169 self.show()
1170
1171- self.backend.connect_to_signal('CredentialsFound',
1172- self.on_credentials_found)
1173- self.backend.connect_to_signal('CredentialsNotFound',
1174- self.on_credentials_not_found)
1175- self.backend.connect_to_signal('CredentialsError',
1176- self.on_credentials_error)
1177- self.backend.connect_to_signal('AuthorizationDenied',
1178- self.on_authorization_denied)
1179-
1180 kw = dict(result_cb=self.on_network_state_changed)
1181 self.network_manager_state = networkstate.NetworkManagerState(**kw)
1182 self.network_manager_state.find_online_state()
1183@@ -323,6 +278,14 @@
1184 ControlPanelMixin._set_warning(self, message,
1185 label=self.warning_label)
1186
1187+ def _window_xid(self):
1188+ """Return settings for credentials backend."""
1189+ if self.main_window.window is not None:
1190+ settings = {'window_id': str(self.main_window.window.xid)}
1191+ else:
1192+ settings = {}
1193+ return settings
1194+
1195 def set_property(self, prop_name, new_value):
1196 """Override 'set_property' to disable buttons if prop is 'greyed'."""
1197 if prop_name == 'greyed':
1198@@ -342,21 +305,17 @@
1199
1200 def on_join_now_button_clicked(self, *a, **kw):
1201 """User wants to join now."""
1202- settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
1203- WINDOW_ID_KEY: str(self.main_window.window.xid),
1204- PING_URL_KEY: U1_PING_URL}
1205- self.backend.register(U1_APP_NAME, settings,
1206- reply_handler=NO_OP, error_handler=error_handler)
1207+ d = self.backend.register(**self._window_xid())
1208+ d.addCallback(self.on_credentials_result)
1209+ d.addErrback(self.on_credentials_error)
1210 self.set_property('greyed', True)
1211 self.warning_label.set_text('')
1212
1213 def on_connect_button_clicked(self, *a, **kw):
1214 """User wants to connect now."""
1215- settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
1216- WINDOW_ID_KEY: str(self.main_window.window.xid),
1217- PING_URL_KEY: U1_PING_URL}
1218- self.backend.login(U1_APP_NAME, settings,
1219- reply_handler=NO_OP, error_handler=error_handler)
1220+ d = self.backend.login(**self._window_xid())
1221+ d.addCallback(self.on_credentials_result)
1222+ d.addErrback(self.on_credentials_error)
1223 self.set_property('greyed', True)
1224 self.warning_label.set_text('')
1225
1226@@ -364,31 +323,42 @@
1227 """User wants to learn more."""
1228 uri_hook(self.learn_more_button, LEARN_MORE_LINK)
1229
1230- @filter_by_app_name
1231+ def on_credentials_result(self, result):
1232+ """Process the credentials response.
1233+
1234+ If 'result' is a non empty dict, they were found.
1235+ If 'result' is an empty dict, they were not found.
1236+ If 'result' is None, the user cancelled the process.
1237+
1238+ """
1239+ if result is None:
1240+ self.on_authorization_denied()
1241+ elif result == {}:
1242+ self.on_credentials_not_found()
1243+ else:
1244+ self.on_credentials_found(result)
1245+
1246 @log_call(logger.info, with_args=False)
1247- def on_credentials_found(self, app_name, credentials):
1248- """SSO backend notifies of credentials found."""
1249+ def on_credentials_found(self, credentials):
1250+ """Credentials backend notifies of credentials found."""
1251 self.set_property('greyed', False)
1252 self.emit('credentials-found', self._credentials_are_new, credentials)
1253
1254- @filter_by_app_name
1255 @log_call(logger.info)
1256- def on_credentials_not_found(self, app_name):
1257- """SSO backend notifies of credentials not found."""
1258+ def on_credentials_not_found(self):
1259+ """Creds backend notifies of credentials not found."""
1260 self._credentials_are_new = True
1261 self.set_property('greyed', False)
1262
1263- @filter_by_app_name
1264 @log_call(logger.error)
1265- def on_credentials_error(self, app_name, error_dict):
1266- """SSO backend notifies of an error when fetching credentials."""
1267+ def on_credentials_error(self, error_dict):
1268+ """Creds backend notifies of an error when fetching credentials."""
1269 self.set_property('greyed', False)
1270 self._set_warning(CREDENTIALS_ERROR)
1271
1272- @filter_by_app_name
1273 @log_call(logger.info)
1274- def on_authorization_denied(self, app_name):
1275- """SSO backend notifies that user refused auth for 'app_name'."""
1276+ def on_authorization_denied(self):
1277+ """Creds backend notifies that user refused auth for 'app_name'."""
1278 self.set_property('greyed', False)
1279
1280 @log_call(logger.info)
1281@@ -402,8 +372,9 @@
1282 else:
1283 self.set_sensitive(True)
1284 self.warning_label.set_text(msg)
1285- self.backend.find_credentials(U1_APP_NAME, {},
1286- reply_handler=NO_OP, error_handler=error_handler)
1287+ d = self.backend.find_credentials()
1288+ d.addCallback(self.on_credentials_result)
1289+ d.addErrback(self.on_credentials_error)
1290
1291
1292 class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
1293@@ -520,7 +491,7 @@
1294 scroll_to_cell = True
1295 else:
1296 free_bytes_str = self.FREE_SPACE
1297- free_bytes_args = {'free_space': self.humanize(free_bytes)}
1298+ free_bytes_args = {'free_space': humanize(free_bytes)}
1299 free_bytes = free_bytes_str % free_bytes_args
1300
1301 row = (self.ROW_HEADER % (name, free_bytes),
1302@@ -1542,7 +1513,7 @@
1303 """Backend notifies of account info."""
1304 used = int(info['quota_used'])
1305 total = int(info['quota_total'])
1306- data = {'used': self.humanize(used), 'total': self.humanize(total),
1307+ data = {'used': humanize(used), 'total': humanize(total),
1308 'percentage': (used / total) * 100}
1309 self._update_quota(QUOTA_LABEL % data, data)
1310
1311
1312=== modified file 'ubuntuone/controlpanel/gui/gtk/tests/__init__.py'
1313--- ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2011-07-22 21:26:48 +0000
1314+++ ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2011-08-25 19:40:18 +0000
1315@@ -22,6 +22,7 @@
1316
1317 from collections import defaultdict
1318
1319+from twisted.internet import defer
1320 from ubuntuone.devtools.handlers import MementoHandler
1321
1322 from ubuntuone.controlpanel.gui.gtk import gui
1323@@ -83,14 +84,12 @@
1324 self._signals[signal].append(handler)
1325
1326
1327-class FakedSSOBackend(FakedDBusBackend):
1328- """Fake a SSO Backend, act as a dbus.Interface."""
1329+class FakedCredentialsBackend(FakedObject):
1330+ """Fake a credentials backend."""
1331
1332- bus_name = gui.ubuntu_sso.DBUS_BUS_NAME
1333- object_path = gui.ubuntu_sso.DBUS_CREDENTIALS_PATH
1334- iface = gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE
1335 exposed_methods = ['find_credentials', 'clear_credentials',
1336 'login', 'register']
1337+ next_result = defer.succeed(None)
1338
1339
1340 class FakedControlPanelBackend(FakedDBusBackend):
1341@@ -135,8 +134,6 @@
1342 if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
1343 return FakedControlPanelBackend(obj, dbus_interface,
1344 *args, **kwargs)
1345- if dbus_interface == gui.ubuntu_sso.DBUS_CREDENTIALS_IFACE:
1346- return FakedSSOBackend(obj, dbus_interface, *args, **kwargs)
1347 if dbus_interface == gui.DBUS_IFACE_GUI:
1348 return FakeControlPanelBackend(
1349 obj, dbus_interface, *args, **kwargs)
1350@@ -188,9 +185,11 @@
1351 # pylint: disable=E1102
1352 klass = None
1353 kwargs = {}
1354+ backend_is_dbus = True
1355
1356 def setUp(self):
1357 super(BaseTestCase, self).setUp()
1358+ self.patch(gui, 'CredentialsManagementTool', FakedCredentialsBackend)
1359 self.patch(gui.os.path, 'expanduser',
1360 lambda path: path.replace('~', USER_HOME))
1361 self.patch(gui.gtk, 'main', lambda: None)
1362@@ -221,14 +220,13 @@
1363 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
1364 self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
1365
1366- def assert_backend_called(self, method_name, args, backend=None):
1367+ def assert_backend_called(self, method_name, *args, **kwargs):
1368 """Check that the control panel backend 'method_name' was called."""
1369- if backend is None:
1370- backend = self.ui.backend
1371- self.assertIn(method_name, backend._called)
1372- kwargs = {'reply_handler': gui.NO_OP,
1373- 'error_handler': gui.error_handler}
1374- self.assertEqual(backend._called[method_name], (args, kwargs))
1375+ if self.backend_is_dbus:
1376+ kwargs = {'reply_handler': gui.NO_OP,
1377+ 'error_handler': gui.error_handler}
1378+ self.assertIn(method_name, self.ui.backend._called)
1379+ self.assertEqual(self.ui.backend._called[method_name], (args, kwargs))
1380
1381 def assert_warning_correct(self, warning, text):
1382 """Check that 'warning' is visible, showing 'text'."""
1383
1384=== modified file 'ubuntuone/controlpanel/gui/gtk/tests/test_gui.py'
1385--- ubuntuone/controlpanel/gui/gtk/tests/test_gui.py 2011-07-22 21:26:48 +0000
1386+++ ubuntuone/controlpanel/gui/gtk/tests/test_gui.py 2011-08-25 19:40:18 +0000
1387@@ -42,6 +42,7 @@
1388 )
1389 from ubuntuone.controlpanel.gui.gtk.tests.test_package_manager import (
1390 SUCCESS, FAILURE)
1391+from ubuntuone.controlpanel.gui import humanize
1392
1393
1394 # Attribute 'yyy' defined outside __init__, access to a protected member
1395@@ -159,7 +160,7 @@
1396 self.ui.backend._called.pop('volumes_info', None)
1397 self.ui.load()
1398
1399- self.assert_backend_called('volumes_info', ())
1400+ self.assert_backend_called('volumes_info')
1401
1402 def test_is_processing_after_load(self):
1403 """The ui is processing when contents are load."""
1404@@ -196,7 +197,7 @@
1405 treeiter = self.ui.volumes_store.get_iter_root()
1406 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:
1407 name = "%s's" % name if name else gui.MY_FOLDERS
1408- free_bytes = self.ui.humanize(int(free_bytes))
1409+ free_bytes = humanize(int(free_bytes))
1410 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})
1411
1412 # check parent row
1413@@ -277,7 +278,7 @@
1414 treeiter = self.ui.volumes_store.get_iter_root()
1415 for name, free_bytes, volumes in FAKE_VOLUMES_NO_FREE_SPACE_INFO:
1416 name = "%s's" % name if name else gui.MY_FOLDERS
1417- free_bytes = self.ui.humanize(int(free_bytes))
1418+ free_bytes = humanize(int(free_bytes))
1419 free_bytes = self.ui.NO_FREE_SPACE % {'free_space': free_bytes}
1420
1421 # check parent row
1422@@ -440,7 +441,7 @@
1423 subscribed = gui.bool_str(not bool(volume['subscribed']))
1424 # backend was called
1425 self.assert_backend_called('change_volume_settings',
1426- (fid, {'subscribed': subscribed}))
1427+ fid, {'subscribed': subscribed})
1428 # store was updated
1429 it = self.ui.volumes_store.get_iter(path)
1430 value = self.ui.volumes_store.get_value(it, 1)
1431@@ -572,7 +573,7 @@
1432 """Changing throttling settings updates the backend properly."""
1433 expected = self.ui.__dict__
1434 self.assert_backend_called('change_device_settings',
1435- (self.ui.id, expected))
1436+ self.ui.id, expected)
1437 self.assertEqual(self.ui.warning_label.get_text(), '')
1438
1439 limit_enabled = self.ui.throttling_limits.get_sensitive()
1440@@ -806,7 +807,7 @@
1441 self.ui.is_local = False
1442 self.ui.remove.clicked()
1443
1444- self.assert_backend_called('remove_device', (self.ui.id,))
1445+ self.assert_backend_called('remove_device', self.ui.id)
1446 self.assertFalse(self.ui.get_sensitive(),
1447 'Must be disabled while removing.')
1448
1449@@ -936,7 +937,7 @@
1450 self.ui.backend._called.pop('devices_info', None)
1451 self.ui.load()
1452
1453- self.assert_backend_called('devices_info', ())
1454+ self.assert_backend_called('devices_info')
1455
1456 def test_is_processing_after_load(self):
1457 """The ui is processing when contents are load."""
1458@@ -1246,7 +1247,7 @@
1459
1460 def test_file_sync_status_is_requested(self):
1461 """The file sync status is requested to the backend."""
1462- self.assert_backend_called('file_sync_status', ())
1463+ self.assert_backend_called('file_sync_status')
1464
1465 def test_is_disabled(self):
1466 """Until file sync status is given, the widget is disabled."""
1467@@ -1273,10 +1274,10 @@
1468 assert self.ui.button.get_active()
1469
1470 self.ui.button.set_active(not self.ui.button.get_active())
1471- self.assert_backend_called('disable_files', ())
1472+ self.assert_backend_called('disable_files')
1473
1474 self.ui.button.set_active(not self.ui.button.get_active())
1475- self.assert_backend_called('enable_files', ())
1476+ self.assert_backend_called('enable_files')
1477
1478 def test_on_file_sync_enabled(self):
1479 """When file sync is enabled, the button is active."""
1480@@ -1330,7 +1331,7 @@
1481
1482 args = (self.service_id,
1483 {'enabled': gui.bool_str(self.ui.button.get_active())})
1484- self.assert_backend_called('change_replication_settings', args)
1485+ self.assert_backend_called('change_replication_settings', *args)
1486
1487 def test_dependency(self):
1488 """The dependency box is None."""
1489@@ -1543,7 +1544,7 @@
1490 self.ui.load_replications()
1491
1492 self.assertTrue(self.ui.message.active)
1493- self.assert_backend_called('replications_info', ())
1494+ self.assert_backend_called('replications_info')
1495
1496
1497 class ServicesWithDesktopcouchTestCase(ServicesTestCase):
1498@@ -1750,7 +1751,7 @@
1499 def test_file_sync_status_is_requested_on_load(self):
1500 """The file sync status is requested to the backend."""
1501 self.ui.load()
1502- self.assert_backend_called('file_sync_status', ())
1503+ self.assert_backend_called('file_sync_status')
1504
1505 def test_on_file_sync_status_disabled(self):
1506 """The file sync is disabled.
1507@@ -1862,43 +1863,43 @@
1508 self.ui.backend._called.clear()
1509 self.ui.on_files_start_error({'error_msg': 'error msg'})
1510
1511- self.assert_backend_called('file_sync_status', ())
1512+ self.assert_backend_called('file_sync_status')
1513
1514 def test_on_connect_clicked(self):
1515 """User requested connection."""
1516 self.ui.on_connect_clicked(self.ui.button)
1517
1518- self.assert_backend_called('connect_files', ())
1519+ self.assert_backend_called('connect_files')
1520
1521 def test_on_disconnect_clicked(self):
1522 """User requested disconnection."""
1523 self.ui.on_disconnect_clicked(self.ui.button)
1524
1525- self.assert_backend_called('disconnect_files', ())
1526+ self.assert_backend_called('disconnect_files')
1527
1528 def test_on_enable_clicked(self):
1529 """User requested enable the service."""
1530 self.ui.on_enable_clicked(self.ui.button)
1531
1532- self.assert_backend_called('enable_files', ())
1533+ self.assert_backend_called('enable_files')
1534
1535 def test_on_restart_clicked(self):
1536 """User requested restart the service."""
1537 self.ui.on_restart_clicked(self.ui.button)
1538
1539- self.assert_backend_called('restart_files', ())
1540+ self.assert_backend_called('restart_files')
1541
1542 def test_on_start_clicked(self):
1543 """User requested start the service."""
1544 self.ui.on_start_clicked(self.ui.button)
1545
1546- self.assert_backend_called('start_files', ())
1547+ self.assert_backend_called('start_files')
1548
1549 def test_on_stop_clicked(self):
1550 """User requested stop the service."""
1551 self.ui.on_stop_clicked(self.ui.button)
1552
1553- self.assert_backend_called('stop_files', ())
1554+ self.assert_backend_called('stop_files')
1555
1556
1557 class ManagementPanelTestCase(ControlPanelMixinTestCase):
1558@@ -1912,8 +1913,8 @@
1559 used = int(info['quota_used'])
1560 total = int(info['quota_total'])
1561 percentage = round((used / total) * 100, 2)
1562- expected = {'used': self.ui.humanize(used),
1563- 'total': self.ui.humanize(total),
1564+ expected = {'used': humanize(used),
1565+ 'total': humanize(total),
1566 'percentage': percentage}
1567 msg = gui.QUOTA_LABEL % expected
1568 self.assertEqual(self.ui.quota_label.get_text(), msg)
1569@@ -1992,7 +1993,7 @@
1570 def test_account_info_is_requested_on_load(self):
1571 """The account info is requested to the backend."""
1572 self.ui.load()
1573- self.assert_backend_called('account_info', ())
1574+ self.assert_backend_called('account_info')
1575
1576 def test_file_sync_status_info_is_requested_on_load(self):
1577 """The file sync status info is requested to the backend."""
1578
1579=== modified file 'ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py'
1580--- ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2011-07-22 21:26:48 +0000
1581+++ ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2011-08-25 19:40:18 +0000
1582@@ -20,9 +20,14 @@
1583
1584 from __future__ import division
1585
1586+from twisted.internet import defer
1587+from twisted.python.failure import Failure
1588
1589 from ubuntuone.controlpanel.gui.gtk import gui
1590-from ubuntuone.controlpanel.gui.gtk.tests import BaseTestCase, FakedSSOBackend
1591+from ubuntuone.controlpanel.gui.gtk.tests import (
1592+ BaseTestCase,
1593+ FakedCredentialsBackend,
1594+)
1595 from ubuntuone.controlpanel.tests import TOKEN
1596
1597 from ubuntuone.devtools.testcase import skipIf
1598@@ -287,7 +292,7 @@
1599 """On 'credentials-found' signal, ask syncdaemon to connect."""
1600 # credentials are new
1601 self.ui.overview.emit('credentials-found', True, object())
1602- self.assert_backend_called('connect_files', ())
1603+ self.assert_backend_called('connect_files')
1604
1605 def test_local_device_removed_shows_overview_panel(self):
1606 """On 'local-device-removed' signal, the overview panel is shown."""
1607@@ -306,7 +311,7 @@
1608 def test_backend_is_shutdown_on_close(self):
1609 """When the control panel is closed, the backend is shutdown."""
1610 self.ui.emit('destroy')
1611- self.assert_backend_called('shutdown', ())
1612+ self.assert_backend_called('shutdown')
1613
1614
1615 class UbuntuOneBinTestCase(BaseTestCase):
1616@@ -437,6 +442,11 @@
1617 klass = gui.OverviewPanel
1618 kwargs = {'main_window': gui.gtk.Window()}
1619 ui_filename = 'overview.ui'
1620+ backend_is_dbus = False
1621+
1622+ def setUp(self):
1623+ super(OverwiewPanelTestCase, self).setUp()
1624+ gui.gtk.link_button_set_uri_hook(lambda *a: None)
1625
1626 def test_is_a_greyable_bin(self):
1627 """Inherits from GreyableBin."""
1628@@ -450,20 +460,102 @@
1629 """The 'join_now' button is the default widget."""
1630 self.assertTrue(self.ui.join_now_button.get_property('can-default'))
1631
1632- def test_sso_backend(self):
1633- """Has a correct SSO backend."""
1634- self.assertIsInstance(self.ui.backend, FakedSSOBackend)
1635-
1636- def test_sso_backend_signals(self):
1637- """The proper signals are connected to the backend."""
1638- self.assertEqual(self.ui.backend._signals['CredentialsFound'],
1639- [self.ui.on_credentials_found])
1640- self.assertEqual(self.ui.backend._signals['CredentialsNotFound'],
1641- [self.ui.on_credentials_not_found])
1642- self.assertEqual(self.ui.backend._signals['CredentialsError'],
1643- [self.ui.on_credentials_error])
1644- self.assertEqual(self.ui.backend._signals['AuthorizationDenied'],
1645- [self.ui.on_authorization_denied])
1646+ def test_backend(self):
1647+ """Has a correct backend."""
1648+ self.assertIsInstance(self.ui.backend, FakedCredentialsBackend)
1649+
1650+
1651+class OverwiewPanelBackendCallbacksTestCase(OverwiewPanelTestCase):
1652+ """Proper callbacks are chained to the credentials backend methods."""
1653+
1654+ failure = Exception()
1655+
1656+ def test_find_credentials_fired_with_credentials(self):
1657+ """Test that on_credentials_found is called."""
1658+ self.ui.backend.next_result = defer.succeed(TOKEN)
1659+ self.patch(self.ui, 'on_credentials_found', self._set_called)
1660+
1661+ self.ui.on_network_state_changed(gui.networkstate.ONLINE)
1662+
1663+ self.assertEqual(self._called, ((TOKEN,), {}))
1664+
1665+ def test_find_credentials_fired_without_credentials(self):
1666+ """Test that on_credentials_not_found is called."""
1667+ self.ui.backend.next_result = defer.succeed({})
1668+ self.patch(self.ui, 'on_credentials_not_found', self._set_called)
1669+
1670+ self.ui.on_network_state_changed(gui.networkstate.ONLINE)
1671+
1672+ self.assertEqual(self._called, ((), {}))
1673+
1674+ def test_find_credentials_errback(self):
1675+ """Test that on_credentials_error is called."""
1676+ self.ui.backend.next_result = defer.fail(self.failure)
1677+ self.patch(self.ui, 'on_credentials_error', self._set_called)
1678+
1679+ self.ui.on_network_state_changed(gui.networkstate.ONLINE)
1680+
1681+ failure = self._called[0][0]
1682+ self.assertIsInstance(failure, Failure)
1683+ self.assertEqual(failure.value, self.failure)
1684+
1685+ def test_register_fired_with_credentials(self):
1686+ """Test that on_credentials_found is called."""
1687+ self.ui.backend.next_result = defer.succeed(TOKEN)
1688+ self.patch(self.ui, 'on_credentials_found', self._set_called)
1689+
1690+ self.ui.join_now_button.clicked()
1691+
1692+ self.assertEqual(self._called, ((TOKEN,), {}))
1693+
1694+ def test_register_fired_with_credentials_none(self):
1695+ """Test that on_authorization_denied is called."""
1696+ self.ui.backend.next_result = defer.succeed(None)
1697+ self.patch(self.ui, 'on_authorization_denied', self._set_called)
1698+
1699+ self.ui.join_now_button.clicked()
1700+
1701+ self.assertEqual(self._called, ((), {}))
1702+
1703+ def test_register_errback(self):
1704+ """Test that on_credentials_error is called."""
1705+ self.ui.backend.next_result = defer.fail(self.failure)
1706+ self.patch(self.ui, 'on_credentials_error', self._set_called)
1707+
1708+ self.ui.join_now_button.clicked()
1709+
1710+ failure = self._called[0][0]
1711+ self.assertIsInstance(failure, Failure)
1712+ self.assertEqual(failure.value, self.failure)
1713+
1714+ def test_login_fired_with_credentials(self):
1715+ """Test that on_credentials_found is called."""
1716+ self.ui.backend.next_result = defer.succeed(TOKEN)
1717+ self.patch(self.ui, 'on_credentials_found', self._set_called)
1718+
1719+ self.ui.connect_button.clicked()
1720+
1721+ self.assertEqual(self._called, ((TOKEN,), {}))
1722+
1723+ def test_login_fired_with_credentials_none(self):
1724+ """Test that on_authorization_denied is called."""
1725+ self.ui.backend.next_result = defer.succeed(None)
1726+ self.patch(self.ui, 'on_authorization_denied', self._set_called)
1727+
1728+ self.ui.connect_button.clicked()
1729+
1730+ self.assertEqual(self._called, ((), {}))
1731+
1732+ def test_login_errback(self):
1733+ """Test that on_credentials_error is called."""
1734+ self.ui.backend.next_result = defer.fail(self.failure)
1735+ self.patch(self.ui, 'on_credentials_error', self._set_called)
1736+
1737+ self.ui.connect_button.clicked()
1738+
1739+ failure = self._called[0][0]
1740+ self.assertIsInstance(failure, Failure)
1741+ self.assertEqual(failure.value, self.failure)
1742
1743
1744 class OverwiewNetworkStatePanelTestCase(OverwiewPanelTestCase):
1745@@ -514,13 +606,13 @@
1746 def test_find_credentials_is_called(self):
1747 """Credentials are asked to SSO backend."""
1748 self.assertFalse(self.ui._credentials_are_new)
1749- self.assert_backend_called('find_credentials', (gui.U1_APP_NAME, {}))
1750+ self.assert_backend_called('find_credentials')
1751
1752 def test_on_credentials_found(self):
1753 """Callback 'on_credentials_found' is correct."""
1754 self.ui.connect('credentials-found', self._set_called)
1755
1756- self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN)
1757+ self.ui.on_credentials_found(TOKEN)
1758
1759 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1760 # assume credentials were in local keyring
1761@@ -531,9 +623,9 @@
1762 self.ui.connect('credentials-found', self._set_called)
1763
1764 # credentials weren't in the system
1765- self.ui.on_credentials_not_found(gui.U1_APP_NAME)
1766+ self.ui.on_credentials_not_found()
1767 # now they are!
1768- self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN)
1769+ self.ui.on_credentials_found(TOKEN)
1770
1771 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1772 # assume credentials were not in local keyring
1773@@ -541,70 +633,31 @@
1774
1775 def test_on_credentials_not_found(self):
1776 """Callback 'on_credentials_not_found' is correct."""
1777- self.ui.on_credentials_not_found(gui.U1_APP_NAME)
1778+ self.ui.on_credentials_not_found()
1779 self.assertTrue(self.ui.get_visible())
1780 self.assertTrue(self.ui._credentials_are_new)
1781
1782 def test_on_credentials_error(self):
1783 """Callback 'on_credentials_error' is correct."""
1784- self.ui.on_credentials_error(gui.U1_APP_NAME, {})
1785+ self.ui.on_credentials_error({})
1786 self.assertTrue(self.ui.get_visible())
1787 self.assert_warning_correct(self.ui.warning_label,
1788 gui.CREDENTIALS_ERROR)
1789
1790 def test_on_authorization_denied(self):
1791 """Callback 'on_authorization_denied' is correct."""
1792- self.ui.on_authorization_denied(gui.U1_APP_NAME)
1793+ self.ui.on_authorization_denied()
1794 self.assertTrue(self.ui.get_visible())
1795 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1796 self.assertEqual(self.ui.warning_label.get_text(), '')
1797
1798
1799-class OverwiewPanelAppNameMismatchTestCase(OverwiewPanelTestCase):
1800- """The test suite for the overview panel when the app_name won't match."""
1801-
1802- NOT_U1_APP = 'Not ' + gui.U1_APP_NAME
1803-
1804- def test_filter_by_app_name(self):
1805- """The filter_by_app_name decorator is correct."""
1806- f = gui.filter_by_app_name(self._set_called)
1807- f(self.ui, self.NOT_U1_APP)
1808- self.assertFalse(self._called)
1809- self.assertTrue(self.memento.check_info('ignoring', self.NOT_U1_APP))
1810-
1811- args = ('test', object())
1812- kwargs = {'really': 'AWESOME'}
1813- f(self.ui, gui.U1_APP_NAME, *args, **kwargs)
1814- self.assertEqual(self._called,
1815- ((self.ui, gui.U1_APP_NAME,) + args, kwargs))
1816-
1817- def test_on_credentials_found(self):
1818- """Callback 'on_credentials_found' is not executed."""
1819- self.assert_function_decorated(gui.filter_by_app_name,
1820- self.ui.on_credentials_found)
1821-
1822- def test_on_credentials_not_found(self):
1823- """Callback 'on_credentials_not_found' is not executed."""
1824- self.assert_function_decorated(gui.filter_by_app_name,
1825- self.ui.on_credentials_not_found)
1826-
1827- def test_on_credentials_error(self):
1828- """Callback 'on_credentials_error' is not executed."""
1829- self.assert_function_decorated(gui.filter_by_app_name,
1830- self.ui.on_credentials_error)
1831-
1832- def test_on_authorization_denied(self):
1833- """Callback 'on_authorization_denied' is not executed."""
1834- self.assert_function_decorated(gui.filter_by_app_name,
1835- self.ui.on_authorization_denied)
1836-
1837-
1838 class OverwiewPanelNoCredsTestCase(OverwiewPanelTestCase):
1839 """The test suite for the overview panel when no credentials are found."""
1840
1841 def setUp(self):
1842 super(OverwiewPanelNoCredsTestCase, self).setUp()
1843- self.ui.on_credentials_not_found(gui.U1_APP_NAME)
1844+ self.ui.on_credentials_not_found()
1845
1846 def test_startup_visibility(self):
1847 """The widget is visible at startup."""
1848@@ -631,12 +684,8 @@
1849 self.ui.join_now_button.clicked()
1850
1851 window_id = self.kwargs['main_window'].window.xid
1852- args = (gui.U1_APP_NAME,
1853- {gui.TC_URL_KEY: gui.U1_TC_URL,
1854- gui.HELP_TEXT_KEY: gui.U1_DESCRIPTION,
1855- gui.WINDOW_ID_KEY: str(window_id),
1856- gui.PING_URL_KEY: gui.U1_PING_URL})
1857- self.assert_backend_called('register', args)
1858+ kwargs = {'window_id': str(window_id)}
1859+ self.assert_backend_called('register', **kwargs)
1860
1861 def test_connect_button_clicked(self):
1862 """Test the 'join now' button callback."""
1863@@ -646,12 +695,8 @@
1864 self.ui.connect_button.clicked()
1865
1866 window_id = self.kwargs['main_window'].window.xid
1867- args = (gui.U1_APP_NAME,
1868- {gui.TC_URL_KEY: gui.U1_TC_URL,
1869- gui.HELP_TEXT_KEY: gui.U1_DESCRIPTION,
1870- gui.WINDOW_ID_KEY: str(window_id),
1871- gui.PING_URL_KEY: gui.U1_PING_URL})
1872- self.assert_backend_called('login', args)
1873+ kwargs = {'window_id': str(window_id)}
1874+ self.assert_backend_called('login', **kwargs)
1875
1876 def test_join_now_button_clicked_set_greyed(self):
1877 """Clicking on 'join_now' self is greyed."""
1878@@ -660,7 +705,7 @@
1879
1880 def test_join_now_button_clicked_removes_warning(self):
1881 """Clicking on 'join_now' the warnings are removed."""
1882- self.ui.on_authorization_denied(gui.U1_APP_NAME) # show warning
1883+ self.ui.on_authorization_denied() # show warning
1884 self.ui.join_now_button.clicked()
1885
1886 self.assertEqual(self.ui.warning_label.get_text(), '')
1887@@ -672,7 +717,7 @@
1888
1889 def test_connect_button_clicked_removes_warning(self):
1890 """Clicking on 'connect' the warnings are removed."""
1891- self.ui.on_authorization_denied(gui.U1_APP_NAME) # show warning
1892+ self.ui.on_authorization_denied() # show warning
1893 self.ui.connect_button.clicked()
1894
1895 self.assertEqual(self.ui.warning_label.get_text(), '')
1896@@ -688,21 +733,21 @@
1897 def test_on_credentials_not_found_unset_greyed(self):
1898 """Callback 'on_credentials_not_found' unsets the 'greyed' prop."""
1899 self.ui.connect_button.clicked()
1900- self.ui.on_credentials_not_found(gui.U1_APP_NAME)
1901+ self.ui.on_credentials_not_found()
1902
1903 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1904
1905 def test_on_credentials_error_unset_greyed(self):
1906 """Callback 'on_credentials_error' unsets the 'greyed' prop."""
1907 self.ui.connect_button.clicked()
1908- self.ui.on_credentials_error(gui.U1_APP_NAME, {})
1909+ self.ui.on_credentials_error({})
1910
1911 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1912
1913 def test_on_authorization_denied_unset_greyed(self):
1914 """Callback 'on_authorization_denied' unsets the 'greyed' prop."""
1915 self.ui.connect_button.clicked()
1916- self.ui.on_authorization_denied(gui.U1_APP_NAME)
1917+ self.ui.on_authorization_denied()
1918
1919 self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.')
1920
1921
1922=== modified file 'ubuntuone/controlpanel/gui/qt/filesyncstatus.py'
1923--- ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2011-07-22 21:26:48 +0000
1924+++ ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2011-08-25 19:40:18 +0000
1925@@ -165,6 +165,14 @@
1926 self.ui.sync_status_button.setText(action)
1927 self.ui.sync_status_button.setEnabled(True)
1928 self.ui.sync_status_button.show()
1929+ if status_key == backend.FILE_SYNC_DISCONNECTED:
1930+ self.ui.sync_status_button.setProperty("secondary", False)
1931+ else:
1932+ self.ui.sync_status_button.setProperty("secondary", True)
1933+ self.ui.sync_status_button.style().unpolish(
1934+ self.ui.sync_status_button)
1935+ self.ui.sync_status_button.style().polish(
1936+ self.ui.sync_status_button)
1937 else:
1938 self.ui.sync_status_button.hide()
1939
1940
1941=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py'
1942--- ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2011-07-22 21:26:48 +0000
1943+++ ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2011-08-25 19:40:18 +0000
1944@@ -65,10 +65,22 @@
1945
1946 self.assertTrue(self.ui.ui.sync_status_button.isEnabled())
1947 self.assertEqual(self.ui.ui.sync_status_button.text(), action)
1948+ if action == gui.FILE_SYNC_DISCONNECTED:
1949+ self.assertFalse(
1950+ self.ui.ui.sync_status_button.property("secondary"))
1951+ else:
1952+ self.assertTrue(
1953+ self.ui.ui.sync_status_button.property("secondary"))
1954
1955 self.ui.ui.sync_status_button.click()
1956 self.assertFalse(self.ui.ui.sync_status_button.isEnabled())
1957 self.assert_backend_called(callback)
1958+ if action == gui.FILE_SYNC_DISCONNECTED:
1959+ self.assertFalse(
1960+ self.ui.ui.sync_status_button.property("secondary"))
1961+ else:
1962+ self.assertTrue(
1963+ self.ui.ui.sync_status_button.property("secondary"))
1964
1965 if tooltip is not None:
1966 self.assertEqual(self.ui.ui.sync_status_button.toolTip(), tooltip)
1967
1968=== modified file 'ubuntuone/controlpanel/gui/qt/ui/account_ui.py'
1969--- ubuntuone/controlpanel/gui/qt/ui/account_ui.py 2011-08-12 19:12:08 +0000
1970+++ ubuntuone/controlpanel/gui/qt/ui/account_ui.py 2011-08-25 19:40:18 +0000
1971@@ -2,7 +2,7 @@
1972
1973 # Form implementation generated from reading ui file 'data/qt/account.ui'
1974 #
1975-# Created: Fri Aug 12 15:07:18 2011
1976+# Created: Thu Aug 25 15:26:50 2011
1977 # by: PyQt4 UI code generator 4.8.3
1978 #
1979 # WARNING! All changes made in this file will be lost!
1980@@ -27,7 +27,7 @@
1981 self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
1982 self.edit_profile_button = GoToWebButton(Form)
1983 self.edit_profile_button.setObjectName(_fromUtf8("edit_profile_button"))
1984- self.gridLayout_2.addWidget(self.edit_profile_button, 2, 2, 1, 1)
1985+ self.gridLayout_2.addWidget(self.edit_profile_button, 0, 2, 1, 1)
1986 self.services = QtGui.QGroupBox(Form)
1987 self.services.setObjectName(_fromUtf8("services"))
1988 self.verticalLayout_3 = QtGui.QVBoxLayout(self.services)
1989@@ -62,7 +62,7 @@
1990 self.gridLayout_2.addWidget(self.profile_info, 0, 0, 1, 1)
1991 self.edit_services_button = GoToWebButton(Form)
1992 self.edit_services_button.setObjectName(_fromUtf8("edit_services_button"))
1993- self.gridLayout_2.addWidget(self.edit_services_button, 0, 2, 1, 1)
1994+ self.gridLayout_2.addWidget(self.edit_services_button, 2, 2, 1, 1)
1995 spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
1996 self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1)
1997 spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
1998
1999=== modified file 'ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py'
2000--- ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py 2011-08-12 19:12:08 +0000
2001+++ ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py 2011-08-25 19:40:18 +0000
2002@@ -2,7 +2,7 @@
2003
2004 # Form implementation generated from reading ui file 'data/qt/controlpanel.ui'
2005 #
2006-# Created: Fri Aug 12 15:07:18 2011
2007+# Created: Thu Aug 25 15:26:50 2011
2008 # by: PyQt4 UI code generator 4.8.3
2009 #
2010 # WARNING! All changes made in this file will be lost!
2011@@ -18,7 +18,7 @@
2012 class Ui_Form(object):
2013 def setupUi(self, Form):
2014 Form.setObjectName(_fromUtf8("Form"))
2015- Form.resize(387, 203)
2016+ Form.resize(367, 142)
2017 sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
2018 sizePolicy.setHorizontalStretch(0)
2019 sizePolicy.setVerticalStretch(0)
2020@@ -133,10 +133,16 @@
2021 self.tab_widget.addTab(self.account_tab, _fromUtf8(""))
2022 self.verticalLayout.addWidget(self.tab_widget)
2023 self.frame_footer = QtGui.QFrame(Form)
2024+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
2025+ sizePolicy.setHorizontalStretch(0)
2026+ sizePolicy.setVerticalStretch(0)
2027+ sizePolicy.setHeightForWidth(self.frame_footer.sizePolicy().hasHeightForWidth())
2028+ self.frame_footer.setSizePolicy(sizePolicy)
2029+ self.frame_footer.setMaximumSize(QtCore.QSize(16777215, 30))
2030 self.frame_footer.setObjectName(_fromUtf8("frame_footer"))
2031 self.horizontalLayout = QtGui.QHBoxLayout(self.frame_footer)
2032 self.horizontalLayout.setSpacing(5)
2033- self.horizontalLayout.setContentsMargins(3, 0, 3, 3)
2034+ self.horizontalLayout.setMargin(0)
2035 self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
2036 self.help_button = GoToWebButton(self.frame_footer)
2037 self.help_button.setObjectName(_fromUtf8("help_button"))
2038@@ -149,24 +155,30 @@
2039 self.follow_us_label.setFont(font)
2040 self.follow_us_label.setObjectName(_fromUtf8("follow_us_label"))
2041 self.horizontalLayout.addWidget(self.follow_us_label)
2042- self.twitter_button = QtGui.QToolButton(self.frame_footer)
2043+ self.twitter_button = QtGui.QPushButton(self.frame_footer)
2044+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
2045+ sizePolicy.setHorizontalStretch(0)
2046+ sizePolicy.setVerticalStretch(0)
2047+ sizePolicy.setHeightForWidth(self.twitter_button.sizePolicy().hasHeightForWidth())
2048+ self.twitter_button.setSizePolicy(sizePolicy)
2049+ self.twitter_button.setMaximumSize(QtCore.QSize(16, 16))
2050 self.twitter_button.setCursor(QtCore.Qt.PointingHandCursor)
2051- self.twitter_button.setStyleSheet(_fromUtf8("border: 0;"))
2052- self.twitter_button.setText(_fromUtf8(""))
2053 icon = QtGui.QIcon()
2054 icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/twitter.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
2055 self.twitter_button.setIcon(icon)
2056- self.twitter_button.setIconSize(QtCore.QSize(22, 22))
2057 self.twitter_button.setObjectName(_fromUtf8("twitter_button"))
2058 self.horizontalLayout.addWidget(self.twitter_button)
2059- self.facebook_button = QtGui.QToolButton(self.frame_footer)
2060+ self.facebook_button = QtGui.QPushButton(self.frame_footer)
2061+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
2062+ sizePolicy.setHorizontalStretch(0)
2063+ sizePolicy.setVerticalStretch(0)
2064+ sizePolicy.setHeightForWidth(self.facebook_button.sizePolicy().hasHeightForWidth())
2065+ self.facebook_button.setSizePolicy(sizePolicy)
2066+ self.facebook_button.setMaximumSize(QtCore.QSize(16, 16))
2067 self.facebook_button.setCursor(QtCore.Qt.PointingHandCursor)
2068- self.facebook_button.setStyleSheet(_fromUtf8("border: 0;"))
2069- self.facebook_button.setText(_fromUtf8(""))
2070 icon1 = QtGui.QIcon()
2071 icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/facebook.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
2072 self.facebook_button.setIcon(icon1)
2073- self.facebook_button.setIconSize(QtCore.QSize(22, 22))
2074 self.facebook_button.setObjectName(_fromUtf8("facebook_button"))
2075 self.horizontalLayout.addWidget(self.facebook_button)
2076 self.verticalLayout.addWidget(self.frame_footer)
2077@@ -182,7 +194,7 @@
2078 self.get_more_space_button.setText(_('Get more storage'))
2079 self.tab_widget.setTabText(self.tab_widget.indexOf(self.folders_tab), _('Folders'))
2080 self.tab_widget.setTabText(self.tab_widget.indexOf(self.devices_tab), _('Devices'))
2081- self.tab_widget.setTabText(self.tab_widget.indexOf(self.preferences_tab), _('Preferences'))
2082+ self.tab_widget.setTabText(self.tab_widget.indexOf(self.preferences_tab), _('Settings'))
2083 self.tab_widget.setTabText(self.tab_widget.indexOf(self.account_tab), _('Account information'))
2084 self.help_button.setText(_('Get help online'))
2085 self.follow_us_label.setText(_('Talk to us'))
2086
2087=== modified file 'ubuntuone/controlpanel/gui/qt/ui/device_ui.py'
2088--- ubuntuone/controlpanel/gui/qt/ui/device_ui.py 2011-08-12 19:12:08 +0000
2089+++ ubuntuone/controlpanel/gui/qt/ui/device_ui.py 2011-08-25 19:40:18 +0000
2090@@ -2,7 +2,7 @@
2091
2092 # Form implementation generated from reading ui file 'data/qt/device.ui'
2093 #
2094-# Created: Fri Aug 12 15:07:18 2011
2095+# Created: Thu Aug 25 15:26:50 2011
2096 # by: PyQt4 UI code generator 4.8.3
2097 #
2098 # WARNING! All changes made in this file will be lost!
2099@@ -32,6 +32,7 @@
2100 spacerItem = QtGui.QSpacerItem(217, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
2101 self.horizontalLayout.addItem(spacerItem)
2102 self.remove_device_button = QtGui.QPushButton(Form)
2103+ self.remove_device_button.setProperty(_fromUtf8("secondary"), True)
2104 self.remove_device_button.setObjectName(_fromUtf8("remove_device_button"))
2105 self.horizontalLayout.addWidget(self.remove_device_button)
2106
2107
2108=== modified file 'ubuntuone/controlpanel/gui/qt/ui/devices_ui.py'
2109--- ubuntuone/controlpanel/gui/qt/ui/devices_ui.py 2011-08-12 19:12:08 +0000
2110+++ ubuntuone/controlpanel/gui/qt/ui/devices_ui.py 2011-08-25 19:40:18 +0000
2111@@ -2,7 +2,7 @@
2112
2113 # Form implementation generated from reading ui file 'data/qt/devices.ui'
2114 #
2115-# Created: Fri Aug 12 15:07:18 2011
2116+# Created: Thu Aug 25 15:26:50 2011
2117 # by: PyQt4 UI code generator 4.8.3
2118 #
2119 # WARNING! All changes made in this file will be lost!
2120
2121=== modified file 'ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py'
2122--- ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py 2011-08-12 19:12:08 +0000
2123+++ ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py 2011-08-25 19:40:18 +0000
2124@@ -2,7 +2,7 @@
2125
2126 # Form implementation generated from reading ui file 'data/qt/filesyncstatus.ui'
2127 #
2128-# Created: Fri Aug 12 15:07:18 2011
2129+# Created: Thu Aug 25 15:26:50 2011
2130 # by: PyQt4 UI code generator 4.8.3
2131 #
2132 # WARNING! All changes made in this file will be lost!
2133@@ -35,6 +35,7 @@
2134 self.horizontalLayout.addWidget(self.sync_status_label)
2135 self.verticalLayout.addLayout(self.horizontalLayout)
2136 self.sync_status_button = QtGui.QPushButton(Form)
2137+ self.sync_status_button.setProperty(_fromUtf8("secondary"), True)
2138 self.sync_status_button.setObjectName(_fromUtf8("sync_status_button"))
2139 self.verticalLayout.addWidget(self.sync_status_button)
2140 self.sync_status_label.setBuddy(self.sync_status_button)
2141
2142=== modified file 'ubuntuone/controlpanel/gui/qt/ui/folders_ui.py'
2143--- ubuntuone/controlpanel/gui/qt/ui/folders_ui.py 2011-08-12 19:12:08 +0000
2144+++ ubuntuone/controlpanel/gui/qt/ui/folders_ui.py 2011-08-25 19:40:18 +0000
2145@@ -2,7 +2,7 @@
2146
2147 # Form implementation generated from reading ui file 'data/qt/folders.ui'
2148 #
2149-# Created: Fri Aug 12 15:07:18 2011
2150+# Created: Thu Aug 25 15:26:50 2011
2151 # by: PyQt4 UI code generator 4.8.3
2152 #
2153 # WARNING! All changes made in this file will be lost!
2154@@ -20,7 +20,6 @@
2155 Form.setObjectName(_fromUtf8("Form"))
2156 Form.resize(345, 279)
2157 Form.setWindowTitle(_fromUtf8("Form"))
2158- Form.setStyleSheet(_fromUtf8("padding: 0px;"))
2159 self.verticalLayout = QtGui.QVBoxLayout(Form)
2160 self.verticalLayout.setSpacing(0)
2161 self.verticalLayout.setMargin(0)
2162@@ -32,7 +31,7 @@
2163 self.horizontalLayout_2.setMargin(3)
2164 self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
2165 self.share_publish_button = GoToWebButton(self.frame_top)
2166- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
2167+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
2168 sizePolicy.setHorizontalStretch(0)
2169 sizePolicy.setVerticalStretch(0)
2170 sizePolicy.setHeightForWidth(self.share_publish_button.sizePolicy().hasHeightForWidth())
2171@@ -66,7 +65,7 @@
2172 spacerItem1 = QtGui.QSpacerItem(53, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
2173 self.horizontalLayout.addItem(spacerItem1)
2174 self.add_folder_button = AddFolderButton(self.frame_bottom)
2175- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
2176+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
2177 sizePolicy.setHorizontalStretch(0)
2178 sizePolicy.setVerticalStretch(0)
2179 sizePolicy.setHeightForWidth(self.add_folder_button.sizePolicy().hasHeightForWidth())
2180
2181=== modified file 'ubuntuone/controlpanel/gui/qt/ui/images_rc.py'
2182--- ubuntuone/controlpanel/gui/qt/ui/images_rc.py 2011-08-12 19:12:08 +0000
2183+++ ubuntuone/controlpanel/gui/qt/ui/images_rc.py 2011-08-25 19:40:18 +0000
2184@@ -2,7 +2,7 @@
2185
2186 # Resource object code
2187 #
2188-# Created: Fri Aug 12 15:07:18 2011
2189+# Created: Thu Aug 25 15:26:50 2011
2190 # by: The Resource Compiler for PyQt (Qt v4.7.2)
2191 #
2192 # WARNING! All changes made in this file will be lost!
2193@@ -307,90 +307,84 @@
2194 \xf8\xb9\x66\x02\x5d\x38\x49\x38\xe7\x8e\x81\x63\xe0\x88\x7f\x33\
2195 \x0f\xac\x24\xad\x7e\x03\xdb\x11\x28\xad\xbd\x6a\x0e\xf8\x00\x00\
2196 \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
2197-\x00\x00\x05\x1d\
2198-\x89\
2199-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2200-\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
2201-\x00\x00\x04\xe4\x49\x44\x41\x54\x78\xda\xbd\x97\xd9\x4f\xdc\x55\
2202-\x14\xc7\xfd\x37\x7c\xb1\x16\xda\x29\x05\x5a\xa8\xec\x20\x52\x95\
2203-\xae\x94\x62\x23\x89\x21\x46\x4d\x7c\x33\xbe\xa8\x2f\x8d\x7d\xe9\
2204-\x43\xe3\xd2\x5a\x6b\x95\x61\x93\xd2\x05\x81\xd6\x16\x0a\x96\x75\
2205-\x98\x8d\x6d\xa6\x82\x0c\xdb\x0c\xfb\xb0\x15\x04\xa4\x44\x91\x52\
2206-\xa9\x9d\xb1\xc7\x73\x6e\x38\xbf\xfb\x63\x7e\x3f\x48\xe0\xa1\x93\
2207-\x7c\x72\x96\x7b\xee\xf9\x7e\x73\x5b\xc8\xf0\x82\xf8\xa4\x9e\x8e\
2208-\x44\x0a\x11\xfb\x73\xa2\x90\x34\xd5\xe2\xf6\xac\x32\xe7\xfc\xd9\
2209-\xae\x59\xff\x97\x9e\x05\x38\xd7\x37\xaf\x0b\x9e\xe9\xc6\x2f\xdc\
2210-\x7f\xa8\x67\x36\xbd\x77\xb6\x7b\xd6\x4f\x5a\xa4\x29\xb4\xc9\x4d\
2211-\x66\x89\x63\xee\xb4\x6b\x06\xde\x77\x3c\x80\x93\x36\x2f\xa4\x5b\
2212-\xd7\xf3\x56\xd3\x04\xa3\xf4\x32\xec\xe3\x9c\xeb\xcd\xeb\xf6\x4e\
2213-\x21\xef\xb6\x4c\xc0\x99\xae\x39\xc8\x2c\x69\x9b\x13\x2f\x41\x4e\
2214-\x3e\x75\x4e\xf8\xb3\x9a\xc6\x21\xc3\x36\x0e\x27\xcc\xa3\x1c\x35\
2215-\x04\x9c\x73\x4e\x6c\x32\xe3\xd5\x9c\x67\xb5\x4c\x02\x69\x92\xb6\
2216-\x30\xf0\xc9\xaf\x53\x70\xd2\xe2\x85\xb4\xc6\x51\x38\x6e\x1a\x16\
2217-\xa4\x63\x4d\xf1\x94\x65\x04\xd2\xee\x76\x43\x98\xd1\x02\xa1\xd9\
2218-\x66\x38\x5e\xee\x82\x4c\xdb\x18\x9f\xe3\x9d\x11\xca\x05\x69\xa6\
2219-\x11\xea\x31\xdc\xa3\xa8\x46\x68\x7d\xd6\x31\x0d\x8a\x81\x8f\x9d\
2220-\x53\x70\xb4\x7e\x48\x18\x60\xb8\x7e\xbb\xb2\x07\x2a\xfa\x67\x60\
2221-\xf9\x89\x4f\x50\x8e\x79\x66\x55\x2f\x9d\x33\x3c\xaf\xad\x79\xa7\
2222-\x69\x14\x8e\x35\x0c\x53\x8d\xc6\x46\x44\xfc\xc8\x31\x29\x0d\x7c\
2223-\xd8\xe4\x15\x03\x87\x6b\x07\x38\x32\x42\x3c\xf0\x53\xee\x99\x81\
2224-\x23\xb5\x83\x3c\xab\x81\xf7\x30\xb2\x96\xa0\xa6\x34\xf0\x9e\x7d\
2225-\x0c\x8e\xd4\x0d\x09\x52\xab\xfb\x45\x64\x36\xf8\xd0\x9c\x06\xd5\
2226-\x3d\xae\x39\x47\x13\xeb\xcf\x50\x53\x1a\xc8\x6c\x18\x84\xc3\x35\
2227-\x03\xf0\xe6\x2f\x6e\x38\x84\x87\x9c\x13\xbe\x67\xa0\x07\x9f\xd3\
2228-\x2c\x83\xb5\x47\x90\x7a\xcf\x13\xd0\x77\x6b\x72\xd4\x94\x06\x32\
2229-\xea\x07\xe9\x12\xd2\x0f\xaf\x57\xf6\x11\x94\x0b\x56\xff\x03\x5d\
2230-\x76\x7e\x53\x0b\x3b\x2e\x08\x30\xaf\x13\xb5\xba\x97\x52\xec\x84\
2231-\x34\x7c\x7a\xda\xc1\xbb\xd5\x1a\x27\x6a\xfa\xa5\x81\x43\x55\x7d\
2232-\x90\x52\xd1\x0b\x6f\x54\xb9\xd7\xf0\x28\xf5\x23\x3f\x6c\x8b\x7b\
2233-\xc3\xf3\x90\x76\xb3\x83\x77\x11\x28\xec\x56\x72\xd4\x94\x06\x0e\
2234-\xde\xed\x83\xe4\x3b\xdd\x0c\x0e\xf4\x00\xf7\x96\x7c\xb0\x6d\x2a\
2235-\x87\xe6\x69\x0f\xc3\x3b\x39\x97\x06\x52\xca\x7b\x81\x79\xf5\xe7\
2236-\x2e\x46\xd4\x8b\x4f\x61\xdb\xfc\xed\x03\xde\xab\xb7\x5f\x1a\x48\
2237-\xbc\xd9\x09\x4c\xf2\xed\x6e\x85\xa4\x5b\x2e\x98\xff\x17\xb6\xc5\
2238-\x9f\x28\xde\x3c\xbd\x44\x7b\xe4\x5e\x99\xa3\x01\x97\x34\x10\x5f\
2239-\xda\x01\x04\x1d\x92\x68\x12\xc7\x5b\x9d\x30\xf3\x04\x74\x79\xfa\
2240-\x6c\x73\xec\xd3\x7f\x41\xea\x1d\x17\xed\x5d\xdb\xa5\xe4\x8c\x34\
2241-\x90\x50\xd6\x09\xb1\x3f\xb5\x03\x45\xce\xb9\x9e\x5c\x05\x3d\x34\
2242-\x73\xaa\x1c\x85\x7e\xe3\x5d\x7a\x73\x8c\x34\x10\x57\xd2\x01\xd1\
2243-\x37\xee\x0b\x28\x67\xa8\xf6\xfe\x03\xba\xf0\xbc\x44\xde\xe5\x3c\
2244-\xa6\x58\xb3\x4f\x9d\x4b\x03\xb1\xc5\xed\x40\x44\x5d\x73\x8a\xa8\
2245-\x66\x0c\xc5\x86\x1e\x6b\x39\x76\xdb\xa5\xb9\xc3\xb9\x6e\x2d\x7b\
2246-\xdc\x97\x06\x0e\x14\xb5\x01\x11\x75\xd5\x01\x9c\x47\x5f\xbf\x2f\
2247-\xa2\x79\x6a\x09\x3c\x2b\xa0\x21\xbd\xd8\x01\x47\xcb\xda\x21\x09\
2248-\x9f\x52\x7d\x47\xe2\xe4\x1d\x0c\x6b\xf0\x8c\x34\xf0\xca\x55\x27\
2249-\xa8\x89\x28\x6c\x55\xf2\xd2\xfe\x39\xe8\x7d\x04\x1a\x9c\x8b\x3e\
2250-\xf8\xa0\xc2\x05\xbb\xce\x57\xc3\x8b\xe7\xaa\xd6\xa8\x54\xf2\x98\
2251-\x5c\x2b\xc4\x5d\x73\xa8\x77\x71\xce\x51\x1a\x88\xbc\xd2\x06\x91\
2252-\x57\x5a\x61\x5f\x41\xb3\x80\x6a\x82\xf2\x77\xf0\xbb\xc0\x30\x3e\
2253-\xb9\x6b\x79\x6b\xe4\xf4\xcc\x42\x7c\x9e\x95\x77\x29\xec\xc7\x9d\
2254-\x08\xed\x96\x06\xf6\xff\xd8\x02\x44\x58\x9e\x5d\xc4\xf0\x7c\x3b\
2255-\xd7\x82\x86\xb1\x45\x70\xe3\xb3\x77\x2e\x6f\x8d\xeb\x03\x0f\x71\
2256-\x4f\x33\x84\xe3\x0e\xde\xad\xd6\x92\x2f\x80\x8d\xd0\x1c\x1b\xec\
2257-\xcb\x6f\x86\x30\x8c\xa1\x01\x24\xe2\xbf\x5b\xcf\xc2\x0a\xbe\xc4\
2258-\xd6\x0c\x0c\xac\x00\xed\xe3\xdd\x02\xce\x51\x53\x1a\x88\xc2\xe7\
2259-\xd8\x6b\xb4\xc0\xde\x6c\x0b\x84\xe7\xda\x09\x1c\xb4\x52\x14\xbd\
2260-\x30\x8c\x09\x45\xad\x50\x3f\xf6\x50\xfc\x8e\x1f\x5f\xc5\xe5\x9b\
2261-\x9b\x11\x66\xdb\x66\x96\xf8\x3e\x45\xde\x27\x88\xcc\xb5\x49\x03\
2262-\xd1\x68\x20\x04\x9b\x7b\xbe\x37\x13\x22\x0f\x35\xda\xd4\x3d\xaa\
2263-\x45\x4c\xbd\xe1\x84\xa2\xae\x07\xd0\x8a\xbf\xe9\x1e\xfb\x61\x43\
2264-\xea\xbc\x0b\x10\x9d\x8f\x82\x46\xab\xb8\xcb\xf7\x99\x03\x6a\x03\
2265-\x31\x05\x4d\xb0\xfb\x3b\x93\x42\xc8\x0f\x16\x42\xd3\xe3\x68\xc0\
2266-\x05\x5c\x6f\x00\x8a\x34\xf2\xbc\xee\xbe\xa8\x5c\xab\xca\x00\x16\
2267-\xbb\xbe\xad\x87\x3d\x97\x1b\x15\x0c\x97\x4d\xdc\xa3\xc8\x35\x81\
2268-\x0b\x1a\x44\x5f\xd6\xf2\x4c\x75\x47\xce\x5c\x92\xbb\x79\x26\x46\
2269-\x6d\x20\x0e\x8b\x60\xfc\x56\x13\x14\xc0\xee\x4b\x26\x08\xba\x28\
2270-\xeb\x60\x12\xc3\x1e\x42\xf5\x66\x91\x73\xcd\x5d\xca\x0d\x18\x13\
2271-\x0a\x54\x3f\x05\xb1\xc6\x46\x9f\x01\x85\x5e\x3e\x5f\x03\xc1\x17\
2272-\xeb\x75\xa1\x33\x26\x48\x53\x8b\xbb\x0c\x7d\x35\x53\xdf\xd1\xdc\
2273-\x0f\x41\x33\xf1\x46\x8b\x8f\x0d\x14\x1a\x3e\x2f\xfd\x3d\x31\x0f\
2274-\xff\xa3\xe0\x53\xee\xbc\x50\x27\x78\xe9\xab\x6a\x06\x2f\xd5\x8a\
2275-\xc8\x67\x3b\xbe\xae\xe1\x9a\xa2\x44\xce\x68\x72\x51\xe3\x1e\xd2\
2276-\x20\x2d\xd2\xe4\x3f\xcd\x22\xc8\x89\xe1\x0c\x9a\xc8\x36\xf9\x0f\
2277-\xe2\xd3\xbc\x96\x67\x01\x8a\x0c\xf7\x38\xea\x91\x92\x6f\xe5\x99\
2278-\x80\x68\x5b\xd7\x4b\xce\x31\xfb\x49\x8b\x34\x85\x36\x00\xb0\x89\
2279-\xe7\xfd\xe7\x79\x04\x69\xff\x0f\xc3\x78\xed\x1a\xcc\x42\xc6\x50\
2280-\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
2281+\x00\x00\x02\x50\
2282+\x89\
2283+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2284+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
2285+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
2286+\x01\x00\x9a\x9c\x18\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
2287+\x74\x77\x61\x72\x65\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\
2288+\x65\x52\x65\x61\x64\x79\x71\xc9\x65\x3c\x00\x00\x01\xdd\x49\x44\
2289+\x41\x54\x78\xda\x94\x53\xbb\x6e\x13\x41\x14\x3d\x33\x3b\x5e\x7b\
2290+\x63\x3b\x0f\x04\x29\x42\x91\x2d\x03\x55\x0a\xa4\xd4\x28\xe9\x23\
2291+\x37\x94\xf9\x01\xc4\x27\x50\x52\xd2\x13\x41\xc3\x17\xa4\xa3\xa3\
2292+\xa0\x41\xe9\x40\x02\x04\x54\xd9\x14\x11\x49\x94\x28\xb1\x1d\xef\
2293+\x63\x66\x76\x86\x3b\xb3\x8e\xbd\x11\x44\x8a\xef\xea\xee\xce\xeb\
2294+\x9c\x3d\xf7\xcc\x8c\x78\xf9\xf1\xcb\xfa\x72\xb7\xbb\x77\xbf\xd3\
2295+\x8e\xe7\x5b\x11\xee\x12\x83\x3c\xc3\xd9\xd5\x28\x39\x1d\x0e\x7b\
2296+\x78\xfd\xf9\xd7\xc1\xd1\x95\xb2\xca\xd8\x99\xd2\x61\x1c\x56\x74\
2297+\x9a\x61\xcc\x60\xf1\x67\x24\x31\x4b\x08\xc6\xb0\x18\xb5\x62\x11\
2298+\x70\x86\x81\xd4\x44\x51\x0f\x36\xfe\xda\x5b\x09\x98\x9f\xb5\x10\
2299+\xb9\x92\xc8\x4b\x33\x99\x48\x8b\x12\xcf\xde\x7e\xf5\xed\x0f\x2f\
2300+\x9e\x80\xd4\xde\x4a\xe2\xb0\x22\x97\x0a\xb2\x46\x50\x12\x40\xdb\
2301+\x4a\x81\xf5\xe0\x29\x01\xd5\x8e\x3a\x9f\xc3\x8a\x42\x2b\xa8\xb2\
2302+\x9c\x0c\x46\x21\x47\x2b\x6c\xf8\xf6\xf6\xee\xb7\xc9\x78\xbb\xc9\
2303+\xf1\x7e\x67\xed\xc6\xcf\x1c\xd6\x13\x48\x33\x25\x68\x18\x06\xd1\
2304+\x14\xff\x9a\x16\x06\x30\xa4\xa0\xbe\xb6\x22\x50\x9a\xea\x04\xb4\
2305+\xa9\x98\xa3\xc0\xc2\x34\x2b\x05\xaf\x36\x57\xb0\xba\x34\x25\xbb\
2306+\x24\xc9\x76\x5c\x83\xe0\x1c\x0e\xcb\x25\xb1\x38\xa3\x24\x11\xb8\
2307+\x54\xd6\x60\x79\x29\xf2\x24\xef\xbe\xf7\xd1\x2f\x2c\x39\x5e\x3d\
2308+\x6a\xbc\xc6\xa5\xc7\x78\x05\xa5\xae\x3a\xe3\xda\xce\x33\x89\xde\
2309+\xda\x02\x76\x7f\x0c\x70\x90\x59\x3c\xff\x74\xee\xc7\x3b\x0d\x8e\
2310+\x37\x4f\xef\xd1\x79\xc9\x7d\x3f\x74\x0a\x08\xcb\x0b\xad\xfd\x7e\
2311+\x4a\x32\xd2\x65\x46\xfd\xf5\x07\x0c\x3b\x8f\x17\x30\xdf\x0e\xc1\
2312+\x49\x89\xcf\x50\x78\xf9\xd7\xeb\x1c\xc6\x61\x85\x7b\xb9\x90\x66\
2313+\xea\xee\x71\x9a\xe3\xd1\x62\x80\xad\x87\x9d\x1b\x46\x26\xc3\x11\
2314+\x79\x65\x6b\x26\x12\x81\xab\x63\x90\x66\x08\x18\x47\xaa\x6b\x0e\
2315+\x53\x49\xfd\x42\xfd\xf7\x00\xcd\x89\xc0\x63\x2a\x0f\xb4\x4e\xf6\
2316+\x93\x24\xde\x88\x57\x11\x77\xe7\xee\x74\x0f\x2e\xd2\x14\xfb\xc9\
2317+\xa1\x53\x90\x08\x5b\x96\xbd\xdf\xc7\x27\x7b\x3f\x4f\x4e\xe3\x59\
2318+\x2e\x13\xb7\x36\xa1\x1d\xef\xfd\x15\x60\x00\xe8\x15\x51\x25\x42\
2319+\x1c\xe5\x09\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
2320+\x00\x00\x02\x50\
2321+\x89\
2322+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2323+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
2324+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
2325+\x01\x00\x9a\x9c\x18\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
2326+\x74\x77\x61\x72\x65\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\
2327+\x65\x52\x65\x61\x64\x79\x71\xc9\x65\x3c\x00\x00\x01\xdd\x49\x44\
2328+\x41\x54\x78\xda\x94\x53\xbb\x6e\x13\x41\x14\x3d\x33\x3b\x5e\x7b\
2329+\x63\x3b\x0f\x04\x29\x42\x91\x2d\x03\x55\x0a\xa4\xd4\x28\xe9\x23\
2330+\x37\x94\xf9\x01\xc4\x27\x50\x52\xd2\x13\x41\xc3\x17\xa4\xa3\xa3\
2331+\xa0\x41\xe9\x40\x02\x04\x54\xd9\x14\x11\x49\x94\x28\xb1\x1d\xef\
2332+\x63\x66\x76\x86\x3b\xb3\x8e\xbd\x11\x44\x8a\xef\xea\xee\xce\xeb\
2333+\x9c\x3d\xf7\xcc\x8c\x78\xf9\xf1\xcb\xfa\x72\xb7\xbb\x77\xbf\xd3\
2334+\x8e\xe7\x5b\x11\xee\x12\x83\x3c\xc3\xd9\xd5\x28\x39\x1d\x0e\x7b\
2335+\x78\xfd\xf9\xd7\xc1\xd1\x95\xb2\xca\xd8\x99\xd2\x61\x1c\x56\x74\
2336+\x9a\x61\xcc\x60\xf1\x67\x24\x31\x4b\x08\xc6\xb0\x18\xb5\x62\x11\
2337+\x70\x86\x81\xd4\x44\x51\x0f\x36\xfe\xda\x5b\x09\x98\x9f\xb5\x10\
2338+\xb9\x92\xc8\x4b\x33\x99\x48\x8b\x12\xcf\xde\x7e\xf5\xed\x0f\x2f\
2339+\x9e\x80\xd4\xde\x4a\xe2\xb0\x22\x97\x0a\xb2\x46\x50\x12\x40\xdb\
2340+\x4a\x81\xf5\xe0\x29\x01\xd5\x8e\x3a\x9f\xc3\x8a\x42\x2b\xa8\xb2\
2341+\x9c\x0c\x46\x21\x47\x2b\x6c\xf8\xf6\xf6\xee\xb7\xc9\x78\xbb\xc9\
2342+\xf1\x7e\x67\xed\xc6\xcf\x1c\xd6\x13\x48\x33\x25\x68\x18\x06\xd1\
2343+\x14\xff\x9a\x16\x06\x30\xa4\xa0\xbe\xb6\x22\x50\x9a\xea\x04\xb4\
2344+\xa9\x98\xa3\xc0\xc2\x34\x2b\x05\xaf\x36\x57\xb0\xba\x34\x25\xbb\
2345+\x24\xc9\x76\x5c\x83\xe0\x1c\x0e\xcb\x25\xb1\x38\xa3\x24\x11\xb8\
2346+\x54\xd6\x60\x79\x29\xf2\x24\xef\xbe\xf7\xd1\x2f\x2c\x39\x5e\x3d\
2347+\x6a\xbc\xc6\xa5\xc7\x78\x05\xa5\xae\x3a\xe3\xda\xce\x33\x89\xde\
2348+\xda\x02\x76\x7f\x0c\x70\x90\x59\x3c\xff\x74\xee\xc7\x3b\x0d\x8e\
2349+\x37\x4f\xef\xd1\x79\xc9\x7d\x3f\x74\x0a\x08\xcb\x0b\xad\xfd\x7e\
2350+\x4a\x32\xd2\x65\x46\xfd\xf5\x07\x0c\x3b\x8f\x17\x30\xdf\x0e\xc1\
2351+\x49\x89\xcf\x50\x78\xf9\xd7\xeb\x1c\xc6\x61\x85\x7b\xb9\x90\x66\
2352+\xea\xee\x71\x9a\xe3\xd1\x62\x80\xad\x87\x9d\x1b\x46\x26\xc3\x11\
2353+\x79\x65\x6b\x26\x12\x81\xab\x63\x90\x66\x08\x18\x47\xaa\x6b\x0e\
2354+\x53\x49\xfd\x42\xfd\xf7\x00\xcd\x89\xc0\x63\x2a\x0f\xb4\x4e\xf6\
2355+\x93\x24\xde\x88\x57\x11\x77\xe7\xee\x74\x0f\x2e\xd2\x14\xfb\xc9\
2356+\xa1\x53\x90\x08\x5b\x96\xbd\xdf\xc7\x27\x7b\x3f\x4f\x4e\xe3\x59\
2357+\x2e\x13\xb7\x36\xa1\x1d\xef\xfd\x15\x60\x00\xe8\x15\x51\x25\x42\
2358+\x1c\xe5\x09\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
2359 \x00\x00\x01\x27\
2360 \x89\
2361 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2362@@ -412,308 +406,329 @@
2363 \x04\xc3\x06\xd0\xb4\xf6\x1c\xaa\xf8\x39\x94\x8f\x12\xe1\x00\x01\
2364 \x06\x00\x00\x4e\x7e\x7b\x48\x93\x95\x27\x00\x00\x00\x00\x49\x45\
2365 \x4e\x44\xae\x42\x60\x82\
2366-\x00\x00\x12\xb1\
2367+\x00\x00\x14\x10\
2368 \x51\
2369 \x4d\x61\x69\x6e\x57\x69\x6e\x64\x6f\x77\x20\x7b\x0a\x20\x20\x20\
2370 \x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\
2371 \x72\x3a\x20\x23\x64\x64\x34\x38\x31\x34\x3b\x0a\x7d\x0a\x0a\x51\
2372 \x57\x69\x64\x67\x65\x74\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\
2373 \x74\x2d\x66\x61\x6d\x69\x6c\x79\x3a\x20\x22\x55\x62\x75\x6e\x74\
2374-\x75\x22\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x20\x7b\x0a\
2375-\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\
2376-\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\x66\x72\x61\x6d\
2377-\x65\x5f\x68\x65\x61\x64\x65\x72\x20\x7b\x0a\x20\x20\x20\x20\x62\
2378-\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\x66\x66\x66\x66\
2379-\x66\x66\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\
2380-\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\
2381-\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\
2382-\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2383-\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\
2384-\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\
2385-\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x6d\x69\x6e\x2d\x68\
2386-\x65\x69\x67\x68\x74\x3a\x20\x39\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\
2387-\x51\x46\x72\x61\x6d\x65\x23\x66\x72\x61\x6d\x65\x5f\x67\x72\x65\
2388-\x65\x74\x69\x6e\x67\x20\x7b\x0a\x20\x20\x20\x20\x6d\x61\x72\x67\
2389-\x69\x6e\x20\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\
2390-\x69\x6e\x67\x2d\x6c\x65\x66\x74\x3a\x20\x31\x35\x70\x78\x3b\x0a\
2391-\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x74\x6f\x70\x3a\
2392-\x20\x31\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\
2393-\x6e\x67\x2d\x72\x69\x67\x68\x74\x3a\x20\x31\x30\x70\x78\x3b\x0a\
2394-\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x62\x6f\x74\x74\
2395-\x6f\x6d\x3a\x20\x31\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\
2396-\x61\x6d\x65\x23\x66\x72\x61\x6d\x65\x5f\x73\x74\x61\x74\x75\x73\
2397-\x2c\x0a\x51\x46\x72\x61\x6d\x65\x23\x66\x72\x61\x6d\x65\x5f\x73\
2398-\x74\x6f\x72\x61\x67\x65\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2399-\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x64\x6f\x74\x74\x65\
2400-\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\
2401-\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\
2402-\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x77\x69\
2403-\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\
2404-\x64\x64\x69\x6e\x67\x3a\x20\x31\x30\x70\x78\x3b\x0a\x20\x20\x20\
2405-\x20\x6d\x69\x6e\x2d\x77\x69\x64\x74\x68\x3a\x20\x34\x30\x70\x78\
2406-\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\x61\x63\x63\x6f\
2407-\x75\x6e\x74\x5f\x73\x65\x70\x61\x72\x61\x74\x6f\x72\x20\x7b\x0a\
2408-\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\
2409-\x3a\x20\x64\x6f\x74\x74\x65\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2410-\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\
2411-\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2412-\x62\x6f\x74\x74\x6f\x6d\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\
2413-\x78\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\
2414-\x6e\x5b\x65\x6e\x61\x62\x6c\x65\x64\x3d\x22\x74\x72\x75\x65\x22\
2415-\x5d\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\
2416-\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\x69\
2417-\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\x30\
2418-\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\x0a\
2419-\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\
2420-\x23\x66\x65\x63\x66\x63\x32\x2c\x20\x73\x74\x6f\x70\x3a\x20\x31\
2421-\x2e\x30\x20\x23\x65\x34\x34\x65\x31\x39\x29\x3b\x0a\x20\x20\x20\
2422+\x75\x22\x3b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\
2423+\x65\x3a\x20\x31\x33\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\
2424+\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\x33\x3b\x0a\x7d\x0a\x0a\
2425+\x51\x46\x72\x61\x6d\x65\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2426+\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x7d\x0a\x0a\x51\x46\
2427+\x72\x61\x6d\x65\x23\x66\x72\x61\x6d\x65\x5f\x68\x65\x61\x64\x65\
2428+\x72\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\
2429+\x6e\x64\x3a\x20\x23\x66\x66\x66\x66\x66\x66\x3b\x0a\x20\x20\x20\
2430 \x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\
2431 \x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2432 \x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\
2433-\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\x20\x36\x70\x78\x3b\x0a\
2434-\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\
2435-\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\
2436-\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\
2437-\x20\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\
2438-\x70\x78\x3b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3a\x20\
2439-\x31\x34\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\
2440-\x74\x74\x6f\x6e\x3a\x68\x6f\x76\x65\x72\x5b\x65\x6e\x61\x62\x6c\
2441+\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\
2442+\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2443+\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\
2444+\x20\x20\x20\x20\x6d\x69\x6e\x2d\x68\x65\x69\x67\x68\x74\x3a\x20\
2445+\x39\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\
2446+\x66\x72\x61\x6d\x65\x5f\x67\x72\x65\x65\x74\x69\x6e\x67\x20\x7b\
2447+\x0a\x20\x20\x20\x20\x6d\x61\x72\x67\x69\x6e\x20\x30\x70\x78\x3b\
2448+\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x6c\x65\x66\
2449+\x74\x3a\x20\x31\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\
2450+\x64\x69\x6e\x67\x2d\x74\x6f\x70\x3a\x20\x31\x30\x70\x78\x3b\x0a\
2451+\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x72\x69\x67\x68\
2452+\x74\x3a\x20\x31\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\
2453+\x64\x69\x6e\x67\x2d\x62\x6f\x74\x74\x6f\x6d\x3a\x20\x31\x30\x70\
2454+\x78\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\x66\x72\x61\
2455+\x6d\x65\x5f\x73\x74\x61\x74\x75\x73\x2c\x0a\x51\x46\x72\x61\x6d\
2456+\x65\x23\x66\x72\x61\x6d\x65\x5f\x73\x74\x6f\x72\x61\x67\x65\x20\
2457+\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\
2458+\x6c\x65\x3a\x20\x64\x6f\x74\x74\x65\x64\x3b\x0a\x20\x20\x20\x20\
2459+\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\
2460+\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2461+\x72\x2d\x6c\x65\x66\x74\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\
2462+\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\x20\
2463+\x31\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x6d\x69\x6e\x2d\x77\x69\
2464+\x64\x74\x68\x3a\x20\x34\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x46\
2465+\x72\x61\x6d\x65\x23\x61\x63\x63\x6f\x75\x6e\x74\x5f\x73\x65\x70\
2466+\x61\x72\x61\x74\x6f\x72\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2467+\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x64\x6f\x74\x74\x65\
2468+\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\
2469+\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\
2470+\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\
2471+\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\
2472+\x46\x72\x61\x6d\x65\x23\x66\x72\x6d\x5f\x62\x6f\x78\x20\x7b\x20\
2473+\x2f\x2a\x54\x68\x65\x20\x4c\x6f\x61\x64\x69\x6e\x67\x20\x4f\x76\
2474+\x65\x72\x6c\x61\x79\x20\x66\x72\x61\x6d\x65\x2e\x2a\x2f\x0a\x20\
2475+\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\
2476+\x66\x66\x66\x66\x66\x66\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2477+\x65\x72\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\
2478+\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\
2479+\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2480+\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\
2481+\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x77\
2482+\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\
2483+\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\
2484+\x20\x6d\x69\x6e\x2d\x68\x65\x69\x67\x68\x74\x3a\x20\x31\x30\x30\
2485+\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\x66\x72\
2486+\x6d\x5f\x62\x6f\x78\x20\x3e\x20\x51\x4c\x61\x62\x65\x6c\x20\x7b\
2487+\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\
2488+\x32\x34\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\
2489+\x74\x74\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2490+\x72\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\
2491+\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\
2492+\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\
2493+\x69\x6e\x67\x3a\x20\x36\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\
2494+\x64\x64\x69\x6e\x67\x2d\x6c\x65\x66\x74\x3a\x20\x32\x30\x70\x78\
2495+\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x72\x69\
2496+\x67\x68\x74\x3a\x20\x32\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2497+\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\
2498+\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\
2499+\x5b\x65\x6e\x61\x62\x6c\x65\x64\x3d\x22\x74\x72\x75\x65\x22\x5d\
2500+\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\
2501+\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\x69\x65\
2502+\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\x30\x2c\
2503+\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\x0a\x20\
2504+\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\x23\
2505+\x66\x65\x39\x65\x38\x34\x2c\x73\x74\x6f\x70\x3a\x20\x31\x2e\x30\
2506+\x20\x23\x64\x64\x34\x38\x31\x34\x29\x3b\x0a\x20\x20\x20\x20\x63\
2507+\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\
2508+\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\
2509+\x39\x39\x39\x39\x39\x39\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\
2510+\x42\x75\x74\x74\x6f\x6e\x3a\x68\x6f\x76\x65\x72\x5b\x65\x6e\x61\
2511+\x62\x6c\x65\x64\x3d\x22\x74\x72\x75\x65\x22\x5d\x20\x7b\x0a\x20\
2512+\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x71\
2513+\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\x69\x65\x6e\x74\x28\x78\
2514+\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\x30\x2c\x20\x78\x32\x3a\
2515+\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\x0a\x20\x20\x20\x20\x20\
2516+\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\x23\x66\x66\x62\x31\
2517+\x39\x63\x2c\x73\x74\x6f\x70\x3a\x20\x31\x2e\x30\x20\x23\x64\x64\
2518+\x34\x38\x31\x34\x29\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\
2519+\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2520+\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x39\x39\x39\
2521+\x39\x39\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\
2522+\x6f\x6e\x3a\x70\x72\x65\x73\x73\x65\x64\x5b\x65\x6e\x61\x62\x6c\
2523 \x65\x64\x3d\x22\x74\x72\x75\x65\x22\x5d\x20\x7b\x0a\x20\x20\x20\
2524 \x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x71\x6c\x69\
2525 \x6e\x65\x61\x72\x67\x72\x61\x64\x69\x65\x6e\x74\x28\x78\x31\x3a\
2526 \x20\x30\x2c\x20\x79\x31\x3a\x20\x30\x2c\x20\x78\x32\x3a\x20\x30\
2527 \x2c\x20\x79\x32\x3a\x20\x31\x2c\x0a\x20\x20\x20\x20\x20\x20\x20\
2528-\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\x23\x66\x65\x64\x61\x64\x31\
2529-\x2c\x73\x74\x6f\x70\x3a\x20\x31\x2e\x30\x20\x23\x65\x34\x37\x61\
2530-\x35\x35\x29\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2531-\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\
2532-\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\
2533-\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\
2534-\x67\x3a\x20\x36\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\
2535-\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2536-\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\
2537-\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2538-\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\
2539-\x68\x65\x69\x67\x68\x74\x3a\x20\x31\x32\x70\x78\x3b\x0a\x7d\x0a\
2540-\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x3a\x70\x72\x65\
2541-\x73\x73\x65\x64\x5b\x65\x6e\x61\x62\x6c\x65\x64\x3d\x22\x74\x72\
2542+\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\x23\x62\x39\x33\x66\x31\x34\
2543+\x2c\x73\x74\x6f\x70\x3a\x20\x31\x2e\x30\x20\x23\x64\x64\x34\x38\
2544+\x31\x34\x29\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\
2545+\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2546+\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x39\x39\x39\x39\x39\
2547+\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\
2548+\x5b\x73\x65\x63\x6f\x6e\x64\x61\x72\x79\x3d\x22\x74\x72\x75\x65\
2549+\x22\x5d\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\
2550+\x75\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\
2551+\x69\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\
2552+\x30\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\
2553+\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\
2554+\x20\x23\x66\x66\x66\x66\x66\x66\x2c\x73\x74\x6f\x70\x3a\x20\x31\
2555+\x2e\x30\x20\x23\x65\x36\x65\x36\x65\x36\x29\x3b\x0a\x20\x20\x20\
2556+\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\x33\x3b\
2557+\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\
2558+\x72\x3a\x20\x23\x39\x39\x39\x39\x39\x39\x3b\x0a\x7d\x0a\x0a\x51\
2559+\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x3a\x68\x6f\x76\x65\x72\
2560+\x5b\x73\x65\x63\x6f\x6e\x64\x61\x72\x79\x3d\x22\x74\x72\x75\x65\
2561+\x22\x5d\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\
2562+\x75\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\
2563+\x69\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\
2564+\x30\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\
2565+\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\
2566+\x20\x23\x66\x66\x66\x66\x66\x66\x2c\x73\x74\x6f\x70\x3a\x20\x31\
2567+\x2e\x30\x20\x23\x65\x64\x65\x64\x65\x64\x29\x3b\x0a\x20\x20\x20\
2568+\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\x33\x3b\
2569+\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\
2570+\x72\x3a\x20\x23\x39\x39\x39\x39\x39\x39\x3b\x0a\x7d\x0a\x0a\x51\
2571+\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x3a\x70\x72\x65\x73\x73\
2572+\x65\x64\x5b\x73\x65\x63\x6f\x6e\x64\x61\x72\x79\x3d\x22\x74\x72\
2573 \x75\x65\x22\x5d\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\
2574 \x72\x6f\x75\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\
2575 \x61\x64\x69\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\
2576 \x3a\x20\x30\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\
2577 \x31\x2c\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\
2578-\x20\x30\x20\x23\x65\x34\x34\x65\x31\x39\x2c\x73\x74\x6f\x70\x3a\
2579-\x20\x31\x2e\x30\x20\x23\x66\x65\x63\x66\x63\x32\x29\x3b\x0a\x20\
2580-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\x64\x69\x75\x73\
2581-\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2582-\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\
2583-\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\x20\x36\x70\x78\
2584-\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\
2585-\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\
2586-\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\
2587-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\
2588-\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\x68\x74\
2589-\x3a\x20\x31\x32\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\
2590-\x42\x75\x74\x74\x6f\x6e\x5b\x65\x6e\x61\x62\x6c\x65\x64\x3d\x22\
2591-\x66\x61\x6c\x73\x65\x22\x5d\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\
2592-\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\
2593-\x72\x67\x72\x61\x64\x69\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\
2594-\x20\x79\x31\x3a\x20\x30\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\
2595-\x32\x3a\x20\x31\x2c\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\
2596-\x6f\x70\x3a\x20\x30\x20\x23\x65\x61\x65\x61\x65\x61\x2c\x20\x73\
2597-\x74\x6f\x70\x3a\x20\x31\x2e\x30\x20\x23\x63\x61\x63\x61\x63\x61\
2598-\x29\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\
2599-\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2600-\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\
2601-\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\
2602-\x20\x36\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\
2603-\x20\x23\x35\x39\x35\x39\x35\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2604-\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\
2605-\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2606-\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\
2607-\x68\x65\x69\x67\x68\x74\x3a\x20\x31\x32\x70\x78\x3b\x0a\x7d\x0a\
2608-\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x23\x68\x65\x6c\
2609-\x70\x5f\x62\x75\x74\x74\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x62\
2610-\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x74\x72\x61\x6e\x73\
2611-\x70\x61\x72\x65\x6e\x74\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2612-\x65\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\x20\x20\x20\x63\x6f\
2613-\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\
2614-\x68\x65\x69\x67\x68\x74\x3a\x20\x32\x30\x70\x78\x3b\x0a\x20\x20\
2615-\x20\x20\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\x74\x69\x6f\
2616-\x6e\x3a\x20\x75\x6e\x64\x65\x72\x6c\x69\x6e\x65\x3b\x0a\x20\x20\
2617-\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\x20\x30\x70\x78\x3b\x0a\
2618-\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x23\x65\
2619-\x78\x70\x6c\x6f\x72\x65\x5f\x66\x6f\x6c\x64\x65\x72\x5f\x62\x75\
2620-\x74\x74\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2621-\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\x20\x20\x20\x62\x61\x63\
2622-\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\
2623-\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x35\x39\x35\x39\x35\
2624-\x39\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\
2625-\x6e\x23\x61\x64\x64\x5f\x66\x6f\x6c\x64\x65\x72\x5f\x62\x75\x74\
2626-\x74\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\
2627-\x67\x3a\x20\x35\x70\x78\x3b\x0a\x7d\x0a\x0a\x47\x6f\x54\x6f\x57\
2628-\x65\x62\x42\x75\x74\x74\x6f\x6e\x23\x73\x68\x61\x72\x65\x5f\x70\
2629-\x75\x62\x6c\x69\x73\x68\x5f\x62\x75\x74\x74\x6f\x6e\x20\x7b\x0a\
2630+\x20\x30\x20\x23\x64\x39\x64\x39\x64\x39\x2c\x73\x74\x6f\x70\x3a\
2631+\x20\x31\x2e\x30\x20\x23\x66\x65\x66\x65\x66\x65\x29\x3b\x0a\x20\
2632+\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\
2633+\x33\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\
2634+\x6c\x6f\x72\x3a\x20\x23\x39\x39\x39\x39\x39\x39\x3b\x0a\x7d\x0a\
2635+\x0a\x51\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x5b\x65\x6e\x61\
2636+\x62\x6c\x65\x64\x3d\x22\x66\x61\x6c\x73\x65\x22\x5d\x20\x7b\x0a\
2637 \x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\
2638-\x74\x72\x61\x6e\x73\x70\x61\x72\x65\x6e\x74\x3b\x0a\x20\x20\x20\
2639-\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\
2640-\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x64\x64\x34\x38\x31\
2641-\x34\x3b\x0a\x20\x20\x20\x20\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\
2642-\x72\x61\x74\x69\x6f\x6e\x3a\x20\x75\x6e\x64\x65\x72\x6c\x69\x6e\
2643-\x65\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\
2644-\x61\x62\x20\x7b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3a\
2645-\x20\x31\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\
2646-\x3a\x20\x23\x33\x33\x33\x33\x33\x33\x3b\x0a\x20\x20\x20\x20\x62\
2647-\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\x72\x3a\
2648-\x20\x23\x65\x34\x65\x30\x64\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2649-\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x6c\x65\x66\x74\x2d\x72\x61\
2650-\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2651-\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x72\x69\x67\x68\x74\x2d\
2652-\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\
2653-\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\
2654-\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\
2655-\x67\x3a\x20\x31\x32\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2656-\x64\x65\x72\x2d\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\
2657-\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2658-\x65\x72\x2d\x72\x69\x67\x68\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\
2659-\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2660-\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\
2661-\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2662-\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\
2663-\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2664-\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\
2665-\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\x3a\
2666-\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\x0a\x20\x20\x20\x20\x62\
2667-\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\x72\x3a\
2668-\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2669-\x65\x72\x2d\x74\x6f\x70\x2d\x6c\x65\x66\x74\x2d\x72\x61\x64\x69\
2670-\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2671-\x64\x65\x72\x2d\x74\x6f\x70\x2d\x72\x69\x67\x68\x74\x2d\x72\x61\
2672-\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2673-\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\
2674-\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\
2675-\x20\x31\x32\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2676-\x72\x2d\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\
2677-\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2678-\x2d\x72\x69\x67\x68\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\
2679-\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2680-\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\
2681-\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2682-\x62\x6f\x74\x74\x6f\x6d\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\
2683-\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2684-\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\
2685-\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\x3a\x66\x69\x72\x73\
2686-\x74\x3a\x21\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\x0a\x20\x20\
2687-\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\
2688-\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\
2689-\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\
2690-\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x7d\x0a\
2691-\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\x3a\x66\x69\
2692-\x72\x73\x74\x3a\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\x0a\x20\
2693-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\
2694-\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x7d\
2695-\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\x3a\x6d\
2696-\x69\x64\x64\x6c\x65\x3a\x21\x73\x65\x6c\x65\x63\x74\x65\x64\x20\
2697+\x71\x6c\x69\x6e\x65\x61\x72\x67\x72\x61\x64\x69\x65\x6e\x74\x28\
2698+\x78\x31\x3a\x20\x30\x2c\x20\x79\x31\x3a\x20\x30\x2c\x20\x78\x32\
2699+\x3a\x20\x30\x2c\x20\x79\x32\x3a\x20\x31\x2c\x0a\x20\x20\x20\x20\
2700+\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\x30\x20\x23\x65\x61\x65\
2701+\x61\x65\x61\x2c\x20\x73\x74\x6f\x70\x3a\x20\x31\x2e\x30\x20\x23\
2702+\x63\x61\x63\x61\x63\x61\x29\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\
2703+\x6f\x72\x3a\x20\x23\x35\x39\x35\x39\x35\x39\x3b\x0a\x20\x20\x20\
2704+\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\
2705+\x39\x33\x39\x33\x38\x39\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\
2706+\x42\x75\x74\x74\x6f\x6e\x23\x68\x65\x6c\x70\x5f\x62\x75\x74\x74\
2707+\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\
2708+\x75\x6e\x64\x3a\x20\x74\x72\x61\x6e\x73\x70\x61\x72\x65\x6e\x74\
2709+\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\
2710+\x6e\x65\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x77\
2711+\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x74\x65\x78\x74\x2d\x64\
2712+\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x3a\x20\x75\x6e\x64\x65\x72\
2713+\x6c\x69\x6e\x65\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\
2714+\x67\x3a\x20\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x50\x75\x73\x68\
2715+\x42\x75\x74\x74\x6f\x6e\x23\x65\x78\x70\x6c\x6f\x72\x65\x5f\x66\
2716+\x6f\x6c\x64\x65\x72\x5f\x62\x75\x74\x74\x6f\x6e\x20\x7b\x0a\x20\
2717+\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\
2718+\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\
2719+\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\
2720+\x3a\x20\x23\x35\x39\x35\x39\x35\x39\x3b\x0a\x20\x20\x20\x20\x70\
2721+\x61\x64\x64\x69\x6e\x67\x2d\x6c\x65\x66\x74\x3a\x20\x31\x30\x70\
2722+\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x72\
2723+\x69\x67\x68\x74\x3a\x20\x31\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\
2724+\x50\x75\x73\x68\x42\x75\x74\x74\x6f\x6e\x23\x74\x77\x69\x74\x74\
2725+\x65\x72\x5f\x62\x75\x74\x74\x6f\x6e\x2c\x0a\x51\x50\x75\x73\x68\
2726+\x42\x75\x74\x74\x6f\x6e\x23\x66\x61\x63\x65\x62\x6f\x6f\x6b\x5f\
2727+\x62\x75\x74\x74\x6f\x6e\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2728+\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x7d\x0a\x0a\x47\x6f\
2729+\x54\x6f\x57\x65\x62\x42\x75\x74\x74\x6f\x6e\x23\x73\x68\x61\x72\
2730+\x65\x5f\x70\x75\x62\x6c\x69\x73\x68\x5f\x62\x75\x74\x74\x6f\x6e\
2731+\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\
2732+\x64\x3a\x20\x74\x72\x61\x6e\x73\x70\x61\x72\x65\x6e\x74\x3b\x0a\
2733+\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\x6e\x65\
2734+\x3b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x64\x64\
2735+\x34\x38\x31\x34\x3b\x0a\x20\x20\x20\x20\x74\x65\x78\x74\x2d\x64\
2736+\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x3a\x20\x75\x6e\x64\x65\x72\
2737+\x6c\x69\x6e\x65\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\
2738+\x3a\x3a\x74\x61\x62\x20\x7b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\
2739+\x68\x74\x3a\x20\x31\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x63\x6f\
2740+\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\x33\x3b\x0a\x20\x20\
2741+\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\
2742+\x6f\x72\x3a\x20\x23\x65\x34\x65\x30\x64\x64\x3b\x0a\x20\x20\x20\
2743+\x20\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x6c\x65\x66\x74\
2744+\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\
2745+\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x72\x69\x67\
2746+\x68\x74\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\
2747+\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\
2748+\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\
2749+\x64\x69\x6e\x67\x3a\x20\x31\x32\x70\x78\x3b\x0a\x20\x20\x20\x20\
2750+\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\
2751+\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\
2752+\x6f\x72\x64\x65\x72\x2d\x72\x69\x67\x68\x74\x2d\x63\x6f\x6c\x6f\
2753+\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\
2754+\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\
2755+\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2756+\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x63\x6f\x6c\x6f\
2757+\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\
2758+\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\
2759+\x78\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\
2760+\x61\x62\x3a\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\x0a\x20\x20\
2761+\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\
2762+\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\
2763+\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x6c\x65\x66\x74\x2d\x72\
2764+\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\
2765+\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x72\x69\x67\x68\x74\
2766+\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\
2767+\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\
2768+\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\
2769+\x6e\x67\x3a\x20\x31\x32\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2770+\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\
2771+\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\
2772+\x64\x65\x72\x2d\x72\x69\x67\x68\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\
2773+\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2774+\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\
2775+\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2776+\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x63\x6f\x6c\x6f\x72\x3a\
2777+\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2778+\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x7d\
2779+\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\x3a\x66\
2780+\x69\x72\x73\x74\x3a\x21\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\
2781+\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\
2782+\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\
2783+\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\x74\
2784+\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\
2785+\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\
2786+\x3a\x66\x69\x72\x73\x74\x3a\x73\x65\x6c\x65\x63\x74\x65\x64\x20\
2787 \x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\
2788-\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\x34\x65\x30\x64\x64\
2789+\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\
2790 \x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\
2791-\x62\x3a\x68\x6f\x76\x65\x72\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\
2792-\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x71\x6c\x69\x6e\x65\x61\
2793-\x72\x67\x72\x61\x64\x69\x65\x6e\x74\x28\x78\x31\x3a\x20\x30\x2c\
2794-\x20\x79\x31\x3a\x20\x30\x2c\x20\x78\x32\x3a\x20\x30\x2c\x20\x79\
2795-\x32\x3a\x20\x31\x2c\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\
2796-\x6f\x70\x3a\x20\x30\x20\x23\x66\x61\x66\x61\x66\x61\x2c\x20\x73\
2797-\x74\x6f\x70\x3a\x20\x30\x2e\x34\x20\x23\x66\x34\x66\x34\x66\x34\
2798-\x2c\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x73\x74\x6f\x70\x3a\x20\
2799-\x30\x2e\x35\x20\x23\x65\x37\x65\x37\x65\x37\x2c\x20\x73\x74\x6f\
2800-\x70\x3a\x20\x31\x2e\x30\x20\x23\x66\x61\x66\x61\x66\x61\x29\x3b\
2801-\x0a\x20\x20\x20\x20\x74\x65\x78\x74\x2d\x64\x65\x63\x6f\x72\x61\
2802-\x74\x69\x6f\x6e\x3a\x20\x75\x6e\x64\x65\x72\x6c\x69\x6e\x65\x3b\
2803-\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\x3a\x74\x61\x62\
2804-\x3a\x6c\x61\x73\x74\x3a\x21\x73\x65\x6c\x65\x63\x74\x65\x64\x20\
2805-\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x6c\x65\x66\
2806-\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\x34\x65\x30\x64\x64\
2807-\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x57\x69\x64\x67\x65\x74\x20\
2808+\x62\x3a\x6d\x69\x64\x64\x6c\x65\x3a\x21\x73\x65\x6c\x65\x63\x74\
2809+\x65\x64\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2810+\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\x34\x65\
2811+\x30\x64\x64\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\x3a\
2812+\x3a\x74\x61\x62\x3a\x68\x6f\x76\x65\x72\x20\x7b\x0a\x20\x20\x20\
2813+\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\x66\x36\
2814+\x66\x36\x66\x36\x3b\x0a\x20\x20\x20\x20\x74\x65\x78\x74\x2d\x64\
2815+\x65\x63\x6f\x72\x61\x74\x69\x6f\x6e\x3a\x20\x75\x6e\x64\x65\x72\
2816+\x6c\x69\x6e\x65\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x42\x61\x72\
2817+\x3a\x3a\x74\x61\x62\x3a\x6c\x61\x73\x74\x3a\x21\x73\x65\x6c\x65\
2818+\x63\x74\x65\x64\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2819+\x72\x2d\x6c\x65\x66\x74\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\
2820+\x34\x65\x30\x64\x64\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x57\x69\
2821+\x64\x67\x65\x74\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2822+\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x6c\x65\x66\x74\x2d\x72\x61\
2823+\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2824+\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x72\x69\x67\
2825+\x68\x74\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\
2826+\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\
2827+\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\
2828+\x64\x69\x6e\x67\x3a\x20\x31\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\
2829+\x54\x61\x62\x57\x69\x64\x67\x65\x74\x3a\x3a\x70\x61\x6e\x65\x20\
2830 \x7b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\
2831 \x74\x6f\x6d\x2d\x6c\x65\x66\x74\x2d\x72\x61\x64\x69\x75\x73\x3a\
2832 \x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2833 \x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x72\x69\x67\x68\x74\x2d\x72\x61\
2834 \x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\
2835-\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\
2836-\x69\x64\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x3a\
2837-\x20\x31\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x54\x61\x62\x57\x69\
2838-\x64\x67\x65\x74\x3a\x3a\x70\x61\x6e\x65\x20\x7b\x0a\x20\x20\x20\
2839-\x20\x62\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x6c\
2840-\x65\x66\x74\x2d\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\
2841-\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\
2842-\x6f\x6d\x2d\x72\x69\x67\x68\x74\x2d\x72\x61\x64\x69\x75\x73\x3a\
2843-\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2844-\x2d\x74\x6f\x70\x2d\x72\x69\x67\x68\x74\x2d\x72\x61\x64\x69\x75\
2845-\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\
2846-\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\x69\x64\x3b\
2847-\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\
2848-\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\
2849-\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\
2850-\x78\x3b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\
2851-\x64\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2852-\x72\x64\x65\x72\x2d\x74\x6f\x70\x3a\x20\x32\x70\x78\x20\x73\x6f\
2853-\x6c\x69\x64\x20\x77\x68\x69\x74\x65\x3b\x0a\x7d\x0a\x0a\x51\x47\
2854-\x72\x6f\x75\x70\x42\x6f\x78\x20\x7b\x0a\x20\x20\x20\x20\x70\x61\
2855-\x64\x64\x69\x6e\x67\x2d\x74\x6f\x70\x3a\x20\x33\x30\x70\x78\x3b\
2856-\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x3a\x20\x6e\x6f\x6e\
2857-\x65\x3b\x0a\x20\x20\x20\x20\x6d\x61\x72\x67\x69\x6e\x2d\x74\x6f\
2858-\x70\x3a\x20\x31\x65\x78\x3b\x0a\x7d\x0a\x0a\x51\x47\x72\x6f\x75\
2859-\x70\x42\x6f\x78\x3a\x3a\x74\x69\x74\x6c\x65\x20\x7b\x0a\x20\x20\
2860-\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\x33\x33\x33\
2861-\x3b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x3a\x20\x62\x6f\x6c\x64\
2862-\x20\x31\x35\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x47\x72\x6f\x75\x70\
2863-\x42\x6f\x78\x23\x70\x72\x6f\x66\x69\x6c\x65\x2c\x0a\x51\x47\x72\
2864-\x6f\x75\x70\x42\x6f\x78\x23\x73\x65\x72\x76\x69\x63\x65\x73\x20\
2865-\x7b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x6c\x65\
2866-\x66\x74\x3a\x20\x35\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\x69\x73\
2867-\x74\x57\x69\x64\x67\x65\x74\x20\x7b\x0a\x20\x20\x20\x20\x62\x61\
2868-\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\x66\x37\x66\x36\x66\
2869-\x35\x3b\x0a\x20\x20\x20\x20\x61\x6c\x74\x65\x72\x6e\x61\x74\x65\
2870-\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\
2871-\x72\x3a\x20\x23\x65\x66\x65\x64\x65\x63\x3b\x0a\x7d\x0a\x0a\x51\
2872-\x4c\x69\x73\x74\x57\x69\x64\x67\x65\x74\x23\x6c\x69\x73\x74\x5f\
2873-\x64\x65\x76\x69\x63\x65\x73\x3a\x3a\x69\x74\x65\x6d\x20\x7b\x0a\
2874-\x20\x20\x20\x20\x6d\x69\x6e\x2d\x68\x65\x69\x67\x68\x74\x3a\x20\
2875-\x34\x38\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\x61\x62\x65\x6c\x23\
2876-\x6f\x74\x68\x65\x72\x5f\x64\x65\x76\x69\x63\x65\x73\x5f\x6c\x61\
2877-\x62\x65\x6c\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x3a\x20\
2878-\x62\x6f\x6c\x64\x20\x31\x36\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\
2879-\x61\x62\x65\x6c\x23\x70\x65\x72\x63\x65\x6e\x74\x61\x67\x65\x5f\
2880-\x75\x73\x61\x67\x65\x5f\x6c\x61\x62\x65\x6c\x20\x7b\x0a\x20\x20\
2881-\x20\x20\x6d\x61\x72\x67\x69\x6e\x2d\x74\x6f\x70\x3a\x20\x2d\x34\
2882-\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\x61\x62\x65\x6c\x23\x66\x6f\
2883-\x6c\x6c\x6f\x77\x5f\x75\x73\x5f\x6c\x61\x62\x65\x6c\x20\x7b\x0a\
2884-\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\x65\
2885-\x3b\x0a\x7d\x0a\x0a\x51\x46\x72\x61\x6d\x65\x23\x66\x72\x6d\x5f\
2886-\x62\x6f\x78\x20\x7b\x20\x2f\x2a\x20\x74\x68\x65\x20\x6c\x6f\x61\
2887-\x64\x69\x6e\x67\x6f\x76\x65\x72\x6c\x61\x79\x20\x66\x72\x61\x6d\
2888-\x65\x20\x2a\x2f\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\
2889-\x75\x6e\x64\x3a\x20\x23\x66\x66\x66\x66\x66\x66\x3b\x0a\x20\x20\
2890-\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\x64\x69\x75\x73\x3a\
2891-\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2892-\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\
2893-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\
2894-\x20\x23\x39\x33\x39\x33\x38\x39\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2895-\x72\x64\x65\x72\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\
2896-\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x77\x68\x69\x74\
2897-\x65\x3b\x0a\x20\x20\x20\x20\x6d\x69\x6e\x2d\x68\x65\x69\x67\x68\
2898-\x74\x3a\x20\x31\x30\x30\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x41\x62\
2899-\x73\x74\x72\x61\x63\x74\x49\x74\x65\x6d\x56\x69\x65\x77\x20\x7b\
2900-\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\
2901-\x65\x3a\x20\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\
2902-\x72\x64\x65\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\x33\x33\
2903-\x33\x33\x33\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\
2904-\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\
2905-\x61\x6c\x74\x65\x72\x6e\x61\x74\x65\x2d\x62\x61\x63\x6b\x67\x72\
2906-\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\x66\x65\
2907-\x64\x65\x63\x3b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\
2908-\x75\x6e\x64\x3a\x20\x23\x66\x37\x66\x36\x66\x35\x3b\x0a\x7d\x0a\
2909-\
2910+\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x2d\x72\x69\x67\x68\x74\x2d\
2911+\x72\x61\x64\x69\x75\x73\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\
2912+\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\x73\
2913+\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2914+\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x39\x33\x39\x33\x38\x39\x3b\
2915+\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x77\x69\x64\x74\
2916+\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\
2917+\x67\x72\x6f\x75\x6e\x64\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\x20\
2918+\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\x3a\x20\x32\
2919+\x70\x78\x20\x73\x6f\x6c\x69\x64\x20\x77\x68\x69\x74\x65\x3b\x0a\
2920+\x7d\x0a\x0a\x51\x47\x72\x6f\x75\x70\x42\x6f\x78\x20\x7b\x0a\x20\
2921+\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x74\x6f\x70\x3a\x20\
2922+\x33\x30\x70\x78\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\
2923+\x3a\x20\x6e\x6f\x6e\x65\x3b\x0a\x20\x20\x20\x20\x6d\x61\x72\x67\
2924+\x69\x6e\x2d\x74\x6f\x70\x3a\x20\x31\x65\x78\x3b\x0a\x7d\x0a\x0a\
2925+\x51\x47\x72\x6f\x75\x70\x42\x6f\x78\x3a\x3a\x74\x69\x74\x6c\x65\
2926+\x20\x7b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x33\
2927+\x33\x33\x33\x33\x33\x3b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x3a\
2928+\x20\x62\x6f\x6c\x64\x20\x31\x35\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\
2929+\x47\x72\x6f\x75\x70\x42\x6f\x78\x23\x70\x72\x6f\x66\x69\x6c\x65\
2930+\x2c\x0a\x51\x47\x72\x6f\x75\x70\x42\x6f\x78\x23\x73\x65\x72\x76\
2931+\x69\x63\x65\x73\x20\x7b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\
2932+\x6e\x67\x2d\x6c\x65\x66\x74\x3a\x20\x35\x70\x78\x3b\x0a\x7d\x0a\
2933+\x0a\x51\x4c\x69\x73\x74\x57\x69\x64\x67\x65\x74\x20\x7b\x0a\x20\
2934+\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\
2935+\x66\x37\x66\x36\x66\x35\x3b\x0a\x20\x20\x20\x20\x61\x6c\x74\x65\
2936+\x72\x6e\x61\x74\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\
2937+\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x65\x66\x65\x64\x65\x63\x3b\
2938+\x0a\x7d\x0a\x0a\x51\x4c\x69\x73\x74\x57\x69\x64\x67\x65\x74\x23\
2939+\x6c\x69\x73\x74\x5f\x64\x65\x76\x69\x63\x65\x73\x3a\x3a\x69\x74\
2940+\x65\x6d\x20\x7b\x0a\x20\x20\x20\x20\x6d\x69\x6e\x2d\x68\x65\x69\
2941+\x67\x68\x74\x3a\x20\x34\x38\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\
2942+\x61\x62\x65\x6c\x23\x6f\x74\x68\x65\x72\x5f\x64\x65\x76\x69\x63\
2943+\x65\x73\x5f\x6c\x61\x62\x65\x6c\x20\x7b\x0a\x20\x20\x20\x20\x66\
2944+\x6f\x6e\x74\x3a\x20\x62\x6f\x6c\x64\x20\x31\x36\x70\x78\x3b\x0a\
2945+\x7d\x0a\x0a\x51\x4c\x61\x62\x65\x6c\x23\x70\x65\x72\x63\x65\x6e\
2946+\x74\x61\x67\x65\x5f\x75\x73\x61\x67\x65\x5f\x6c\x61\x62\x65\x6c\
2947+\x20\x7b\x0a\x20\x20\x20\x20\x6d\x61\x72\x67\x69\x6e\x2d\x74\x6f\
2948+\x70\x3a\x20\x2d\x34\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\x61\x62\
2949+\x65\x6c\x23\x66\x6f\x6c\x6c\x6f\x77\x5f\x75\x73\x5f\x6c\x61\x62\
2950+\x65\x6c\x20\x7b\x0a\x20\x20\x20\x20\x63\x6f\x6c\x6f\x72\x3a\x20\
2951+\x77\x68\x69\x74\x65\x3b\x0a\x7d\x0a\x0a\x51\x41\x62\x73\x74\x72\
2952+\x61\x63\x74\x49\x74\x65\x6d\x56\x69\x65\x77\x20\x7b\x0a\x20\x20\
2953+\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x73\x74\x79\x6c\x65\x3a\x20\
2954+\x73\x6f\x6c\x69\x64\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\
2955+\x72\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x38\x39\x38\x39\x38\x39\
2956+\x3b\x0a\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x74\x6f\x70\
2957+\x2d\x77\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\
2958+\x20\x62\x6f\x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x2d\x77\
2959+\x69\x64\x74\x68\x3a\x20\x31\x70\x78\x3b\x0a\x20\x20\x20\x20\x61\
2960+\x6c\x74\x65\x72\x6e\x61\x74\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\
2961+\x75\x6e\x64\x2d\x63\x6f\x6c\x6f\x72\x3a\x20\x23\x66\x37\x66\x36\
2962+\x66\x35\x3b\x0a\x20\x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\
2963+\x6e\x64\x3a\x20\x23\x65\x66\x65\x64\x65\x63\x3b\x0a\x7d\x0a\
2964 \x00\x00\x02\x6f\
2965 \x89\
2966 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2967@@ -23296,85 +23311,80 @@
2968 \xee\xee\xee\x93\x74\xd0\xf8\xf8\x78\xff\xac\x9e\xfb\xdf\x96\xbf\
2969 \x02\x0c\x00\x57\x70\xa3\x5d\x63\x1a\xb1\xc1\x00\x00\x00\x00\x49\
2970 \x45\x4e\x44\xae\x42\x60\x82\
2971-\x00\x00\x04\xc5\
2972-\x89\
2973-\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
2974-\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
2975-\x00\x00\x04\x8c\x49\x44\x41\x54\x78\xda\xbd\x97\xfb\x53\xd4\x65\
2976-\x14\xc6\xf9\x17\xfa\x4b\x6c\xa6\xdf\xba\x89\x9a\x19\x65\x99\x95\
2977-\x20\x97\xbd\x21\xec\x02\x8a\x28\x77\x58\x60\xc1\x26\xe9\x66\x4a\
2978-\xa5\x21\x2c\xe2\x28\xb0\x0b\xbb\x2c\x2c\x94\x94\x46\x54\xde\xa6\
2979-\x71\x72\xaa\xd1\x9a\x32\xa6\xa6\x7b\x43\x22\x0a\x22\x5e\xb6\xd3\
2980-\x7b\x5e\xe6\xd9\xf7\xec\xee\x77\x1b\x7e\x92\x99\xcf\x9c\x73\x9e\
2981-\xf7\x5c\x1e\x96\x1f\x80\x0c\xfe\x7a\xe0\xa1\xba\x07\x15\xdd\x8a\
2982-\xa9\xfb\x44\x37\xdf\x94\xc7\xa7\x8a\x9a\x22\x33\x7b\xbb\xce\xc4\
2983-\xf6\xf5\x9e\xa7\xd7\x7b\xce\x5a\x82\xb7\xe4\xf8\xe6\x11\xcc\x40\
2984-\x4b\x3f\xd7\xde\x7d\x36\xc6\xb7\xf8\xa6\x36\xc1\x6e\x0a\x1b\x87\
2985-\x67\xf6\x1c\xfc\x8c\x2a\x5f\x3d\x49\x1e\xdf\xfb\xe4\x6e\x19\x37\
2986-\xf8\xc6\xa9\xb4\xed\x04\xe0\x5a\x53\xd2\xfa\x01\x7a\x64\x2f\x47\
2987-\xdd\x27\x75\x68\x65\x8a\x8a\xf6\x09\x7a\xb9\xf3\x73\xe2\x9b\x7c\
2988-\x3b\x83\x9d\x78\xdf\xfe\x24\x56\xbe\x77\x82\x3c\x6a\x69\x51\xcb\
2989-\x18\x62\x0a\x9e\xb6\x84\x77\xe4\x8c\xec\x91\xd1\x32\xdf\xd9\xfe\
2990-\x21\x79\x0f\x4c\xc6\xf8\xf6\xb2\x81\x8e\x49\x72\xb7\x8e\xd3\xb6\
2991-\xe6\x28\x15\x36\x8d\x6a\x8a\x7d\xe3\x32\x4f\x5b\xf3\x0c\x6a\x47\
2992-\x7d\x98\x36\x97\xf9\x29\xcb\xd5\x41\x1b\x1c\xfb\xe9\x29\xa7\x89\
2993-\xfc\x0e\xf8\x56\xf3\x3b\x53\x14\x37\x50\xbf\x7f\x92\x9c\x8d\x23\
2994-\x6a\xd9\x18\x88\xd7\x2e\xef\xe8\x72\x6c\x1c\xd5\x9a\xd6\x9b\xf4\
2995-\xbb\x06\x7d\xb6\xda\x10\xd9\xab\x7b\xe9\xe4\xe9\xcb\x34\x7f\x73\
2996-\x89\x92\xbe\x30\xaf\x4c\x47\x75\xac\xdb\x77\xca\x18\xd8\xad\x7e\
2997-\x2e\x85\xde\xa8\xfa\x0e\x22\x88\x20\x9d\x9e\xa2\xb9\xbd\x41\x9a\
2998-\x9b\xbf\x45\xf7\x62\xff\x5a\xc2\xbd\x12\x75\xd3\x18\x28\x7f\xe5\
2999-\x04\x39\x1a\x46\x34\x05\xb5\x61\xe4\x29\x1a\x72\xbb\x3a\xc8\x51\
3000-\x12\x9a\xb8\x48\xb7\xef\xc6\xd2\xc2\x33\x72\x87\xba\x69\x0c\x78\
3001-\x7c\x51\xdd\x90\x5f\x13\x52\x1f\xe5\x30\xf2\x15\xc1\xbd\xcc\xe5\
3002-\x2b\x7f\xd2\xe2\xd2\xbd\xb4\xa0\x0f\x33\xee\x96\xa8\x31\x50\xd4\
3003-\x14\x55\xae\x86\x35\xb9\x55\x43\x0c\x6a\x35\x10\x96\x1a\x62\x4a\
3004-\xef\xc2\xe2\xdd\x04\x3e\x3a\xf3\x23\x15\xb7\x8c\xe9\x37\x20\xe7\
3005-\x5c\x8d\x11\x63\x20\xaf\x7a\x90\xb2\x77\x07\x28\xb7\x7a\x08\xa4\
3006-\xab\x11\x53\xb8\xbe\x70\x27\x01\x47\x43\x58\xce\x6a\xb6\x56\x0d\
3007-\xc6\x73\x75\xd3\x18\xc8\xa9\x1c\xa4\x97\x2a\x06\xe2\x64\xef\x0a\
3008-\x92\xd4\x38\x67\x64\xbe\xa5\x22\x80\x5a\xc7\xd9\x1b\x77\x24\xe8\
3009-\x03\x16\x3b\x84\x81\x2d\xbb\x02\x04\x5e\x28\xef\x03\xa8\xd5\xb1\
3010-\x81\x84\x77\xe4\x99\xb9\x6f\x68\x56\xe7\xbc\x46\x33\xd7\x6e\x4b\
3011-\x58\xe3\x37\x60\xb5\xdf\x18\x78\x7e\xfb\x71\x02\x2f\xee\x1c\x88\
3012-\xb3\x79\x47\x1f\x34\xe4\xa8\x75\xfc\xeb\xea\xd2\x4a\xc1\x4e\xcc\
3013-\x2b\x03\xfd\xc6\xc0\x73\xa5\xc7\x48\xa1\x1e\xfb\xd4\xa1\x7e\xc4\
3014-\x04\x36\x95\x1d\xe7\x1e\xd4\x3a\xff\x7d\xe6\xd6\x8a\xf8\xf8\xfc\
3015-\x34\x66\xc4\x4e\x61\x60\x93\x72\xb5\xb1\xe4\x28\x71\x44\xce\xc4\
3016-\xf5\xb2\x63\x96\x6f\xbf\xfe\xbd\xb8\x22\x4e\x9d\x9b\x96\xfb\x80\
3017-\x31\xf0\xac\x72\xf6\xb4\xbb\x57\xc3\x39\x40\x8d\xb7\xe4\xfa\xe7\
3018-\x3f\x6e\xae\x88\x43\x81\x0b\x72\x1f\x72\x63\x60\xa3\xe7\x28\x31\
3019-\x59\x45\x3d\x1c\x01\x34\x19\xd3\xe6\xd3\xbf\x2d\x48\xfe\x6f\x1f\
3020-\x74\x63\xe0\xc9\x42\x3f\x31\x1b\xb6\xf5\x10\xf2\xac\xe2\x5e\xe4\
3021-\x96\x35\x34\xe8\x3f\xfc\x32\x2f\x51\x47\x8e\x24\xcf\xe0\x06\xe6\
3022-\x8c\x01\x16\x25\x4f\x38\xbb\x64\x8e\x88\x05\x96\xef\xdf\xfd\x74\
3023-\x43\xc2\x3a\x90\xbd\x32\x1a\x03\xeb\x5d\x7e\x5a\xef\xf4\xd3\x5a\
3024-\xfb\x61\x0d\xd7\x0c\x72\x51\x5b\x46\xe6\xd2\xf4\x75\x89\xd5\x3c\
3025-\x34\x60\x0c\xac\x73\x74\x11\x93\x59\xf0\x9e\x8e\x6b\x0a\x3a\x51\
3026-\xa7\x65\x9d\xdd\xf4\x33\x5f\x5f\x99\x93\xe0\x0d\x7d\xa8\x01\xd7\
3027-\xd2\xc0\x61\x7a\x3c\xef\x20\xad\xb1\x75\x72\x94\x40\xd7\xc8\x9e\
3028-\xcc\xfc\x43\x1a\xd4\x17\xbf\x9f\x93\xa0\x37\xed\x0e\x75\xd3\x18\
3029-\x58\x6b\x7b\x97\x1e\xc9\x7e\x8b\x1e\x56\x3c\x96\xdb\xa1\x79\x34\
3030-\xe7\x80\x8e\xd0\x00\x34\x44\xe4\x17\xbe\xbd\x26\x91\xbd\x96\x33\
3031-\xab\xf3\x3a\x8c\x81\x12\xdf\x08\x39\xeb\x87\xc8\x51\x37\xc8\xe8\
3032-\xbc\xb0\x31\x2c\x35\x5d\x23\xba\x94\x8e\x1a\x7c\x71\x69\x56\xa2\
3033-\x7a\x42\x5a\x77\x35\x84\xb8\x37\xa5\xbf\xb8\x29\x9c\x68\xc0\x5e\
3034-\x1b\x04\x3c\xc4\xa4\x68\x88\x6c\x0c\x35\x38\xf7\xcd\x55\x09\xf7\
3035-\xa0\xdf\x72\x9f\x5b\x1a\x70\x37\x87\xa9\xa0\x26\x40\x0e\x35\x04\
3036-\xd8\x25\x34\x8e\xf6\xe5\x5a\x63\x53\x0b\xa0\xe3\xed\xf4\x57\xff\
3037-\x48\xe4\x2c\xf2\x84\x9a\x6f\x9a\x3f\xc9\xd8\x40\xf5\x00\xe5\x57\
3038-\x25\xa0\x9c\x0e\x26\xd4\xb6\xea\xa0\xd6\xa0\xcb\xf8\xe9\x97\x33\
3039-\x92\xe4\x59\xb5\x3f\x00\x4d\x7f\x73\xa5\xbe\x88\xf8\x04\xbc\xe1\
3040-\x18\x7f\x2c\x79\x7c\x44\x35\x59\xc1\x6f\xc0\x56\x93\x5c\x07\x13\
3041-\xea\x7c\x75\x4c\xcc\x20\x07\xca\x40\x90\x3c\xcd\xc3\x31\x18\xe8\
3042-\x7e\xc6\xed\x9f\x2d\x6d\x1b\x25\x67\xc3\x10\xe5\xab\x65\xcc\xd6\
3043-\xca\x7e\xa0\x86\x02\x3a\xe2\x2d\x57\x2d\x41\x2d\xfa\x64\x8f\xcc\
3044-\xc5\x5b\x40\xdf\xe0\x5b\x7c\x13\xff\x9a\xad\xd2\xbf\x11\x3d\xfe\
3045-\xd9\xe2\xe6\x50\x6c\xc7\x9e\x28\x95\xb6\x46\x88\x23\x80\x86\x68\
3046-\x45\x59\xdb\x88\xe9\x11\x71\xbb\x3a\x26\xb5\x12\x5f\x24\xc6\xb7\
3047-\xf8\x26\xdf\xce\x20\x22\x98\xb8\xdf\xff\x9e\xaf\xe2\xdb\xff\x01\
3048-\x95\x7c\xeb\x7f\xbd\xd5\xeb\x77\x00\x00\x00\x00\x49\x45\x4e\x44\
3049-\xae\x42\x60\x82\
3050+\x00\x00\x02\x2a\
3051+\x89\
3052+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
3053+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
3054+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
3055+\x01\x00\x9a\x9c\x18\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
3056+\x74\x77\x61\x72\x65\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\
3057+\x65\x52\x65\x61\x64\x79\x71\xc9\x65\x3c\x00\x00\x01\xb7\x49\x44\
3058+\x41\x54\x78\xda\x94\x93\x4d\x2f\x03\x41\x18\xc7\x9f\x5d\xd3\xdd\
3059+\xb6\x0e\x5e\x83\x34\xc1\x88\x38\x38\x78\x8d\x44\x38\x38\x78\xb9\
3060+\x8a\xc6\x55\x22\x1c\x5c\xc4\x37\x70\xf3\x2d\x48\x70\x90\x38\x96\
3061+\xc4\x89\xe8\xcd\x57\xd0\x92\x58\x2b\x95\x4a\x43\xd5\x5b\xed\xdb\
3062+\xec\xac\x99\xd9\x54\xad\x96\xf0\x5c\x9e\xcc\xf3\x3c\xff\xdf\xfc\
3063+\x67\x76\x47\x1a\x9b\x5b\x1f\x0c\x87\xa3\x09\x35\x1c\xc1\x8a\xa2\
3064+\xc2\x5f\xc2\xb6\x2d\xb0\x4c\x43\x37\xcd\xf7\x38\x52\xd5\x48\x62\
3065+\x76\x7a\x1c\x77\xb7\xb7\x82\x24\x49\x7f\x02\x78\x9e\x07\x57\x99\
3066+\x1c\x3e\x3c\x39\x4b\x20\x14\x52\x70\xbe\x60\x41\x4a\x4f\xc3\x7f\
3067+\xa2\xa5\x2e\x0a\x21\x25\x8c\x11\x80\x0c\xe9\x4c\x1e\x5c\xea\xfd\
3068+\x0b\xf0\xf8\x62\x30\x27\x00\x88\x50\x0a\x96\xe3\xfe\x38\x38\x39\
3069+\xda\x05\xcb\xf3\xc3\x50\x1b\x51\xc4\x3a\xbe\xb6\x2f\x32\x61\x12\
3070+\xae\x45\xae\xeb\xb2\x05\xfd\x11\xf0\x55\xec\x0b\xcb\xb3\x5c\xcb\
3071+\x00\xf4\x57\x40\x49\xbc\xba\x71\x04\xda\x6d\x21\xd0\xe3\x5a\xe1\
3072+\x80\x1f\x86\xdb\xf9\x2d\x2e\x6f\x1e\x02\x6b\x24\xcb\x65\x07\x8a\
3073+\x2c\x81\xe9\x04\x01\xc9\xed\xa5\xcf\x4f\xc6\xe3\x78\x6b\xd1\xbf\
3074+\x93\xe5\x1d\x91\xa3\xa8\xa6\xe4\x80\x82\x2a\x68\x41\x80\xe3\x38\
3075+\x55\x9d\x94\xe6\x4a\x1a\x44\x98\x0d\x85\xd1\xec\x6f\x80\x89\xa5\
3076+\x5d\x91\x4f\x37\x17\x44\x9e\x5a\xd9\x0b\xf4\xb9\x86\x6b\x91\x69\
3077+\x19\x0c\x6b\x40\xb1\xf8\x52\x75\x47\x87\xf8\x4e\x2a\xfa\xf5\x21\
3078+\xe0\x5a\xd4\xd8\xde\x09\xb8\x7f\x08\x0a\xcd\x4f\xd5\x01\x0e\x11\
3079+\xb9\x63\x60\x24\x50\xc7\xb1\x7a\xb8\x36\x18\x80\x58\x36\xb8\x66\
3080+\x11\xa2\x1e\x85\x7c\xd1\xaa\x00\x10\xe2\x03\x8c\x37\xe3\xb3\xd6\
3081+\x54\xab\x0a\x0d\xe5\x77\x50\x03\x54\xbf\xbb\xcf\xe1\xbe\x58\x8c\
3082+\xfd\xdb\x0d\x15\x00\x55\xf5\x5f\xe8\x4c\x6f\x5b\xd9\x15\x7b\x8d\
3083+\x5a\x36\x0b\x12\xb1\x75\x44\x8d\xe7\xf8\x85\xa6\x25\x52\x9a\x8e\
3084+\xab\x1d\xa1\xa7\x35\x27\xf2\x41\xf2\x3c\x50\x97\xd9\xc6\x9e\xf9\
3085+\x1a\xff\x10\x60\x00\x7c\x3b\xda\x5b\x6c\x8a\x7e\xa0\x00\x00\x00\
3086+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
3087+\x00\x00\x02\x2a\
3088+\x89\
3089+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
3090+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
3091+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
3092+\x01\x00\x9a\x9c\x18\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
3093+\x74\x77\x61\x72\x65\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\
3094+\x65\x52\x65\x61\x64\x79\x71\xc9\x65\x3c\x00\x00\x01\xb7\x49\x44\
3095+\x41\x54\x78\xda\x94\x93\x4d\x2f\x03\x41\x18\xc7\x9f\x5d\xd3\xdd\
3096+\xb6\x0e\x5e\x83\x34\xc1\x88\x38\x38\x78\x8d\x44\x38\x38\x78\xb9\
3097+\x8a\xc6\x55\x22\x1c\x5c\xc4\x37\x70\xf3\x2d\x48\x70\x90\x38\x96\
3098+\xc4\x89\xe8\xcd\x57\xd0\x92\x58\x2b\x95\x4a\x43\xd5\x5b\xed\xdb\
3099+\xec\xac\x99\xd9\x54\xad\x96\xf0\x5c\x9e\xcc\xf3\x3c\xff\xdf\xfc\
3100+\x67\x76\x47\x1a\x9b\x5b\x1f\x0c\x87\xa3\x09\x35\x1c\xc1\x8a\xa2\
3101+\xc2\x5f\xc2\xb6\x2d\xb0\x4c\x43\x37\xcd\xf7\x38\x52\xd5\x48\x62\
3102+\x76\x7a\x1c\x77\xb7\xb7\x82\x24\x49\x7f\x02\x78\x9e\x07\x57\x99\
3103+\x1c\x3e\x3c\x39\x4b\x20\x14\x52\x70\xbe\x60\x41\x4a\x4f\xc3\x7f\
3104+\xa2\xa5\x2e\x0a\x21\x25\x8c\x11\x80\x0c\xe9\x4c\x1e\x5c\xea\xfd\
3105+\x0b\xf0\xf8\x62\x30\x27\x00\x88\x50\x0a\x96\xe3\xfe\x38\x38\x39\
3106+\xda\x05\xcb\xf3\xc3\x50\x1b\x51\xc4\x3a\xbe\xb6\x2f\x32\x61\x12\
3107+\xae\x45\xae\xeb\xb2\x05\xfd\x11\xf0\x55\xec\x0b\xcb\xb3\x5c\xcb\
3108+\x00\xf4\x57\x40\x49\xbc\xba\x71\x04\xda\x6d\x21\xd0\xe3\x5a\xe1\
3109+\x80\x1f\x86\xdb\xf9\x2d\x2e\x6f\x1e\x02\x6b\x24\xcb\x65\x07\x8a\
3110+\x2c\x81\xe9\x04\x01\xc9\xed\xa5\xcf\x4f\xc6\xe3\x78\x6b\xd1\xbf\
3111+\x93\xe5\x1d\x91\xa3\xa8\xa6\xe4\x80\x82\x2a\x68\x41\x80\xe3\x38\
3112+\x55\x9d\x94\xe6\x4a\x1a\x44\x98\x0d\x85\xd1\xec\x6f\x80\x89\xa5\
3113+\x5d\x91\x4f\x37\x17\x44\x9e\x5a\xd9\x0b\xf4\xb9\x86\x6b\x91\x69\
3114+\x19\x0c\x6b\x40\xb1\xf8\x52\x75\x47\x87\xf8\x4e\x2a\xfa\xf5\x21\
3115+\xe0\x5a\xd4\xd8\xde\x09\xb8\x7f\x08\x0a\xcd\x4f\xd5\x01\x0e\x11\
3116+\xb9\x63\x60\x24\x50\xc7\xb1\x7a\xb8\x36\x18\x80\x58\x36\xb8\x66\
3117+\x11\xa2\x1e\x85\x7c\xd1\xaa\x00\x10\xe2\x03\x8c\x37\xe3\xb3\xd6\
3118+\x54\xab\x0a\x0d\xe5\x77\x50\x03\x54\xbf\xbb\xcf\xe1\xbe\x58\x8c\
3119+\xfd\xdb\x0d\x15\x00\x55\xf5\x5f\xe8\x4c\x6f\x5b\xd9\x15\x7b\x8d\
3120+\x5a\x36\x0b\x12\xb1\x75\x44\x8d\xe7\xf8\x85\xa6\x25\x52\x9a\x8e\
3121+\xab\x1d\xa1\xa7\x35\x27\xf2\x41\xf2\x3c\x50\x97\xd9\xc6\x9e\xf9\
3122+\x1a\xff\x10\x60\x00\x7c\x3b\xda\x5b\x6c\x8a\x7e\xa0\x00\x00\x00\
3123+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
3124 \x00\x00\x01\x68\
3125 \x89\
3126 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
3127@@ -23468,21 +23478,23 @@
3128 "
3129
3130 qt_resource_struct = "\
3131-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0e\x00\x00\x00\x01\
3132-\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x73\
3133+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\x01\
3134+\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xb2\xec\
3135 \x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xe5\
3136-\x00\x00\x01\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x17\xfe\
3137+\x00\x00\x01\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x85\
3138 \x00\x00\x00\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x09\xc8\
3139-\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x05\xaa\x1e\
3140+\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x05\xab\x04\
3141 \x00\x00\x00\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x04\x92\
3142 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
3143 \x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x11\xb2\
3144+\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x14\x06\
3145 \x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x01\xd5\
3146 \x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x83\
3147-\x00\x00\x01\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x16\xd3\
3148-\x00\x00\x01\xd2\x00\x00\x00\x00\x00\x01\x00\x05\xad\xaa\
3149-\x00\x00\x01\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x2a\xb3\
3150-\x00\x00\x01\x96\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x26\
3151+\x00\x00\x01\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x16\x5a\
3152+\x00\x00\x01\xd2\x00\x00\x00\x00\x00\x01\x00\x05\xb0\xbe\
3153+\x00\x00\x01\xd2\x00\x00\x00\x00\x00\x01\x00\x05\xae\x90\
3154+\x00\x00\x01\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x99\
3155+\x00\x00\x01\x96\x00\x00\x00\x00\x00\x01\x00\x00\x2e\x0c\
3156 "
3157
3158 def qInitResources():
3159
3160=== modified file 'ubuntuone/controlpanel/gui/qt/ui/loadingoverlay_ui.py'
3161--- ubuntuone/controlpanel/gui/qt/ui/loadingoverlay_ui.py 2011-08-12 19:12:08 +0000
3162+++ ubuntuone/controlpanel/gui/qt/ui/loadingoverlay_ui.py 2011-08-25 19:40:18 +0000
3163@@ -2,7 +2,7 @@
3164
3165 # Form implementation generated from reading ui file 'data/qt/loadingoverlay.ui'
3166 #
3167-# Created: Fri Aug 12 15:07:18 2011
3168+# Created: Thu Aug 25 15:26:50 2011
3169 # by: PyQt4 UI code generator 4.8.3
3170 #
3171 # WARNING! All changes made in this file will be lost!
3172
3173=== modified file 'ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py'
3174--- ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py 2011-08-12 19:12:08 +0000
3175+++ ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py 2011-08-25 19:40:18 +0000
3176@@ -2,7 +2,7 @@
3177
3178 # Form implementation generated from reading ui file 'data/qt/mainwindow.ui'
3179 #
3180-# Created: Fri Aug 12 15:07:18 2011
3181+# Created: Thu Aug 25 15:26:50 2011
3182 # by: PyQt4 UI code generator 4.8.3
3183 #
3184 # WARNING! All changes made in this file will be lost!
3185@@ -48,7 +48,7 @@
3186 QtCore.QMetaObject.connectSlotsByName(MainWindow)
3187
3188 def retranslateUi(self, MainWindow):
3189- MainWindow.setWindowTitle(_('Ubuntu One Control Panel'))
3190+ MainWindow.setWindowTitle(_('Ubuntu One'))
3191
3192 from ubuntuone.controlpanel.gui.qt.controlpanel import ControlPanel
3193 import images_rc
3194
3195=== modified file 'ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py'
3196--- ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py 2011-08-12 19:12:08 +0000
3197+++ ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py 2011-08-25 19:40:18 +0000
3198@@ -2,7 +2,7 @@
3199
3200 # Form implementation generated from reading ui file 'data/qt/preferences.ui'
3201 #
3202-# Created: Fri Aug 12 15:07:18 2011
3203+# Created: Thu Aug 25 15:26:50 2011
3204 # by: PyQt4 UI code generator 4.8.3
3205 #
3206 # WARNING! All changes made in this file will be lost!
3207@@ -90,6 +90,7 @@
3208 self.apply_changes_button.setObjectName(_fromUtf8("apply_changes_button"))
3209 self.horizontalLayout.addWidget(self.apply_changes_button)
3210 self.restore_defaults_button = QtGui.QPushButton(Form)
3211+ self.restore_defaults_button.setProperty(_fromUtf8("secondary"), True)
3212 self.restore_defaults_button.setObjectName(_fromUtf8("restore_defaults_button"))
3213 self.horizontalLayout.addWidget(self.restore_defaults_button)
3214 self.verticalLayout.addLayout(self.horizontalLayout)
3215@@ -98,7 +99,7 @@
3216 QtCore.QMetaObject.connectSlotsByName(Form)
3217
3218 def retranslateUi(self, Form):
3219- self.bandwidth_settings.setTitle(_('Bandwidth settings'))
3220+ self.bandwidth_settings.setTitle(_('Bandwidth Settings'))
3221 self.limit_uploads_checkbox.setText(_('Limit upload speed to'))
3222 self.kbps_label_1.setText(_('Kilobits per second'))
3223 self.limit_downloads_checkbox.setText(_('Limit download speed to'))
3224
3225=== modified file 'ubuntuone/controlpanel/login_client.py'
3226--- ubuntuone/controlpanel/login_client.py 2011-07-22 21:26:48 +0000
3227+++ ubuntuone/controlpanel/login_client.py 2011-08-25 19:40:18 +0000
3228@@ -18,14 +18,15 @@
3229
3230 """Client to access Ubuntu One credentials."""
3231
3232+# pylint: disable=E0611, F0401
3233+from ubuntuone.platform.credentials import CredentialsManagementTool
3234+# pylint: enable=E0611, F0401
3235+
3236
3237 def get_sso_proxy():
3238 """Return a login client."""
3239- # No name 'credentials' in module 'ubuntuone.platform'
3240- # Reimport 'credentials' (imported line 22)
3241- # pylint: disable=E0611,W0404
3242- from ubuntuone.platform import credentials
3243- return credentials.CredentialsManagementTool()
3244+ result = CredentialsManagementTool()
3245+ return result
3246
3247
3248 def get_credentials():
3249@@ -38,3 +39,15 @@
3250 """Clear the credentials for Ubuntu One."""
3251 proxy = get_sso_proxy()
3252 return proxy.clear_credentials()
3253+
3254+
3255+def login(*args, **kwargs):
3256+ """Get the credentials for Ubuntu One offering the user to login."""
3257+ proxy = get_sso_proxy()
3258+ return proxy.login(*args, **kwargs)
3259+
3260+
3261+def register(*args, **kwargs):
3262+ """Get the credentials for Ubuntu One offering the user to register."""
3263+ proxy = get_sso_proxy()
3264+ return proxy.register(*args, **kwargs)
3265
3266=== modified file 'ubuntuone/controlpanel/sd_client/__init__.py'
3267--- ubuntuone/controlpanel/sd_client/__init__.py 2011-07-22 21:26:48 +0000
3268+++ ubuntuone/controlpanel/sd_client/__init__.py 2011-08-25 19:40:18 +0000
3269@@ -21,221 +21,186 @@
3270 import sys
3271 import warnings
3272
3273+# pylint: disable=E0611
3274+from ubuntuone.platform import tools
3275+# pylint: enable=E0611
3276 from ubuntuone.controlpanel.logger import setup_logging
3277
3278-# pylint: disable=W0611
3279-
3280
3281 logger = setup_logging('sd_client')
3282
3283
3284-def get_syncdaemon_tool():
3285- """Get a proxy for the SyncDaemonTool."""
3286- # Reimport 'SyncDaemonTool'
3287- # No name 'tools' in module 'ubuntuone.platform'
3288- # pylint: disable=W0404, E0611
3289- from ubuntuone.platform import tools
3290- proxy = tools.SyncDaemonTool()
3291- return proxy
3292-
3293-
3294-def get_throttling_limits():
3295- """Get the speed limits from the syncdaemon."""
3296- return get_syncdaemon_tool().get_throttling_limits()
3297-
3298-
3299-def set_throttling_limits(limits):
3300- """Set the speed limits on the syncdaemon."""
3301- dload = int(limits["download"])
3302- uload = int(limits["upload"])
3303- return get_syncdaemon_tool().set_throttling_limits(dload, uload)
3304-
3305-
3306-def bandwidth_throttling_enabled():
3307- """Get the state of throttling in the syncdaemon."""
3308- return get_syncdaemon_tool().is_throttling_enabled()
3309-
3310-
3311-def enable_bandwidth_throttling():
3312- """Enable the speed limits in the syncdaemon."""
3313- return get_syncdaemon_tool().enable_throttling(True)
3314-
3315-
3316-def disable_bandwidth_throttling():
3317- """Disable the speed limits in the syncdaemon."""
3318- return get_syncdaemon_tool().enable_throttling(False)
3319-
3320-
3321-def autoconnect_enabled():
3322- """Get the state of autoconnect in the syncdaemon."""
3323- return get_syncdaemon_tool().is_autoconnect_enabled()
3324-
3325-
3326-def enable_autoconnect():
3327- """Enable autoconnect in the syncdaemon."""
3328- return get_syncdaemon_tool().enable_autoconnect(True)
3329-
3330-
3331-def disable_autoconnect():
3332- """Disable autoconnect in the syncdaemon."""
3333- return get_syncdaemon_tool().enable_autoconnect(False)
3334-
3335-
3336-def show_all_notifications_enabled():
3337- """Get the state of show_all_notifications in the syncdaemon."""
3338- return get_syncdaemon_tool().is_show_all_notifications_enabled()
3339-
3340-
3341-def enable_show_all_notifications():
3342- """Enable show_all_notifications in the syncdaemon."""
3343- return get_syncdaemon_tool().enable_show_all_notifications(True)
3344-
3345-
3346-def disable_show_all_notifications():
3347- """Disable show_all_notifications in the syncdaemon."""
3348- return get_syncdaemon_tool().enable_show_all_notifications(False)
3349-
3350-
3351-def share_autosubscribe_enabled():
3352- """Get the state of share_autosubscribe in the syncdaemon."""
3353- return get_syncdaemon_tool().is_share_autosubscribe_enabled()
3354-
3355-
3356-def enable_share_autosubscribe():
3357- """Enable share_autosubscribe in the syncdaemon."""
3358- return get_syncdaemon_tool().enable_share_autosubscribe(True)
3359-
3360-
3361-def disable_share_autosubscribe():
3362- """Disable share_autosubscribe in the syncdaemon."""
3363- return get_syncdaemon_tool().enable_share_autosubscribe(False)
3364-
3365-
3366-def udf_autosubscribe_enabled():
3367- """Get the state of udf_autosubscribe in the syncdaemon."""
3368- return get_syncdaemon_tool().is_udf_autosubscribe_enabled()
3369-
3370-
3371-def enable_udf_autosubscribe():
3372- """Enable udf_autosubscribe in the syncdaemon."""
3373- return get_syncdaemon_tool().enable_udf_autosubscribe(True)
3374-
3375-
3376-def disable_udf_autosubscribe():
3377- """Disable udf_autosubscribe in the syncdaemon."""
3378- return get_syncdaemon_tool().enable_udf_autosubscribe(False)
3379-
3380-
3381-def get_root_dir():
3382- """Retrieve the root information from syncdaemon."""
3383- return get_syncdaemon_tool().get_root_dir()
3384-
3385-
3386-def get_shares_dir():
3387- """Retrieve the shares information from syncdaemon."""
3388- return get_syncdaemon_tool().get_shares_dir()
3389-
3390-
3391-def get_shares_dir_link():
3392- """Retrieve the shares information from syncdaemon."""
3393- return get_syncdaemon_tool().get_shares_dir_link()
3394-
3395-
3396-def get_folders():
3397- """Retrieve the folders information from syncdaemon."""
3398- return get_syncdaemon_tool().get_folders()
3399-
3400-
3401-def create_folder(path):
3402- """Create a new folder through syncdaemon."""
3403- return get_syncdaemon_tool().create_folder(path)
3404-
3405-
3406-def subscribe_folder(folder_id):
3407- """Subscribe to 'folder_id'."""
3408- return get_syncdaemon_tool().subscribe_folder(folder_id)
3409-
3410-
3411-def unsubscribe_folder(folder_id):
3412- """Unsubscribe 'folder_id'."""
3413- return get_syncdaemon_tool().unsubscribe_folder(folder_id)
3414-
3415-
3416-def get_shares():
3417- """Retrieve the shares information from syncdaemon."""
3418- return get_syncdaemon_tool().get_shares()
3419-
3420-
3421-def subscribe_share(share_id):
3422- """Subscribe to 'share_id'."""
3423- return get_syncdaemon_tool().subscribe_share(share_id)
3424-
3425-
3426-def unsubscribe_share(share_id):
3427- """Unsubscribe 'share_id'."""
3428- return get_syncdaemon_tool().unsubscribe_share(share_id)
3429-
3430-
3431-def get_current_status():
3432- """Retrieve the current status from syncdaemon."""
3433- return get_syncdaemon_tool().get_status()
3434-
3435-
3436-def file_sync_enabled():
3437- """Get if file sync service is enabled."""
3438- return get_syncdaemon_tool().is_files_sync_enabled()
3439-
3440-
3441-def enable_file_sync():
3442- """Enable the file sync service."""
3443- return get_syncdaemon_tool().enable_files_sync(True)
3444-
3445-
3446-def disable_file_sync():
3447- """Enable the file sync service."""
3448- return get_syncdaemon_tool().enable_files_sync(False)
3449-
3450-
3451-def files_sync_enabled():
3452- """Get if file sync service is enabled."""
3453- warnings.warn('use file_sync_enabled instead', DeprecationWarning)
3454- return file_sync_enabled()
3455-
3456-
3457-def set_files_sync_enabled(enabled):
3458- """Set the file sync service to be 'enabled'."""
3459- warnings.warn('use {enable/disable}_file_sync instead', DeprecationWarning)
3460- if enabled:
3461- return enable_file_sync()
3462- else:
3463- return disable_file_sync()
3464-
3465-
3466-def connect_file_sync():
3467- """Connect the file sync service."""
3468- return get_syncdaemon_tool().connect()
3469-
3470-
3471-def disconnect_file_sync():
3472- """Disconnect the file sync service."""
3473- return get_syncdaemon_tool().disconnect()
3474-
3475-
3476-def start_file_sync():
3477- """Start the file sync service."""
3478- return get_syncdaemon_tool().start()
3479-
3480-
3481-def stop_file_sync():
3482- """Stop the file sync service."""
3483- return get_syncdaemon_tool().quit()
3484-
3485-
3486-if sys.platform.startswith("linux"):
3487- from ubuntuone.controlpanel.sd_client.linux \
3488- import set_status_changed_handler
3489-else:
3490-
3491- def set_status_changed_handler(handler):
3492+class SyncDaemonClient(object):
3493+ """An abstraction to SyncDaemonTool."""
3494+
3495+ def __init__(self):
3496+ """Get a proxy for the SyncDaemonTool."""
3497+ self.status_changed_handler = None
3498+ self.proxy = tools.SyncDaemonTool()
3499+
3500+ def get_throttling_limits(self):
3501+ """Get the speed limits from the syncdaemon."""
3502+ return self.proxy.get_throttling_limits()
3503+
3504+ def set_throttling_limits(self, limits):
3505+ """Set the speed limits on the syncdaemon."""
3506+ dload = int(limits["download"])
3507+ uload = int(limits["upload"])
3508+ return self.proxy.set_throttling_limits(dload, uload)
3509+
3510+ def bandwidth_throttling_enabled(self):
3511+ """Get the state of throttling in the syncdaemon."""
3512+ return self.proxy.is_throttling_enabled()
3513+
3514+ def enable_bandwidth_throttling(self):
3515+ """Enable the speed limits in the syncdaemon."""
3516+ return self.proxy.enable_throttling(True)
3517+
3518+ def disable_bandwidth_throttling(self):
3519+ """Disable the speed limits in the syncdaemon."""
3520+ return self.proxy.enable_throttling(False)
3521+
3522+ def autoconnect_enabled(self):
3523+ """Get the state of autoconnect in the syncdaemon."""
3524+ return self.proxy.is_autoconnect_enabled()
3525+
3526+ def enable_autoconnect(self):
3527+ """Enable autoconnect in the syncdaemon."""
3528+ return self.proxy.enable_autoconnect(True)
3529+
3530+ def disable_autoconnect(self):
3531+ """Disable autoconnect in the syncdaemon."""
3532+ return self.proxy.enable_autoconnect(False)
3533+
3534+ def show_all_notifications_enabled(self):
3535+ """Get the state of show_all_notifications in the syncdaemon."""
3536+ return self.proxy.is_show_all_notifications_enabled()
3537+
3538+ def enable_show_all_notifications(self):
3539+ """Enable show_all_notifications in the syncdaemon."""
3540+ return self.proxy.enable_show_all_notifications(True)
3541+
3542+ def disable_show_all_notifications(self):
3543+ """Disable show_all_notifications in the syncdaemon."""
3544+ return self.proxy.enable_show_all_notifications(False)
3545+
3546+ def share_autosubscribe_enabled(self):
3547+ """Get the state of share_autosubscribe in the syncdaemon."""
3548+ return self.proxy.is_share_autosubscribe_enabled()
3549+
3550+ def enable_share_autosubscribe(self):
3551+ """Enable share_autosubscribe in the syncdaemon."""
3552+ return self.proxy.enable_share_autosubscribe(True)
3553+
3554+ def disable_share_autosubscribe(self):
3555+ """Disable share_autosubscribe in the syncdaemon."""
3556+ return self.proxy.enable_share_autosubscribe(False)
3557+
3558+ def udf_autosubscribe_enabled(self):
3559+ """Get the state of udf_autosubscribe in the syncdaemon."""
3560+ return self.proxy.is_udf_autosubscribe_enabled()
3561+
3562+ def enable_udf_autosubscribe(self):
3563+ """Enable udf_autosubscribe in the syncdaemon."""
3564+ return self.proxy.enable_udf_autosubscribe(True)
3565+
3566+ def disable_udf_autosubscribe(self):
3567+ """Disable udf_autosubscribe in the syncdaemon."""
3568+ return self.proxy.enable_udf_autosubscribe(False)
3569+
3570+ def get_root_dir(self):
3571+ """Retrieve the root information from syncdaemon."""
3572+ return self.proxy.get_root_dir()
3573+
3574+ def get_shares_dir(self):
3575+ """Retrieve the shares information from syncdaemon."""
3576+ return self.proxy.get_shares_dir()
3577+
3578+ def get_shares_dir_link(self):
3579+ """Retrieve the shares information from syncdaemon."""
3580+ return self.proxy.get_shares_dir_link()
3581+
3582+ def get_folders(self):
3583+ """Retrieve the folders information from syncdaemon."""
3584+ return self.proxy.get_folders()
3585+
3586+ def create_folder(self, path):
3587+ """Create a new folder through syncdaemon."""
3588+ return self.proxy.create_folder(path)
3589+
3590+ def subscribe_folder(self, folder_id):
3591+ """Subscribe to 'folder_id'."""
3592+ return self.proxy.subscribe_folder(folder_id)
3593+
3594+ def unsubscribe_folder(self, folder_id):
3595+ """Unsubscribe 'folder_id'."""
3596+ return self.proxy.unsubscribe_folder(folder_id)
3597+
3598+ def get_shares(self):
3599+ """Retrieve the shares information from syncdaemon."""
3600+ return self.proxy.get_shares()
3601+
3602+ def subscribe_share(self, share_id):
3603+ """Subscribe to 'share_id'."""
3604+ return self.proxy.subscribe_share(share_id)
3605+
3606+ def unsubscribe_share(self, share_id):
3607+ """Unsubscribe 'share_id'."""
3608+ return self.proxy.unsubscribe_share(share_id)
3609+
3610+ def get_current_status(self):
3611+ """Retrieve the current status from syncdaemon."""
3612+ return self.proxy.get_status()
3613+
3614+ def file_sync_enabled(self):
3615+ """Get if file sync service is enabled."""
3616+ return self.proxy.is_files_sync_enabled()
3617+
3618+ def enable_file_sync(self):
3619+ """Enable the file sync service."""
3620+ return self.proxy.enable_files_sync(True)
3621+
3622+ def disable_file_sync(self):
3623+ """Enable the file sync service."""
3624+ return self.proxy.enable_files_sync(False)
3625+
3626+ def files_sync_enabled(self):
3627+ """Get if file sync service is enabled."""
3628+ warnings.warn('use file_sync_enabled instead', DeprecationWarning)
3629+ return self.file_sync_enabled()
3630+
3631+ def set_files_sync_enabled(self, enabled):
3632+ """Set the file sync service to be 'enabled'."""
3633+ warnings.warn('use {enable/disable}_file_sync instead',
3634+ DeprecationWarning)
3635+ if enabled:
3636+ return self.enable_file_sync()
3637+ else:
3638+ return self.disable_file_sync()
3639+
3640+ def connect_file_sync(self):
3641+ """Connect the file sync service."""
3642+ return self.proxy.connect()
3643+
3644+ def disconnect_file_sync(self):
3645+ """Disconnect the file sync service."""
3646+ return self.proxy.disconnect()
3647+
3648+ def start_file_sync(self):
3649+ """Start the file sync service."""
3650+ return self.proxy.start()
3651+
3652+ def stop_file_sync(self):
3653+ """Stop the file sync service."""
3654+ return self.proxy.quit()
3655+
3656+ def set_status_changed_handler(self, handler):
3657 """Set the status handler function."""
3658- return get_syncdaemon_tool().set_status_changed_handler(handler)
3659+ self.status_changed_handler = handler
3660+ if sys.platform.startswith("linux"):
3661+ # pylint: disable=W0404
3662+ from ubuntuone.controlpanel.sd_client import linux
3663+ result = linux.set_status_changed_handler(handler)
3664+ else:
3665+ result = self.proxy.set_status_changed_handler(handler)
3666+ return result
3667
3668=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
3669--- ubuntuone/controlpanel/tests/test_backend.py 2011-07-22 21:26:48 +0000
3670+++ ubuntuone/controlpanel/tests/test_backend.py 2011-08-25 19:40:18 +0000
3671@@ -66,18 +66,33 @@
3672 )
3673
3674
3675-# pylint: disable=E1101
3676-
3677-
3678-class MockWebClient(object):
3679+# pylint: disable=E1101, W0201, W0212
3680+
3681+
3682+class CallRecorder(object):
3683+ """A class that records every call clients made to it."""
3684+
3685+ def __init__(self):
3686+ self._called = defaultdict(int)
3687+
3688+ def __getattribute__(self, attr_name):
3689+ """Override to we can record calls to members."""
3690+ result = super(CallRecorder, self).__getattribute__(attr_name)
3691+ if attr_name != '_called':
3692+ called = super(CallRecorder, self).__getattribute__('_called')
3693+ called[attr_name] += 1
3694+ return result
3695+
3696+
3697+class MockWebClient(CallRecorder):
3698 """A mock webclient."""
3699
3700- failure = False
3701- results = {}
3702-
3703 def __init__(self, get_credentials):
3704 """Initialize this mock instance."""
3705+ super(MockWebClient, self).__init__()
3706 self.get_credentials = get_credentials
3707+ self.failure = False
3708+ self.results = {}
3709
3710 def call_api(self, method):
3711 """Get a given url from the webservice."""
3712@@ -90,42 +105,48 @@
3713 return defer.succeed(result)
3714
3715
3716-class MockLoginClient(object):
3717+class MockLoginClient(CallRecorder):
3718 """A mock login_client module."""
3719
3720- creds = TOKEN
3721+ def __init__(self):
3722+ """Initialize this mock instance."""
3723+ super(MockLoginClient, self).__init__()
3724+ self.creds = TOKEN
3725
3726- def get_credentials(self):
3727+ def find_credentials(self):
3728 """Return the mock credentials."""
3729 return defer.succeed(self.creds)
3730
3731 def clear_credentials(self):
3732 """Clear the mock credentials."""
3733- MockLoginClient.creds = None
3734+ self.creds = None
3735 return defer.succeed(None)
3736
3737
3738-class MockSDClient(object):
3739+class MockSDClient(CallRecorder):
3740 """A mock sd_client module."""
3741
3742- throttling = False
3743- show_all_notifications = True
3744- autoconnect = True
3745- udf_autosubscribe = False
3746- share_autosubscribe = False
3747- limits = {"download": -1, "upload": -1}
3748- file_sync = True
3749- status = {
3750- 'name': 'TEST', 'queues': 'GORGEOUS', 'connection': '',
3751- 'description': 'Some test state, nothing else.',
3752- 'is_error': '', 'is_connected': 'True', 'is_online': '',
3753- }
3754- status_changed_handler = None
3755- subscribed_folders = []
3756- subscribed_shares = []
3757- actions = []
3758- shares = []
3759- folders = []
3760+ def __init__(self):
3761+ """Initialize this mock instance."""
3762+ super(MockSDClient, self).__init__()
3763+ self.throttling = False
3764+ self.show_all_notifications = True
3765+ self.autoconnect = True
3766+ self.udf_autosubscribe = False
3767+ self.share_autosubscribe = False
3768+ self.limits = {"download": -1, "upload": -1}
3769+ self.file_sync = True
3770+ self.status = {
3771+ 'name': 'TEST', 'queues': 'GORGEOUS', 'connection': '',
3772+ 'description': 'Some test state, nothing else.',
3773+ 'is_error': '', 'is_connected': 'True', 'is_online': '',
3774+ }
3775+ self.status_changed_handler = None
3776+ self.subscribed_folders = []
3777+ self.subscribed_shares = []
3778+ self.actions = []
3779+ self.shares = []
3780+ self.folders = []
3781
3782 def get_throttling_limits(self):
3783 """Return the sample speed limits."""
3784@@ -198,27 +219,27 @@
3785
3786 def files_sync_enabled(self):
3787 """Get if file sync service is enabled."""
3788- return MockSDClient.file_sync
3789+ return self.file_sync
3790
3791 def set_files_sync_enabled(self, enabled):
3792 """Set the file sync service to be 'enabled'."""
3793- MockSDClient.file_sync = enabled
3794+ self.file_sync = enabled
3795
3796 def connect_file_sync(self):
3797 """Connect files service."""
3798- MockSDClient.actions.append('connect')
3799+ self.actions.append('connect')
3800
3801 def disconnect_file_sync(self):
3802 """Disconnect file_sync service."""
3803- MockSDClient.actions.append('disconnect')
3804+ self.actions.append('disconnect')
3805
3806 def start_file_sync(self):
3807 """Start the file_sync service."""
3808- MockSDClient.actions.append('start')
3809+ self.actions.append('start')
3810
3811 def stop_file_sync(self):
3812 """Stop the file_sync service."""
3813- MockSDClient.actions.append('stop')
3814+ self.actions.append('stop')
3815
3816 def get_root_dir(self):
3817 """Grab the root dir."""
3818@@ -234,31 +255,31 @@
3819
3820 def get_folders(self):
3821 """Grab list of folders."""
3822- return MockSDClient.folders
3823+ return self.folders
3824
3825 def subscribe_folder(self, volume_id):
3826 """Subcribe to 'volume_id'."""
3827- MockSDClient.subscribed_folders.append(volume_id)
3828+ self.subscribed_folders.append(volume_id)
3829
3830 def unsubscribe_folder(self, volume_id):
3831 """Unsubcribe from 'volume_id'."""
3832- MockSDClient.subscribed_folders.remove(volume_id)
3833+ self.subscribed_folders.remove(volume_id)
3834
3835 def create_folder(self, path):
3836 """Grab list of folders."""
3837- MockSDClient.folders.append(path)
3838+ self.folders.append(path)
3839
3840 def get_shares(self):
3841 """Grab list of shares."""
3842- return MockSDClient.shares
3843+ return self.shares
3844
3845 def subscribe_share(self, volume_id):
3846 """Subcribe to 'volume_id'."""
3847- MockSDClient.subscribed_shares.append(volume_id)
3848+ self.subscribed_shares.append(volume_id)
3849
3850 def unsubscribe_share(self, volume_id):
3851 """Unsubcribe from 'volume_id'."""
3852- MockSDClient.subscribed_shares.remove(volume_id)
3853+ self.subscribed_shares.remove(volume_id)
3854
3855 def get_current_status(self):
3856 """Grab syncdaemon status."""
3857@@ -273,7 +294,7 @@
3858 return SAMPLE_SHARED
3859
3860
3861-class MockReplicationClient(object):
3862+class MockReplicationClient(CallRecorder):
3863 """A mock replication_client module."""
3864
3865 BOOKMARKS = 'awesome'
3866@@ -311,21 +332,22 @@
3867 def setUp(self):
3868 super(BackendBasicTestCase, self).setUp()
3869 self.patch(backend, "web_client_factory", MockWebClient)
3870- self.patch(backend, "login_client", MockLoginClient())
3871- self.patch(backend, "sd_client", MockSDClient())
3872+ self.patch(backend, "CredentialsManagementTool", MockLoginClient)
3873+ self.patch(backend.sd_client, "SyncDaemonClient", MockSDClient)
3874 self.patch(backend, "replication_client", MockReplicationClient())
3875+
3876 self.local_token = DEVICE_TYPE_COMPUTER + TOKEN["token"]
3877 self.be = backend.ControlBackend()
3878+ self.be.wc.failure = False
3879
3880 self.memento = MementoHandler()
3881 backend.logger.addHandler(self.memento)
3882
3883- MockLoginClient.creds = TOKEN
3884- MockWebClient.failure = None
3885-
3886 def test_backend_creation(self):
3887 """The backend instance is successfully created."""
3888- self.assertEqual(self.be.wc.__class__, MockWebClient)
3889+ self.assertIsInstance(self.be.wc, MockWebClient)
3890+ self.assertIsInstance(self.be.login_client, MockLoginClient)
3891+ self.assertIsInstance(self.be.sd_client, MockSDClient)
3892
3893 @inlineCallbacks
3894 def test_get_token(self):
3895@@ -361,6 +383,22 @@
3896 self.be.shutdown()
3897 # nothing explodes
3898
3899+ @inlineCallbacks
3900+ def test_credentials_are_cached(self):
3901+ """The credentials are cached."""
3902+ creds1 = yield self.be.get_credentials()
3903+ creds2 = yield self.be.get_credentials()
3904+ self.assertEqual(creds1, creds2)
3905+ self.assertEqual(self.be.login_client._called['find_credentials'], 1)
3906+
3907+ def test_login_client_is_cached(self):
3908+ """The login_client instance is cached."""
3909+ self.assertIs(self.be.login_client, self.be.login_client)
3910+
3911+ def test_sd_client_is_cached(self):
3912+ """The sd_client instance is cached."""
3913+ self.assertIs(self.be.sd_client, self.be.sd_client)
3914+
3915
3916 class BackendAccountTestCase(BackendBasicTestCase):
3917 """Account tests for the backend."""
3918@@ -384,16 +422,16 @@
3919 @inlineCallbacks
3920 def test_account_info_fails(self):
3921 """The account_info method exercises its errback."""
3922- MockWebClient.failure = 404
3923+ self.be.wc.failure = 404
3924 yield self.assertFailure(self.be.account_info(),
3925 backend.WebClientError)
3926
3927 @inlineCallbacks
3928 def test_account_info_fails_with_unauthorized(self):
3929 """The account_info clears the credentials on unauthorized."""
3930- MockWebClient.failure = 401
3931+ self.be.wc.failure = 401
3932 d = defer.Deferred()
3933- self.patch(backend.login_client, 'clear_credentials',
3934+ self.patch(self.be.login_client, 'clear_credentials',
3935 lambda: d.callback('called'))
3936 yield self.assertFailure(self.be.account_info(),
3937 backend.UnauthorizedError)
3938@@ -423,7 +461,7 @@
3939 @inlineCallbacks
3940 def test_devices_info_with_webclient_error(self):
3941 """The devices_info returns local info if webclient error."""
3942- MockWebClient.failure = 404
3943+ self.be.wc.failure = 404
3944 result = yield self.be.devices_info()
3945
3946 self.assertEqual(result, [LOCAL_DEVICE])
3947@@ -433,9 +471,9 @@
3948 @inlineCallbacks
3949 def test_devices_info_fails_with_unauthorized(self):
3950 """The devices_info clears the credentials on unauthorized."""
3951- MockWebClient.failure = 401
3952+ self.be.wc.failure = 401
3953 d = defer.Deferred()
3954- self.patch(backend.login_client, 'clear_credentials',
3955+ self.patch(self.be.login_client, 'clear_credentials',
3956 lambda: d.callback('called'))
3957 yield self.assertFailure(self.be.devices_info(),
3958 backend.UnauthorizedError)
3959@@ -446,13 +484,14 @@
3960 """The devices_info returns device only info if files is disabled."""
3961 yield self.be.disable_files()
3962 status = yield self.be.file_sync_status()
3963- assert status['status'] == backend.FILE_SYNC_DISABLED, status
3964+ assert status['status'] == backend.FILE_SYNC_DISABLED
3965
3966 self.be.wc.results[DEVICES_API] = SAMPLE_DEVICES_JSON
3967 result = yield self.be.devices_info()
3968
3969- expected = EXPECTED_DEVICES_INFO[:]
3970- for device in expected:
3971+ expected = []
3972+ for device in EXPECTED_DEVICES_INFO:
3973+ device = device.copy()
3974 device.pop('limit_bandwidth', None)
3975 device.pop(backend.DOWNLOAD_KEY, None)
3976 device.pop(backend.UPLOAD_KEY, None)
3977@@ -461,6 +500,7 @@
3978 device.pop(backend.SHARE_AUTOSUBSCRIBE_KEY, None)
3979 device.pop(backend.UDF_AUTOSUBSCRIBE_KEY, None)
3980 device['configurable'] = False
3981+ expected.append(device)
3982 self.assertEqual(result, expected)
3983
3984 @inlineCallbacks
3985@@ -506,7 +546,7 @@
3986 @inlineCallbacks
3987 def test_device_names_info_with_webclient_error(self):
3988 """The device_names_info returns local info if webclient error."""
3989- MockWebClient.failure = 404
3990+ self.be.wc.failure = 404
3991 result = yield self.be.device_names_info()
3992
3993 device = LOCAL_DEVICE.copy()
3994@@ -525,9 +565,9 @@
3995 @inlineCallbacks
3996 def test_device_names_info_fails_with_unauthorized(self):
3997 """The device_names_info clears the credentials on unauthorized."""
3998- MockWebClient.failure = 401
3999+ self.be.wc.failure = 401
4000 d = defer.Deferred()
4001- self.patch(backend.login_client, 'clear_credentials',
4002+ self.patch(self.be.login_client, 'clear_credentials',
4003 lambda: d.callback('called'))
4004 yield self.assertFailure(self.be.device_names_info(),
4005 backend.UnauthorizedError)
4006@@ -565,7 +605,7 @@
4007 result = yield self.be.remove_device(device_id)
4008 self.assertEqual(result, device_id)
4009 # credentials were not cleared
4010- self.assertEqual(MockLoginClient.creds, TOKEN)
4011+ self.assertEqual(self.be.login_client.creds, TOKEN)
4012
4013 @inlineCallbacks
4014 def test_remove_device_clear_credentials_if_local_device(self):
4015@@ -574,21 +614,21 @@
4016 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
4017 yield self.be.remove_device(self.local_token)
4018 # credentials were cleared
4019- self.assertEqual(MockLoginClient.creds, None)
4020+ self.assertEqual(self.be.login_client.creds, None)
4021
4022 @inlineCallbacks
4023 def test_remove_device_fails(self):
4024 """The remove_device method fails as expected."""
4025- MockWebClient.failure = 404
4026+ self.be.wc.failure = 404
4027 yield self.assertFailure(self.be.remove_device(self.local_token),
4028 backend.WebClientError)
4029
4030 @inlineCallbacks
4031 def test_remove_device_fails_with_unauthorized(self):
4032 """The remove_device clears the credentials on unauthorized."""
4033- MockWebClient.failure = 401
4034+ self.be.wc.failure = 401
4035 d = defer.Deferred()
4036- self.patch(backend.login_client, 'clear_credentials',
4037+ self.patch(self.be.login_client, 'clear_credentials',
4038 lambda: d.callback('called'))
4039 yield self.assertFailure(self.be.remove_device(self.local_token),
4040 backend.UnauthorizedError)
4041@@ -598,6 +638,8 @@
4042 def test_remove_device_does_not_log_device_id(self):
4043 """The remove_device does not log the device_id."""
4044 device_id = DEVICE_TYPE_COMPUTER + TOKEN['token']
4045+ apiurl = DEVICE_REMOVE_API % ('computer', TOKEN['token'])
4046+ self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
4047 yield self.be.remove_device(device_id)
4048
4049 device_id_logged = all(device_id not in r.getMessage()
4050@@ -607,57 +649,57 @@
4051 @inlineCallbacks
4052 def test_change_show_all_notifications(self):
4053 """The device settings are updated."""
4054- backend.sd_client.show_all_notifications = False
4055+ self.be.sd_client.show_all_notifications = False
4056 yield self.be.change_device_settings(self.local_token,
4057 {backend.SHOW_ALL_NOTIFICATIONS_KEY: True})
4058- self.assertEqual(backend.sd_client.show_all_notifications, True)
4059+ self.assertEqual(self.be.sd_client.show_all_notifications, True)
4060 yield self.be.change_device_settings(self.local_token,
4061 {backend.SHOW_ALL_NOTIFICATIONS_KEY: False})
4062- self.assertEqual(backend.sd_client.show_all_notifications, False)
4063+ self.assertEqual(self.be.sd_client.show_all_notifications, False)
4064
4065 @inlineCallbacks
4066 def test_change_limit_bandwidth(self):
4067 """The device settings are updated."""
4068- backend.sd_client.throttling = False
4069+ self.be.sd_client.throttling = False
4070 yield self.be.change_device_settings(self.local_token,
4071 {backend.LIMIT_BW_KEY: True})
4072- self.assertEqual(backend.sd_client.throttling, True)
4073+ self.assertEqual(self.be.sd_client.throttling, True)
4074 yield self.be.change_device_settings(self.local_token,
4075 {backend.LIMIT_BW_KEY: False})
4076- self.assertEqual(backend.sd_client.throttling, False)
4077+ self.assertEqual(self.be.sd_client.throttling, False)
4078
4079 @inlineCallbacks
4080 def test_change_upload_speed_limit(self):
4081 """The device settings are updated."""
4082- backend.sd_client.limits = {"download": -1, "upload": -1}
4083+ self.be.sd_client.limits = {"download": -1, "upload": -1}
4084 yield self.be.change_device_settings(self.local_token,
4085 {backend.UPLOAD_KEY: 1111})
4086- self.assertEqual(backend.sd_client.limits["upload"], 1111)
4087- self.assertEqual(backend.sd_client.limits["download"], -1)
4088+ self.assertEqual(self.be.sd_client.limits["upload"], 1111)
4089+ self.assertEqual(self.be.sd_client.limits["download"], -1)
4090
4091 @inlineCallbacks
4092 def test_change_download_speed_limit(self):
4093 """The device settings are updated."""
4094- backend.sd_client.limits = {"download": -1, "upload": -1}
4095+ self.be.sd_client.limits = {"download": -1, "upload": -1}
4096 yield self.be.change_device_settings(self.local_token,
4097 {backend.DOWNLOAD_KEY: 99})
4098- self.assertEqual(backend.sd_client.limits["upload"], -1)
4099- self.assertEqual(backend.sd_client.limits["download"], 99)
4100+ self.assertEqual(self.be.sd_client.limits["upload"], -1)
4101+ self.assertEqual(self.be.sd_client.limits["download"], 99)
4102
4103 @inlineCallbacks
4104 def test_changing_settings_for_wrong_id_has_no_effect(self):
4105 """If the id is wrong, no settings are changed."""
4106- backend.sd_client.throttling = False
4107- backend.sd_client.limits = {"download": -1, "upload": -1}
4108+ self.be.sd_client.throttling = False
4109+ self.be.sd_client.limits = {"download": -1, "upload": -1}
4110 new_settings = {
4111 backend.DOWNLOAD_KEY: 99,
4112 backend.UPLOAD_KEY: 99,
4113 backend.LIMIT_BW_KEY: True,
4114 }
4115 yield self.be.change_device_settings("wrong token!", new_settings)
4116- self.assertEqual(backend.sd_client.throttling, False)
4117- self.assertEqual(backend.sd_client.limits["upload"], -1)
4118- self.assertEqual(backend.sd_client.limits["download"], -1)
4119+ self.assertEqual(self.be.sd_client.throttling, False)
4120+ self.assertEqual(self.be.sd_client.limits["upload"], -1)
4121+ self.assertEqual(self.be.sd_client.limits["download"], -1)
4122
4123 @inlineCallbacks
4124 def test_changing_settings_does_not_log_device_id(self):
4125@@ -674,7 +716,6 @@
4126 """Volumes tests for the backend."""
4127
4128 # Access to a protected member of a client class
4129- # pylint: disable=W0212
4130
4131 def setUp(self):
4132 super(BackendVolumesTestCase, self).setUp()
4133@@ -753,8 +794,8 @@
4134 @inlineCallbacks
4135 def test_volumes_info(self):
4136 """The volumes_info method exercises its callback."""
4137- self.patch(MockSDClient, 'shares', SAMPLE_SHARES)
4138- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4139+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
4140+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4141
4142 expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
4143 result = yield self.be.volumes_info()
4144@@ -763,16 +804,14 @@
4145 @inlineCallbacks
4146 def test_volumes_info_without_storage_info(self):
4147 """The volumes_info method exercises its callback."""
4148- self.patch(MockSDClient, 'shares', SAMPLE_SHARES)
4149- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4150+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
4151+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4152
4153 expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS,
4154 with_storage_info=False)
4155 result = yield self.be.volumes_info(with_storage_info=False)
4156 self.assertEqual(result, expected)
4157
4158- # pylint: disable=W0212
4159-
4160 def test_cached_volumes_are_initially_empty(self):
4161 """The cached volume list is empty."""
4162 self.assertEqual(self.be._volumes, {})
4163@@ -780,8 +819,8 @@
4164 @inlineCallbacks
4165 def test_volumes_are_cached(self):
4166 """The volume list is cached."""
4167- self.patch(MockSDClient, 'shares', SAMPLE_SHARES)
4168- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4169+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
4170+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4171
4172 expected = {}
4173 info = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
4174@@ -832,8 +871,8 @@
4175 u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
4176 ]
4177
4178- self.patch(MockSDClient, 'shares', read_only_shares)
4179- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4180+ self.patch(self.be.sd_client, 'shares', read_only_shares)
4181+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4182
4183 expected = yield self.expected_volumes(read_only_shares,
4184 SAMPLE_FOLDERS)
4185@@ -843,9 +882,9 @@
4186 @inlineCallbacks
4187 def test_volumes_info_no_quota_for_root(self):
4188 """The volumes_info returns info even if quota call fails."""
4189- MockWebClient.failure = 500
4190- self.patch(MockSDClient, 'shares', SAMPLE_SHARES)
4191- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4192+ self.be.wc.failure = 500
4193+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
4194+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4195
4196 expected = yield self.expected_volumes(SAMPLE_SHARES, SAMPLE_FOLDERS)
4197 result = yield self.be.volumes_info()
4198@@ -860,9 +899,9 @@
4199 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
4200
4201 yield self.be.subscribe_volume(volume_id=fid)
4202- self.addCleanup(lambda: MockSDClient.subscribed_folders.remove(fid))
4203+ self.addCleanup(self.be.sd_client.subscribed_folders.remove, fid)
4204
4205- self.assertEqual(MockSDClient.subscribed_folders, [fid])
4206+ self.assertEqual(self.be.sd_client.subscribed_folders, [fid])
4207
4208 @inlineCallbacks
4209 def test_unsubscribe_volume_folder(self):
4210@@ -871,11 +910,11 @@
4211 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
4212
4213 yield self.be.subscribe_volume(volume_id=fid)
4214- self.assertEqual(MockSDClient.subscribed_folders, [fid])
4215+ self.assertEqual(self.be.sd_client.subscribed_folders, [fid])
4216
4217 yield self.be.unsubscribe_volume(volume_id=fid)
4218
4219- self.assertEqual(MockSDClient.subscribed_folders, [])
4220+ self.assertEqual(self.be.sd_client.subscribed_folders, [])
4221
4222 @inlineCallbacks
4223 def test_subscribe_volume_share(self):
4224@@ -884,9 +923,9 @@
4225 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
4226
4227 yield self.be.subscribe_volume(volume_id=sid)
4228- self.addCleanup(lambda: MockSDClient.subscribed_shares.remove(sid))
4229+ self.addCleanup(self.be.sd_client.subscribed_shares.remove, sid)
4230
4231- self.assertEqual(MockSDClient.subscribed_shares, [sid])
4232+ self.assertEqual(self.be.sd_client.subscribed_shares, [sid])
4233
4234 @inlineCallbacks
4235 def test_unsubscribe_volume_share(self):
4236@@ -895,11 +934,11 @@
4237 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
4238
4239 yield self.be.subscribe_volume(volume_id=sid)
4240- self.assertEqual(MockSDClient.subscribed_shares, [sid])
4241+ self.assertEqual(self.be.sd_client.subscribed_shares, [sid])
4242
4243 yield self.be.unsubscribe_volume(volume_id=sid)
4244
4245- self.assertEqual(MockSDClient.subscribed_shares, [])
4246+ self.assertEqual(self.be.sd_client.subscribed_shares, [])
4247
4248 @inlineCallbacks
4249 def test_change_volume_settings_folder(self):
4250@@ -908,10 +947,10 @@
4251 self.be._volumes[fid] = {u'type': self.be.FOLDER_TYPE}
4252
4253 yield self.be.change_volume_settings(fid, {'subscribed': True})
4254- self.assertEqual(MockSDClient.subscribed_folders, [fid])
4255+ self.assertEqual(self.be.sd_client.subscribed_folders, [fid])
4256
4257 yield self.be.change_volume_settings(fid, {'subscribed': False})
4258- self.assertEqual(MockSDClient.subscribed_folders, [])
4259+ self.assertEqual(self.be.sd_client.subscribed_folders, [])
4260
4261 @inlineCallbacks
4262 def test_change_volume_settings_share(self):
4263@@ -920,17 +959,17 @@
4264 self.be._volumes[sid] = {u'type': self.be.SHARE_TYPE}
4265
4266 yield self.be.change_volume_settings(sid, {'subscribed': True})
4267- self.assertEqual(MockSDClient.subscribed_shares, [sid])
4268+ self.assertEqual(self.be.sd_client.subscribed_shares, [sid])
4269
4270 yield self.be.change_volume_settings(sid, {'subscribed': False})
4271- self.assertEqual(MockSDClient.subscribed_shares, [])
4272+ self.assertEqual(self.be.sd_client.subscribed_shares, [])
4273
4274 @inlineCallbacks
4275 def test_change_volume_settings_no_setting(self):
4276 """The change volume settings does not fail on empty settings."""
4277 yield self.be.change_volume_settings('test', {})
4278- self.assertEqual(MockSDClient.subscribed_folders, [])
4279- self.assertEqual(MockSDClient.subscribed_shares, [])
4280+ self.assertEqual(self.be.sd_client.subscribed_folders, [])
4281+ self.assertEqual(self.be.sd_client.subscribed_shares, [])
4282
4283 @inlineCallbacks
4284 def test_create_folder(self):
4285@@ -938,7 +977,7 @@
4286 folder_path = os.path.join(USER_HOME, 'Test Me')
4287 yield self.be.create_folder(folder_path=folder_path)
4288
4289- self.assertEqual(MockSDClient.folders, [folder_path])
4290+ self.assertEqual(self.be.sd_client.folders, [folder_path])
4291
4292
4293 class BackendValidatePathForFolderTestCase(BackendBasicTestCase):
4294@@ -950,9 +989,9 @@
4295 super(BackendValidatePathForFolderTestCase, self).setUp()
4296 old_home = os.environ['HOME']
4297 os.environ['HOME'] = USER_HOME
4298- self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home))
4299- self.patch(MockSDClient, 'shares', SAMPLE_SHARES)
4300- self.patch(MockSDClient, 'folders', SAMPLE_FOLDERS)
4301+ self.addCleanup(os.environ.__setitem__, 'HOME', old_home)
4302+ self.patch(self.be.sd_client, 'shares', SAMPLE_SHARES)
4303+ self.patch(self.be.sd_client, 'folders', SAMPLE_FOLDERS)
4304 self.patch(backend, 'is_link', lambda p: False)
4305
4306 @defer.inlineCallbacks
4307@@ -1040,8 +1079,8 @@
4308
4309 def _build_msg(self):
4310 """Build expected message regarding file sync status."""
4311- return '%s (%s)' % (MockSDClient.status['description'],
4312- MockSDClient.status['name'])
4313+ return '%s (%s)' % (self.be.sd_client.status['description'],
4314+ self.be.sd_client.status['name'])
4315
4316 @inlineCallbacks
4317 def assert_correct_status(self, status, msg=None):
4318@@ -1054,14 +1093,14 @@
4319 @inlineCallbacks
4320 def test_disabled(self):
4321 """The syncdaemon status is processed and emitted."""
4322- self.patch(MockSDClient, 'file_sync', False)
4323+ self.patch(self.be.sd_client, 'file_sync', False)
4324 yield self.assert_correct_status(FILE_SYNC_DISABLED, msg='')
4325 self.assertTrue(self.be.file_sync_disabled)
4326
4327 @inlineCallbacks
4328 def test_error(self):
4329 """The syncdaemon status is processed and emitted."""
4330- MockSDClient.status = {
4331+ self.be.sd_client.status = {
4332 'is_error': 'True', # nothing else matters
4333 'is_online': '', 'is_connected': '',
4334 'name': 'AUTH_FAILED', 'connection': '', 'queues': '',
4335@@ -1074,7 +1113,7 @@
4336 @inlineCallbacks
4337 def test_starting_when_init_not_user(self):
4338 """The syncdaemon status is processed and emitted."""
4339- MockSDClient.status = {
4340+ self.be.sd_client.status = {
4341 'is_error': '', 'is_online': '', 'is_connected': '',
4342 'connection': 'Not User With Network', 'queues': '',
4343 'name': 'INIT', 'description': 'something new',
4344@@ -1085,7 +1124,7 @@
4345 @inlineCallbacks
4346 def test_starting_when_init_with_user(self):
4347 """The syncdaemon status is processed and emitted."""
4348- MockSDClient.status = {
4349+ self.be.sd_client.status = {
4350 'is_error': '', 'is_online': '', 'is_connected': '',
4351 'connection': 'With User With Network', 'queues': '',
4352 'name': 'INIT', 'description': 'something new',
4353@@ -1096,7 +1135,7 @@
4354 @inlineCallbacks
4355 def test_starting_when_local_rescan_not_user(self):
4356 """The syncdaemon status is processed and emitted."""
4357- MockSDClient.status = {
4358+ self.be.sd_client.status = {
4359 'is_error': '', 'is_online': '', 'is_connected': '',
4360 'connection': 'Not User With Network', 'queues': '',
4361 'name': 'LOCAL_RESCAN', 'description': 'something new',
4362@@ -1107,7 +1146,7 @@
4363 @inlineCallbacks
4364 def test_starting_when_local_rescan_with_user(self):
4365 """The syncdaemon status is processed and emitted."""
4366- MockSDClient.status = {
4367+ self.be.sd_client.status = {
4368 'is_error': '', 'is_online': '', 'is_connected': '',
4369 'connection': 'With User With Network', 'queues': '',
4370 'name': 'LOCAL_RESCAN', 'description': 'something new',
4371@@ -1118,7 +1157,7 @@
4372 @inlineCallbacks
4373 def test_starting_when_ready_with_user(self):
4374 """The syncdaemon status is processed and emitted."""
4375- MockSDClient.status = {
4376+ self.be.sd_client.status = {
4377 'is_error': '', 'is_online': '', 'is_connected': '',
4378 'connection': 'With User With Network', 'queues': '',
4379 'name': 'READY', 'description': 'something nicer',
4380@@ -1129,7 +1168,7 @@
4381 @inlineCallbacks
4382 def test_disconnected(self):
4383 """The syncdaemon status is processed and emitted."""
4384- MockSDClient.status = {
4385+ self.be.sd_client.status = {
4386 'is_error': '', 'is_online': '', 'is_connected': '',
4387 'queues': '', 'description': 'something new',
4388 'connection': 'Not User With Network', # user didn't connect
4389@@ -1143,7 +1182,7 @@
4390 @inlineCallbacks
4391 def test_disconnected_when_waiting(self):
4392 """The syncdaemon status is processed and emitted."""
4393- MockSDClient.status = {
4394+ self.be.sd_client.status = {
4395 'is_error': '', 'is_online': '', 'is_connected': '',
4396 'connection': 'With User With Network', 'queues': '',
4397 'name': 'WAITING', 'description': 'what a long wait!',
4398@@ -1156,7 +1195,7 @@
4399 @inlineCallbacks
4400 def test_syncing_if_online(self):
4401 """The syncdaemon status is processed and emitted."""
4402- MockSDClient.status = {
4403+ self.be.sd_client.status = {
4404 'is_error': '', 'is_online': 'True', 'is_connected': 'True',
4405 'name': 'QUEUE_MANAGER', 'connection': '',
4406 'queues': 'WORKING_ON_CONTENT', # anything but IDLE
4407@@ -1170,7 +1209,7 @@
4408 @inlineCallbacks
4409 def test_syncing_even_if_not_online(self):
4410 """The syncdaemon status is processed and emitted."""
4411- MockSDClient.status = {
4412+ self.be.sd_client.status = {
4413 'is_error': '', 'is_online': '', 'is_connected': 'True',
4414 'name': 'CHECK_VERSION', 'connection': '',
4415 'queues': 'WORKING_ON_CONTENT',
4416@@ -1184,7 +1223,7 @@
4417 @inlineCallbacks
4418 def test_idle(self):
4419 """The syncdaemon status is processed and emitted."""
4420- MockSDClient.status = {
4421+ self.be.sd_client.status = {
4422 'is_error': '', 'is_online': 'True', 'is_connected': 'True',
4423 'name': 'QUEUE_MANAGER', 'connection': '',
4424 'queues': 'IDLE',
4425@@ -1198,7 +1237,7 @@
4426 @inlineCallbacks
4427 def test_stopped(self):
4428 """The syncdaemon status is processed and emitted."""
4429- MockSDClient.status = {
4430+ self.be.sd_client.status = {
4431 'is_error': '', 'is_online': '', 'is_connected': '',
4432 'name': 'SHUTDOWN', 'connection': '',
4433 'queues': 'IDLE',
4434@@ -1212,7 +1251,7 @@
4435 @inlineCallbacks
4436 def test_unknown(self):
4437 """The syncdaemon status is processed and emitted."""
4438- MockSDClient.status = {
4439+ self.be.sd_client.status = status = {
4440 'is_error': '', 'is_online': '', 'is_connected': '',
4441 'name': '', 'connection': '', 'queues': '',
4442 'description': '',
4443@@ -1220,7 +1259,7 @@
4444 yield self.assert_correct_status(FILE_SYNC_UNKNOWN)
4445
4446 has_warning = self.memento.check_warning('file_sync_status: unknown',
4447- repr(MockSDClient.status))
4448+ repr(status))
4449 self.assertTrue(has_warning)
4450
4451 # self.be.file_sync_disabled does not change
4452@@ -1231,10 +1270,8 @@
4453 self.be.status_changed_handler = self._set_called
4454 status = {'name': 'foo', 'description': 'bar', 'is_error': '',
4455 'is_connected': '', 'is_online': '', 'queues': ''}
4456- backend.sd_client.status_changed_handler(status)
4457+ self.be.sd_client.status_changed_handler(status)
4458
4459- # pylint: disable=W0212
4460- # Access to a protected member _process_file_sync_status
4461 expected_status = self.be._process_file_sync_status(status)
4462 self.assertEqual(self._called, ((expected_status,), {}))
4463
4464@@ -1259,7 +1296,7 @@
4465
4466 def setUp(self):
4467 super(BackendFileSyncOpsTestCase, self).setUp()
4468- MockSDClient.actions = []
4469+ self.be.sd_client.actions = []
4470
4471 @inlineCallbacks
4472 def test_enable_files(self):
4473@@ -1267,7 +1304,7 @@
4474 yield self.be.disable_files()
4475
4476 yield self.be.enable_files()
4477- self.assertTrue(MockSDClient.file_sync)
4478+ self.assertTrue(self.be.sd_client.file_sync)
4479 self.assertFalse(self.be.file_sync_disabled)
4480
4481 @inlineCallbacks
4482@@ -1276,7 +1313,7 @@
4483 yield self.be.enable_files()
4484
4485 yield self.be.disable_files()
4486- self.assertFalse(MockSDClient.file_sync)
4487+ self.assertFalse(self.be.sd_client.file_sync)
4488 self.assertTrue(self.be.file_sync_disabled)
4489
4490 @inlineCallbacks
4491@@ -1284,7 +1321,7 @@
4492 """Connect files service."""
4493 yield self.be.connect_files()
4494
4495- self.assertEqual(MockSDClient.actions, ['connect'])
4496+ self.assertEqual(self.be.sd_client.actions, ['connect'])
4497 self.assertFalse(self.be.file_sync_disabled)
4498
4499 @inlineCallbacks
4500@@ -1292,7 +1329,7 @@
4501 """Disconnect files service."""
4502 yield self.be.disconnect_files()
4503
4504- self.assertEqual(MockSDClient.actions, ['disconnect'])
4505+ self.assertEqual(self.be.sd_client.actions, ['disconnect'])
4506 self.assertFalse(self.be.file_sync_disabled)
4507
4508 @inlineCallbacks
4509@@ -1300,7 +1337,7 @@
4510 """Restart the files service."""
4511 yield self.be.restart_files()
4512
4513- self.assertEqual(MockSDClient.actions, ['stop', 'start'])
4514+ self.assertEqual(self.be.sd_client.actions, ['stop', 'start'])
4515 self.assertFalse(self.be.file_sync_disabled)
4516
4517 @inlineCallbacks
4518@@ -1308,7 +1345,7 @@
4519 """Start the files service."""
4520 yield self.be.start_files()
4521
4522- self.assertEqual(MockSDClient.actions, ['start'])
4523+ self.assertEqual(self.be.sd_client.actions, ['start'])
4524 self.assertFalse(self.be.file_sync_disabled)
4525
4526 @inlineCallbacks
4527@@ -1316,7 +1353,7 @@
4528 """Stop the files service."""
4529 yield self.be.stop_files()
4530
4531- self.assertEqual(MockSDClient.actions, ['stop'])
4532+ self.assertEqual(self.be.sd_client.actions, ['stop'])
4533 self.assertFalse(self.be.file_sync_disabled)
4534
4535
4536@@ -1348,7 +1385,7 @@
4537 """The replication settings can be changed."""
4538 rid = '0123-4567'
4539 MockReplicationClient.replications.add(rid)
4540- self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
4541+ self.addCleanup(MockReplicationClient.replications.remove, rid)
4542
4543 yield self.be.change_replication_settings(rid, {'enabled': False})
4544 self.assertIn(rid, MockReplicationClient.exclusions)
4545@@ -1373,7 +1410,7 @@
4546 """The change replication settings does not fail on empty settings."""
4547 rid = '0123-4567'
4548 MockReplicationClient.replications.add(rid)
4549- self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
4550+ self.addCleanup(MockReplicationClient.replications.remove, rid)
4551
4552 prior = MockReplicationClient.exclusions.copy()
4553 yield self.be.change_replication_settings(rid, {})
4554@@ -1398,7 +1435,7 @@
4555 new_value = not self.default_settings[setting_name]
4556 yield self.be.change_file_sync_settings({setting_name: new_value})
4557
4558- self.assertEqual(getattr(backend.sd_client, setting_name), new_value)
4559+ self.assertEqual(getattr(self.be.sd_client, setting_name), new_value)
4560
4561 actual = yield self.be.file_sync_settings_info()
4562 expected = self.default_settings.copy()
4563@@ -1408,17 +1445,17 @@
4564 @inlineCallbacks
4565 def test_file_sync_settings_info(self):
4566 """The settings_info method exercises its callback."""
4567- backend.sd_client.limits = {"download": 1000, "upload": 100}
4568+ self.be.sd_client.limits = {"download": 1000, "upload": 100}
4569 expected = {
4570- backend.AUTOCONNECT_KEY: backend.sd_client.autoconnect,
4571+ backend.AUTOCONNECT_KEY: self.be.sd_client.autoconnect,
4572 backend.SHOW_ALL_NOTIFICATIONS_KEY:
4573- backend.sd_client.show_all_notifications,
4574+ self.be.sd_client.show_all_notifications,
4575 backend.SHARE_AUTOSUBSCRIBE_KEY:
4576- backend.sd_client.share_autosubscribe,
4577+ self.be.sd_client.share_autosubscribe,
4578 backend.UDF_AUTOSUBSCRIBE_KEY:
4579- backend.sd_client.udf_autosubscribe,
4580- backend.DOWNLOAD_KEY: backend.sd_client.limits['download'],
4581- backend.UPLOAD_KEY: backend.sd_client.limits['upload'],
4582+ self.be.sd_client.udf_autosubscribe,
4583+ backend.DOWNLOAD_KEY: self.be.sd_client.limits['download'],
4584+ backend.UPLOAD_KEY: self.be.sd_client.limits['upload'],
4585 }
4586 result = yield self.be.file_sync_settings_info()
4587 self.assertEqual(expected, result)
4588@@ -1453,8 +1490,8 @@
4589 setting_name = backend.DOWNLOAD_KEY
4590 yield self.be.change_file_sync_settings({setting_name: new_value})
4591
4592- self.assertEqual(backend.sd_client.throttling, True)
4593- self.assertEqual(backend.sd_client.limits,
4594+ self.assertEqual(self.be.sd_client.throttling, True)
4595+ self.assertEqual(self.be.sd_client.limits,
4596 {'download': new_value, 'upload': -1})
4597
4598 @inlineCallbacks
4599@@ -1464,8 +1501,8 @@
4600 setting_name = backend.UPLOAD_KEY
4601 yield self.be.change_file_sync_settings({setting_name: new_value})
4602
4603- self.assertEqual(backend.sd_client.throttling, True)
4604- self.assertEqual(backend.sd_client.limits,
4605+ self.assertEqual(self.be.sd_client.throttling, True)
4606+ self.assertEqual(self.be.sd_client.limits,
4607 {'download': -1, 'upload': new_value})
4608
4609 @inlineCallbacks
4610@@ -1486,8 +1523,8 @@
4611 setting_name = backend.UPLOAD_KEY
4612 yield self.be.change_file_sync_settings({setting_name: -1})
4613
4614- self.assertEqual(backend.sd_client.throttling, False)
4615- self.assertEqual(backend.sd_client.limits,
4616+ self.assertEqual(self.be.sd_client.throttling, False)
4617+ self.assertEqual(self.be.sd_client.limits,
4618 {'download': -1, 'upload': -1})
4619
4620 @inlineCallbacks
4621@@ -1508,8 +1545,8 @@
4622 setting_name = backend.DOWNLOAD_KEY
4623 yield self.be.change_file_sync_settings({setting_name: -1})
4624
4625- self.assertEqual(backend.sd_client.throttling, False)
4626- self.assertEqual(backend.sd_client.limits,
4627+ self.assertEqual(self.be.sd_client.throttling, False)
4628+ self.assertEqual(self.be.sd_client.limits,
4629 {'download': -1, 'upload': -1})
4630
4631 @inlineCallbacks
4632@@ -1528,12 +1565,12 @@
4633 # the call we want to test
4634 yield self.be.restore_file_sync_settings()
4635
4636- self.assertEqual(backend.sd_client.autoconnect, True)
4637- self.assertEqual(backend.sd_client.show_all_notifications, True)
4638- self.assertEqual(backend.sd_client.share_autosubscribe, False)
4639- self.assertEqual(backend.sd_client.udf_autosubscribe, False)
4640- self.assertEqual(backend.sd_client.throttling, False)
4641- self.assertEqual(backend.sd_client.limits,
4642+ self.assertEqual(self.be.sd_client.autoconnect, True)
4643+ self.assertEqual(self.be.sd_client.show_all_notifications, True)
4644+ self.assertEqual(self.be.sd_client.share_autosubscribe, False)
4645+ self.assertEqual(self.be.sd_client.udf_autosubscribe, False)
4646+ self.assertEqual(self.be.sd_client.throttling, False)
4647+ self.assertEqual(self.be.sd_client.limits,
4648 {'download': -1, 'upload': -1})
4649
4650 result = yield self.be.file_sync_settings_info()
4651
4652=== modified file 'ubuntuone/controlpanel/tests/test_login_client.py'
4653--- ubuntuone/controlpanel/tests/test_login_client.py 2011-07-22 21:26:48 +0000
4654+++ ubuntuone/controlpanel/tests/test_login_client.py 2011-08-25 19:40:18 +0000
4655@@ -19,10 +19,6 @@
4656 """Tests for the service when accessing the login client."""
4657
4658 from twisted.internet import defer
4659-# No name 'credentials' in module 'ubuntuone.platform'
4660-# pylint: disable=E0611
4661-from ubuntuone.platform import credentials
4662-# pylint: enable=E0611
4663
4664 from ubuntuone.controlpanel import login_client
4665 from ubuntuone.controlpanel.tests import TestCase, TOKEN
4666@@ -49,13 +45,23 @@
4667 FakedCredentialsManagementTool.credentials = None
4668 yield
4669
4670+ @defer.inlineCallbacks
4671+ def login(self):
4672+ """Create credentials for Ubuntu One by logging in."""
4673+ yield defer.succeed(FakedCredentialsManagementTool.credentials)
4674+
4675+ @defer.inlineCallbacks
4676+ def register(self):
4677+ """Create credentials for Ubuntu One by registering."""
4678+ yield defer.succeed(FakedCredentialsManagementTool.credentials)
4679+
4680
4681 class BaseTestCase(TestCase):
4682 """Base TestCase for the login client methods."""
4683
4684 def setUp(self):
4685 super(BaseTestCase, self).setUp()
4686- self.patch(credentials, 'CredentialsManagementTool',
4687+ self.patch(login_client, 'CredentialsManagementTool',
4688 FakedCredentialsManagementTool)
4689 FakedCredentialsManagementTool.credentials = TOKEN
4690
4691@@ -87,7 +93,7 @@
4692
4693 @defer.inlineCallbacks
4694 def test_clear_credentials(self):
4695- """The credentials are properly retrieved."""
4696+ """The credentials are properly cleared."""
4697 yield login_client.clear_credentials()
4698 self.assertEqual(None, FakedCredentialsManagementTool.credentials)
4699
4700@@ -98,3 +104,39 @@
4701 self.fake_fail)
4702 yield self.assertFailure(login_client.clear_credentials(),
4703 CustomError)
4704+
4705+
4706+class LoginTestCase(BaseTestCase):
4707+ """Test for the login method."""
4708+
4709+ @defer.inlineCallbacks
4710+ def test_login(self):
4711+ """The credentials are properly retrieved."""
4712+ yield login_client.login()
4713+ self.assertEqual(TOKEN, FakedCredentialsManagementTool.credentials)
4714+
4715+ @defer.inlineCallbacks
4716+ def test_login_throws_an_error(self):
4717+ """If login fails, the error is propagated."""
4718+ self.patch(FakedCredentialsManagementTool, 'login',
4719+ self.fake_fail)
4720+ yield self.assertFailure(login_client.login(),
4721+ CustomError)
4722+
4723+
4724+class RegisterTestCase(BaseTestCase):
4725+ """Test for the register method."""
4726+
4727+ @defer.inlineCallbacks
4728+ def test_register(self):
4729+ """The credentials are properly retrieved."""
4730+ yield login_client.register()
4731+ self.assertEqual(TOKEN, FakedCredentialsManagementTool.credentials)
4732+
4733+ @defer.inlineCallbacks
4734+ def test_register_throws_an_error(self):
4735+ """If register fails, the error is propagated."""
4736+ self.patch(FakedCredentialsManagementTool, 'register',
4737+ self.fake_fail)
4738+ yield self.assertFailure(login_client.register(),
4739+ CustomError)
4740
4741=== modified file 'ubuntuone/controlpanel/tests/test_sd_client.py'
4742--- ubuntuone/controlpanel/tests/test_sd_client.py 2011-07-22 21:26:48 +0000
4743+++ ubuntuone/controlpanel/tests/test_sd_client.py 2011-08-25 19:40:18 +0000
4744@@ -149,7 +149,7 @@
4745 @inlineCallbacks
4746 def get_shares(self):
4747 """Get the list of shares (accepted or not)."""
4748- result = yield FakedSyncDaemonTool.shares.values()
4749+ result = yield self.shares.values()
4750 returnValue(sorted(result))
4751
4752 def refresh_shares(self):
4753@@ -169,38 +169,38 @@
4754 if path == '': # simulate an error
4755 raise CustomError(path)
4756
4757- folder = FakedSyncDaemonTool.create_udf(path)
4758+ folder = self.create_udf(path)
4759 return folder
4760
4761 def delete_folder(self, folder_id):
4762 """Delete a user defined folder given its id."""
4763- if folder_id not in FakedSyncDaemonTool.folders:
4764+ if folder_id not in self.folders:
4765 raise CustomError(folder_id)
4766
4767- folder = FakedSyncDaemonTool.folders.pop(folder_id)
4768+ folder = self.folders.pop(folder_id)
4769 return folder
4770
4771 @inlineCallbacks
4772 def subscribe_folder(self, folder_id):
4773 """Subscribe to a user defined folder given its id."""
4774- yield FakedSyncDaemonTool._set_folder_attr(folder_id,
4775+ yield self._set_folder_attr(folder_id,
4776 u'subscribed', True)
4777
4778 @inlineCallbacks
4779 def unsubscribe_folder(self, folder_id):
4780 """Unsubscribe from a user defined folder given its id."""
4781- yield FakedSyncDaemonTool._set_folder_attr(folder_id,
4782+ yield self._set_folder_attr(folder_id,
4783 u'subscribed', False)
4784
4785 @inlineCallbacks
4786 def get_folders(self):
4787 """Return the list of folders (a list of dicts)."""
4788- result = yield FakedSyncDaemonTool.folders.values()
4789+ result = yield self.folders.values()
4790 returnValue(sorted(result))
4791
4792 def get_folder_info(self, path):
4793 """Call the get_info method for a UDF path."""
4794- for _, content in FakedSyncDaemonTool.folders.iteritems():
4795+ for _, content in self.folders.iteritems():
4796 if content['path'] == path:
4797 return content
4798 else:
4799@@ -322,9 +322,10 @@
4800 def setUp(self):
4801 super(BaseTestCase, self).setUp()
4802 self.patch(tools, 'SyncDaemonTool', FakedSyncDaemonTool)
4803- FakedSyncDaemonTool.shares.clear()
4804- FakedSyncDaemonTool.folders.clear()
4805- FakedSyncDaemonTool.called.clear()
4806+ self.sd = sd_client.SyncDaemonClient()
4807+ self.sd.proxy.shares.clear()
4808+ self.sd.proxy.folders.clear()
4809+ self.sd.proxy.called.clear()
4810
4811 def fake_fail(self, *a):
4812 """Fake a failure."""
4813@@ -337,79 +338,79 @@
4814 @inlineCallbacks
4815 def test_throttle_limits_returned(self):
4816 """When SD returns the limits, they are handled."""
4817- limits = yield sd_client.get_throttling_limits()
4818- self.assertEqual(limits, FakedSyncDaemonTool.settings['limits'])
4819+ limits = yield self.sd.get_throttling_limits()
4820+ self.assertEqual(limits, self.sd.proxy.settings['limits'])
4821
4822 @inlineCallbacks
4823 def test_getting_throttle_limits_throws_an_error(self):
4824 """Handle error when getting the limits."""
4825- self.patch(FakedSyncDaemonTool, 'get_throttling_limits',
4826+ self.patch(self.sd.proxy, 'get_throttling_limits',
4827 self.fake_fail)
4828- yield self.assertFailure(sd_client.get_throttling_limits(),
4829+ yield self.assertFailure(self.sd.get_throttling_limits(),
4830 CustomError)
4831
4832 @inlineCallbacks
4833 def test_throttle_limits_set(self):
4834 """Setting the limits."""
4835- yield sd_client.set_throttling_limits(SAMPLE_LIMITS)
4836- actual = sd_client.get_throttling_limits()
4837+ yield self.sd.set_throttling_limits(SAMPLE_LIMITS)
4838+ actual = self.sd.get_throttling_limits()
4839 self.assertEqual(actual, SAMPLE_LIMITS)
4840
4841 @inlineCallbacks
4842 def test_setting_throttle_limits_throws_an_error(self):
4843 """Handle error when setting the limits."""
4844- self.patch(FakedSyncDaemonTool, 'set_throttling_limits',
4845+ self.patch(self.sd.proxy, 'set_throttling_limits',
4846 self.fake_fail)
4847- d = sd_client.set_throttling_limits(SAMPLE_LIMITS)
4848+ d = self.sd.set_throttling_limits(SAMPLE_LIMITS)
4849 yield self.assertFailure(d, CustomError)
4850
4851 @inlineCallbacks
4852 def test_throttle_get(self):
4853 """Getting the throttling state."""
4854- FakedSyncDaemonTool.settings['throttling_enabled'] = False
4855- throttling = yield sd_client.bandwidth_throttling_enabled()
4856+ self.sd.proxy.settings['throttling_enabled'] = False
4857+ throttling = yield self.sd.bandwidth_throttling_enabled()
4858 self.assertEqual(throttling, False)
4859
4860- FakedSyncDaemonTool.settings['throttling_enabled'] = True
4861- throttling = yield sd_client.bandwidth_throttling_enabled()
4862+ self.sd.proxy.settings['throttling_enabled'] = True
4863+ throttling = yield self.sd.bandwidth_throttling_enabled()
4864 self.assertEqual(throttling, True)
4865
4866 @inlineCallbacks
4867 def test_throttle_get_throws_an_error(self):
4868 """Handle error when getting the throttling state."""
4869- self.patch(FakedSyncDaemonTool, 'is_throttling_enabled',
4870+ self.patch(self.sd.proxy, 'is_throttling_enabled',
4871 self.fake_fail)
4872- yield self.assertFailure(sd_client.bandwidth_throttling_enabled(),
4873+ yield self.assertFailure(self.sd.bandwidth_throttling_enabled(),
4874 CustomError)
4875
4876 @inlineCallbacks
4877 def test_throttle_enable(self):
4878 """Enabling bw throttling."""
4879- FakedSyncDaemonTool.settings['throttling_enabled'] = False
4880- yield sd_client.enable_bandwidth_throttling()
4881- self.assertTrue(FakedSyncDaemonTool.settings['throttling_enabled'])
4882+ self.sd.proxy.settings['throttling_enabled'] = False
4883+ yield self.sd.enable_bandwidth_throttling()
4884+ self.assertTrue(self.sd.proxy.settings['throttling_enabled'])
4885
4886 @inlineCallbacks
4887 def test_throttle_enable_throws_an_error(self):
4888 """Handle error when enabling throttling."""
4889- self.patch(FakedSyncDaemonTool, 'enable_throttling',
4890+ self.patch(self.sd.proxy, 'enable_throttling',
4891 self.fake_fail)
4892- yield self.assertFailure(sd_client.enable_bandwidth_throttling(),
4893+ yield self.assertFailure(self.sd.enable_bandwidth_throttling(),
4894 CustomError)
4895
4896 @inlineCallbacks
4897 def test_throttle_disable(self):
4898 """Disabling bw throttling."""
4899- FakedSyncDaemonTool.settings['throttling_enabled'] = True
4900- yield sd_client.disable_bandwidth_throttling()
4901- self.assertFalse(FakedSyncDaemonTool.settings['throttling_enabled'])
4902+ self.sd.proxy.settings['throttling_enabled'] = True
4903+ yield self.sd.disable_bandwidth_throttling()
4904+ self.assertFalse(self.sd.proxy.settings['throttling_enabled'])
4905
4906 @inlineCallbacks
4907 def test_throttle_disable_throws_an_error(self):
4908 """Handle error when disabling throttling."""
4909- self.patch(FakedSyncDaemonTool, 'enable_throttling',
4910+ self.patch(self.sd.proxy, 'enable_throttling',
4911 self.fake_fail)
4912- yield self.assertFailure(sd_client.disable_bandwidth_throttling(),
4913+ yield self.assertFailure(self.sd.disable_bandwidth_throttling(),
4914 CustomError)
4915
4916
4917@@ -420,53 +421,56 @@
4918 # pylint: disable=E1120
4919
4920 name = 'show_all_notifications'
4921- getter = lambda _: sd_client.show_all_notifications_enabled()
4922- enabler = lambda _: sd_client.enable_show_all_notifications()
4923- disabler = lambda _: sd_client.disable_show_all_notifications()
4924+
4925+ def setUp(self):
4926+ super(NotificationsTestCase, self).setUp()
4927+ self.getter = getattr(self.sd, '%s_enabled' % self.name)
4928+ self.enabler = getattr(self.sd, 'enable_%s' % self.name)
4929+ self.disabler = getattr(self.sd, 'disable_%s' % self.name)
4930
4931 @inlineCallbacks
4932 def test_get_value(self):
4933 """Getting the show_all_notifications state."""
4934- FakedSyncDaemonTool.settings[self.name] = False
4935+ self.sd.proxy.settings[self.name] = False
4936 state = yield self.getter()
4937 self.assertEqual(state, False)
4938- FakedSyncDaemonTool.settings[self.name] = True
4939+ self.sd.proxy.settings[self.name] = True
4940 state = yield self.getter()
4941 self.assertEqual(state, True)
4942
4943 @inlineCallbacks
4944 def test_get_value_throws_an_error(self):
4945 """Handle error when getting the show_all_notifications state."""
4946- self.patch(FakedSyncDaemonTool, 'is_%s_enabled' % self.name,
4947+ self.patch(self.sd.proxy, 'is_%s_enabled' % self.name,
4948 self.fake_fail)
4949 yield self.assertFailure(self.getter(), CustomError)
4950
4951 @inlineCallbacks
4952 def test_enable(self):
4953 """Enabling show_all_notifications."""
4954- FakedSyncDaemonTool.settings[self.name] = False
4955+ self.sd.proxy.settings[self.name] = False
4956 yield self.enabler()
4957- self.assertTrue(FakedSyncDaemonTool.settings[self.name])
4958+ self.assertTrue(self.sd.proxy.settings[self.name])
4959
4960 @inlineCallbacks
4961 def test_enable_throws_an_error(self):
4962 """Handle error when enabling show_all_notifications."""
4963- self.patch(FakedSyncDaemonTool, 'enable_%s' % self.name,
4964+ self.patch(self.sd.proxy, 'enable_%s' % self.name,
4965 self.fake_fail)
4966 yield self.assertFailure(self.enabler(), CustomError)
4967
4968 @inlineCallbacks
4969 def test_disable(self):
4970 """Disabling show_all_notifications."""
4971- FakedSyncDaemonTool.settings[self.name] = True
4972+ self.sd.proxy.settings[self.name] = True
4973 yield self.disabler()
4974- value = FakedSyncDaemonTool.settings[self.name]
4975+ value = self.sd.proxy.settings[self.name]
4976 self.assertFalse(value)
4977
4978 @inlineCallbacks
4979 def test_disable_throws_an_error(self):
4980 """Handle error when disabling show_all_notifications."""
4981- self.patch(FakedSyncDaemonTool, 'enable_%s' % self.name,
4982+ self.patch(self.sd.proxy, 'enable_%s' % self.name,
4983 self.fake_fail)
4984 yield self.assertFailure(self.disabler(), CustomError)
4985
4986@@ -475,27 +479,18 @@
4987 """Test for the autoconnect syncdaemon client methods."""
4988
4989 name = 'autoconnect'
4990- getter = lambda _: sd_client.autoconnect_enabled()
4991- enabler = lambda _: sd_client.enable_autoconnect()
4992- disabler = lambda _: sd_client.disable_autoconnect()
4993
4994
4995 class ShareAutosubscribeTestCase(NotificationsTestCase):
4996 """Test for the share_autosubscribe syncdaemon client methods."""
4997
4998 name = 'share_autosubscribe'
4999- getter = lambda _: sd_client.share_autosubscribe_enabled()
5000- enabler = lambda _: sd_client.enable_share_autosubscribe()
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: