Merge lp:~diegosarmentero/ubuntuone-control-panel/tab-shares-functions into lp:ubuntuone-control-panel

Proposed by Diego Sarmentero
Status: Merged
Approved by: Roberto Alsina
Approved revision: 363
Merged at revision: 352
Proposed branch: lp:~diegosarmentero/ubuntuone-control-panel/tab-shares-functions
Merge into: lp:ubuntuone-control-panel
Diff against target: 2027 lines (+1644/-41)
17 files modified
data/qt/images.qrc (+1/-0)
data/qt/share_file.ui (+94/-0)
data/qt/share_links.ui (+196/-10)
data/qt/ubuntuone.qss (+61/-0)
ubuntuone/controlpanel/backend.py (+36/-1)
ubuntuone/controlpanel/gui/__init__.py (+2/-0)
ubuntuone/controlpanel/gui/qt/share_file.py (+63/-0)
ubuntuone/controlpanel/gui/qt/share_links.py (+189/-1)
ubuntuone/controlpanel/gui/qt/share_links_search.py (+318/-0)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+18/-0)
ubuntuone/controlpanel/gui/qt/tests/test_share_file.py (+76/-0)
ubuntuone/controlpanel/gui/qt/tests/test_share_links.py (+161/-3)
ubuntuone/controlpanel/gui/qt/tests/test_share_links_search.py (+302/-0)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+15/-26)
ubuntuone/controlpanel/sd_client/__init__.py (+20/-0)
ubuntuone/controlpanel/tests/test_backend.py (+64/-0)
ubuntuone/controlpanel/tests/test_sd_client.py (+28/-0)
To merge this branch: bzr merge lp:~diegosarmentero/ubuntuone-control-panel/tab-shares-functions
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Alejandro J. Cura (community) Approve
Review via email: mp+121283@code.launchpad.net

Commit message

- Adding functionality to the Share Links Tab (LP: #1039142).

To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

This is a huge branch, that could have been split in at least three branches. :-(

Please fix the commented lines in the last three testcases.
Does this need a freeze exception?

review: Needs Fixing
352. By Diego Sarmentero

fixing docstrings

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> This is a huge branch, that could have been split in at least three branches.
> :-(

I know, we talk about the length of the branch with ralsina on friday.
A lot of parts are really trivial, comment, qss, xml and docstring anyway...

>
> Please fix the commented lines in the last three testcases.
> Does this need a freeze exception?

Docstrings fixed.

353. By Diego Sarmentero

retrieving folders data asynchronously

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Some comments so far:

5 docstrings are copypasted with: """Obtain the data to create the sync menu."""
Please make them unique.
---
There's an extra blank line after "class ShareLinksPanel" and its docstring.

354. By Diego Sarmentero

enhanced line button updated

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Let's move all QLabel font sizes and color styles used in <span>s into one file with all styling constants.
This is not blocking for this branch, so a new bug is being opened.

---

Empty lines before the docstrings in class ActionsButtons and class EnhancedLineEdit.

----

It's never a good idea to touch class variables in tests, because the state of the class variable is not resetted between tests. Please use regular instances for the fake objects instead every time you can, something like:

class FakeDesktopService(object):
    """Fake QDesktopService."""

    def __init__(self):
        self.opened_url = None

    def openUrl(self, url):
        """Fake openUrl."""
        self.opened_url = url

[...]

    def test_open_in_browser(self):
        """Test the execution of open_in_browser."""
        fake_desktop_service = FakeDesktopService()
        self.patch(QtGui, "QDesktopServices", fake_desktop_service)
        url = 'http://ubuntuone.com/asd123'
        self.ui.ui.line_copy_link.setText(url)
        self.ui._open_in_browser()
        expected = QtCore.QUrl(url)
        self.assertEqual(expected, fake_desktop_service.opened_url)

And similarly in the two tests in ActionsButtonsTestCase.

----

Please, fix the docstrings in:
 * test_move_to_main_list
 * test_get_public_files
 * test_copy

review: Needs Fixing
355. By Diego Sarmentero

tests fixed

356. By Diego Sarmentero

fixing docstring

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> Let's move all QLabel font sizes and color styles used in <span>s into one
> file with all styling constants.
> This is not blocking for this branch, so a new bug is being opened.
>

I've created a different bug for this:
https://bugs.launchpad.net/ubuntuone-control-panel/+bug/1042228

> ---
>
> Empty lines before the docstrings in class ActionsButtons and class
> EnhancedLineEdit.
>
> ----
>
> It's never a good idea to touch class variables in tests, because the state of
> the class variable is not resetted between tests. Please use regular instances
> for the fake objects instead every time you can, something like:
>
> class FakeDesktopService(object):
> """Fake QDesktopService."""
>
> def __init__(self):
> self.opened_url = None
>
> def openUrl(self, url):
> """Fake openUrl."""
> self.opened_url = url
>
> [...]
>
> def test_open_in_browser(self):
> """Test the execution of open_in_browser."""
> fake_desktop_service = FakeDesktopService()
> self.patch(QtGui, "QDesktopServices", fake_desktop_service)
> url = 'http://ubuntuone.com/asd123'
> self.ui.ui.line_copy_link.setText(url)
> self.ui._open_in_browser()
> expected = QtCore.QUrl(url)
> self.assertEqual(expected, fake_desktop_service.opened_url)
>
>
> And similarly in the two tests in ActionsButtonsTestCase.
>
> ----
>
> Please, fix the docstrings in:
> * test_move_to_main_list
> * test_get_public_files
> * test_copy

Fixed

Revision history for this message
Alejandro J. Cura (alecu) wrote :

There are no tests for keyPressEvent and moveEvent in SearchBox.
And also keyPressEvent could benefit from being refactored into smaller functions that are more easily testable.

----

The UI completely freezes for about 15 seconds when starting. The window manager even dims it, as it does with unresponsive windows.

This does not happen on trunk.

----

Besides the above two issues, all tests pass and the code looks good so far.

review: Needs Fixing
357. By Diego Sarmentero

FakeDesktopService improved

358. By Diego Sarmentero

fixing memory and performance issues

359. By Diego Sarmentero

tests fixed

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> There are no tests for keyPressEvent and moveEvent in SearchBox.
> And also keyPressEvent could benefit from being refactored into smaller
> functions that are more easily testable.
>
> ----
>
> The UI completely freezes for about 15 seconds when starting. The window
> manager even dims it, as it does with unresponsive windows.
>
> This does not happen on trunk.
>
> ----
>
> Besides the above two issues, all tests pass and the code looks good so far.

Fixed

360. By Diego Sarmentero

deleting legacy code

361. By Diego Sarmentero

adding missing docstring

362. By Diego Sarmentero

fixed docstrings

Revision history for this message
Alejandro J. Cura (alecu) wrote :

There are "ifs" in _key_down_pressed and _key_up_pressed, but there's only one test for each.
Please add more tests that check all possible combinations.

----

There's no need for this to be a dict:
    self.fake_desktop_service.data['url']

It should be just a variable:
    self.fake_desktop_service.opened_url

review: Needs Fixing
Revision history for this message
Roberto Alsina (ralsina) wrote :

I get this backtrace:

Traceback (most recent call last):
  File "/home/ralsina/canonical/shares/ubuntuone/controlpanel/gui/qt/share_links_search.py", line 296, in run
    folders_data += self.get_folder_info(folder)
  File "/home/ralsina/canonical/shares/ubuntuone/controlpanel/gui/qt/share_links_search.py", line 316, in get_folder_info
    for root, _, files in os.walk(folder):
  File "/usr/lib/python2.7/os.py", line 294, in walk
    for x in walk(new_path, topdown, onerror, followlinks):
  File "/usr/lib/python2.7/os.py", line 284, in walk
    if isdir(join(top, name)):
  File "/usr/lib/python2.7/posixpath.py", line 71, in join
    path += '/' + b
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf1 in position 17: ordinal not in range(128)

review: Needs Fixing
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

> There are "ifs" in _key_down_pressed and _key_up_pressed, but there's only
> one test for each.
> Please add more tests that check all possible combinations.
>
> ----
>
> There's no need for this to be a dict:
> self.fake_desktop_service.data['url']
>
> It should be just a variable:
> self.fake_desktop_service.opened_url

Fixed

363. By Diego Sarmentero

tests improved

Revision history for this message
Roberto Alsina (ralsina) wrote :

> I get this backtrace:
>
>
> Traceback (most recent call last):
> File "/home/ralsina/canonical/shares/ubuntuone/controlpanel/gui/qt/share_lin
> ks_search.py", line 296, in run
> folders_data += self.get_folder_info(folder)
> File "/home/ralsina/canonical/shares/ubuntuone/controlpanel/gui/qt/share_lin
> ks_search.py", line 316, in get_folder_info
> for root, _, files in os.walk(folder):
> File "/usr/lib/python2.7/os.py", line 294, in walk
> for x in walk(new_path, topdown, onerror, followlinks):
> File "/usr/lib/python2.7/os.py", line 284, in walk
> if isdir(join(top, name)):
> File "/usr/lib/python2.7/posixpath.py", line 71, in join
> path += '/' + b
> UnicodeDecodeError: 'ascii' codec can't decode byte 0xf1 in position 17:
> ordinal not in range(128)

This was caused by files with invalid utf-8 names, which is not as critical. Removing this needsfixing.

Revision history for this message
Roberto Alsina (ralsina) wrote :

The search box doesn't let you type spaces.

review: Needs Fixing
Revision history for this message
Alejandro J. Cura (alecu) wrote :

+1

review: Approve
Revision history for this message
Roberto Alsina (ralsina) wrote :

Removing objections, created separate bugs for them

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/delete_search.png'
2Binary files data/delete_search.png 1970-01-01 00:00:00 +0000 and data/delete_search.png 2012-08-28 00:37:19 +0000 differ
3=== modified file 'data/qt/images.qrc'
4--- data/qt/images.qrc 2012-05-29 14:30:36 +0000
5+++ data/qt/images.qrc 2012-08-28 00:37:19 +0000
6@@ -25,6 +25,7 @@
7 <file>../sync_status_syncing.png</file>
8 <file>../twitter.png</file>
9 <file>../facebook.png</file>
10+ <file>../delete_search.png</file>
11 <file>../Ubuntu-R.ttf</file>
12 <file>../Ubuntu-B.ttf</file>
13 <file>ubuntuone.qss</file>
14
15=== added file 'data/qt/share_file.ui'
16--- data/qt/share_file.ui 1970-01-01 00:00:00 +0000
17+++ data/qt/share_file.ui 2012-08-28 00:37:19 +0000
18@@ -0,0 +1,94 @@
19+<?xml version="1.0" encoding="UTF-8"?>
20+<ui version="4.0">
21+ <class>Form</class>
22+ <widget class="QWidget" name="Form">
23+ <property name="geometry">
24+ <rect>
25+ <x>0</x>
26+ <y>0</y>
27+ <width>519</width>
28+ <height>58</height>
29+ </rect>
30+ </property>
31+ <property name="windowTitle">
32+ <string>Form</string>
33+ </property>
34+ <layout class="QHBoxLayout" name="horizontalLayout">
35+ <item>
36+ <layout class="QHBoxLayout" name="horizontalLayout_2">
37+ <item>
38+ <widget class="QLabel" name="lbl_icon">
39+ <property name="text">
40+ <string/>
41+ </property>
42+ </widget>
43+ </item>
44+ <item>
45+ <layout class="QVBoxLayout" name="verticalLayout_3">
46+ <item>
47+ <widget class="QLabel" name="lbl_filename">
48+ <property name="text">
49+ <string>filename</string>
50+ </property>
51+ </widget>
52+ </item>
53+ <item>
54+ <widget class="QLabel" name="lbl_path">
55+ <property name="sizePolicy">
56+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
57+ <horstretch>0</horstretch>
58+ <verstretch>0</verstretch>
59+ </sizepolicy>
60+ </property>
61+ <property name="font">
62+ <font>
63+ <pointsize>9</pointsize>
64+ </font>
65+ </property>
66+ <property name="text">
67+ <string>path</string>
68+ </property>
69+ </widget>
70+ </item>
71+ </layout>
72+ </item>
73+ </layout>
74+ </item>
75+ <item>
76+ <spacer name="horizontalSpacer">
77+ <property name="orientation">
78+ <enum>Qt::Horizontal</enum>
79+ </property>
80+ <property name="sizeHint" stdset="0">
81+ <size>
82+ <width>40</width>
83+ <height>20</height>
84+ </size>
85+ </property>
86+ </spacer>
87+ </item>
88+ <item>
89+ <widget class="QPushButton" name="btn_open">
90+ <property name="sizePolicy">
91+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
92+ <horstretch>0</horstretch>
93+ <verstretch>0</verstretch>
94+ </sizepolicy>
95+ </property>
96+ <property name="text">
97+ <string>Open</string>
98+ </property>
99+ </widget>
100+ </item>
101+ <item>
102+ <widget class="QPushButton" name="btn_disable">
103+ <property name="text">
104+ <string>Disable link</string>
105+ </property>
106+ </widget>
107+ </item>
108+ </layout>
109+ </widget>
110+ <resources/>
111+ <connections/>
112+</ui>
113
114=== modified file 'data/qt/share_links.ui'
115--- data/qt/share_links.ui 2012-08-20 17:51:54 +0000
116+++ data/qt/share_links.ui 2012-08-28 00:37:19 +0000
117@@ -6,13 +6,16 @@
118 <rect>
119 <x>0</x>
120 <y>0</y>
121- <width>532</width>
122- <height>335</height>
123+ <width>567</width>
124+ <height>341</height>
125 </rect>
126 </property>
127 <property name="windowTitle">
128 <string>Form</string>
129 </property>
130+ <property name="styleSheet">
131+ <string notr="true"/>
132+ </property>
133 <layout class="QVBoxLayout" name="verticalLayout">
134 <property name="spacing">
135 <number>15</number>
136@@ -38,7 +41,7 @@
137 <enum>QLayout::SetDefaultConstraint</enum>
138 </property>
139 <property name="leftMargin">
140- <number>10</number>
141+ <number>15</number>
142 </property>
143 <item>
144 <widget class="QLabel" name="search_files_lbl">
145@@ -61,7 +64,7 @@
146 </widget>
147 </item>
148 <item>
149- <widget class="QLineEdit" name="lineEdit">
150+ <widget class="SearchBox" name="line_search">
151 <property name="sizePolicy">
152 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
153 <horstretch>0</horstretch>
154@@ -82,7 +85,10 @@
155 </layout>
156 </item>
157 <item>
158- <widget class="QStackedWidget" name="stackedWidget">
159+ <widget class="QStackedWidget" name="stacked_widget">
160+ <property name="currentIndex">
161+ <number>0</number>
162+ </property>
163 <widget class="QWidget" name="page">
164 <layout class="QVBoxLayout" name="verticalLayout_4">
165 <property name="spacing">
166@@ -107,13 +113,38 @@
167 <number>0</number>
168 </property>
169 <item>
170- <widget class="QTreeWidget" name="treeWidget">
171+ <widget class="QTreeWidget" name="tree_shared_files">
172+ <property name="alternatingRowColors">
173+ <bool>true</bool>
174+ </property>
175+ <property name="indentation">
176+ <number>15</number>
177+ </property>
178+ <property name="rootIsDecorated">
179+ <bool>false</bool>
180+ </property>
181 <attribute name="headerVisible">
182 <bool>false</bool>
183 </attribute>
184- <column>
185- <property name="text">
186- <string notr="true">1</string>
187+ <attribute name="headerDefaultSectionSize">
188+ <number>250</number>
189+ </attribute>
190+ <attribute name="headerStretchLastSection">
191+ <bool>true</bool>
192+ </attribute>
193+ <column>
194+ <property name="text">
195+ <string notr="true">Filename</string>
196+ </property>
197+ </column>
198+ <column>
199+ <property name="text">
200+ <string>Path</string>
201+ </property>
202+ </column>
203+ <column>
204+ <property name="text">
205+ <string notr="true">Actions</string>
206 </property>
207 </column>
208 </widget>
209@@ -123,11 +154,166 @@
210 </item>
211 </layout>
212 </widget>
213- <widget class="QWidget" name="page_2"/>
214+ <widget class="QWidget" name="page_2">
215+ <layout class="QVBoxLayout" name="verticalLayout_5">
216+ <property name="spacing">
217+ <number>0</number>
218+ </property>
219+ <property name="margin">
220+ <number>0</number>
221+ </property>
222+ <item>
223+ <layout class="QHBoxLayout" name="horizontalLayout">
224+ <property name="spacing">
225+ <number>0</number>
226+ </property>
227+ <property name="leftMargin">
228+ <number>15</number>
229+ </property>
230+ <item>
231+ <widget class="QLabel" name="label_share_file">
232+ <property name="sizePolicy">
233+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
234+ <horstretch>0</horstretch>
235+ <verstretch>0</verstretch>
236+ </sizepolicy>
237+ </property>
238+ <property name="font">
239+ <font>
240+ <weight>75</weight>
241+ <bold>true</bold>
242+ </font>
243+ </property>
244+ <property name="text">
245+ <string>Share this file</string>
246+ </property>
247+ </widget>
248+ </item>
249+ <item>
250+ <spacer name="horizontalSpacer">
251+ <property name="orientation">
252+ <enum>Qt::Horizontal</enum>
253+ </property>
254+ <property name="sizeType">
255+ <enum>QSizePolicy::Expanding</enum>
256+ </property>
257+ <property name="sizeHint" stdset="0">
258+ <size>
259+ <width>40</width>
260+ <height>0</height>
261+ </size>
262+ </property>
263+ </spacer>
264+ </item>
265+ <item>
266+ <widget class="QPushButton" name="back_to_file_list">
267+ <property name="text">
268+ <string>Back to file list</string>
269+ </property>
270+ </widget>
271+ </item>
272+ </layout>
273+ </item>
274+ <item>
275+ <layout class="QVBoxLayout" name="verticalLayout">
276+ <property name="spacing">
277+ <number>0</number>
278+ </property>
279+ <item>
280+ <widget class="QFrame" name="frame_share_file">
281+ <property name="frameShape">
282+ <enum>QFrame::StyledPanel</enum>
283+ </property>
284+ <property name="frameShadow">
285+ <enum>QFrame::Raised</enum>
286+ </property>
287+ <layout class="QVBoxLayout" name="verticalLayout_7">
288+ <property name="spacing">
289+ <number>0</number>
290+ </property>
291+ <property name="margin">
292+ <number>0</number>
293+ </property>
294+ <item>
295+ <layout class="QHBoxLayout" name="hbox_share_file">
296+ <property name="spacing">
297+ <number>0</number>
298+ </property>
299+ </layout>
300+ </item>
301+ <item>
302+ <layout class="QVBoxLayout" name="verticalLayout_6">
303+ <property name="leftMargin">
304+ <number>15</number>
305+ </property>
306+ <property name="topMargin">
307+ <number>15</number>
308+ </property>
309+ <item>
310+ <widget class="QLineEdit" name="line_copy_link">
311+ <property name="sizePolicy">
312+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
313+ <horstretch>0</horstretch>
314+ <verstretch>0</verstretch>
315+ </sizepolicy>
316+ </property>
317+ <property name="minimumSize">
318+ <size>
319+ <width>470</width>
320+ <height>0</height>
321+ </size>
322+ </property>
323+ <property name="readOnly">
324+ <bool>true</bool>
325+ </property>
326+ </widget>
327+ </item>
328+ <item>
329+ <widget class="QPushButton" name="open_in_browser">
330+ <property name="sizePolicy">
331+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
332+ <horstretch>0</horstretch>
333+ <verstretch>0</verstretch>
334+ </sizepolicy>
335+ </property>
336+ <property name="text">
337+ <string>Open in browser</string>
338+ </property>
339+ </widget>
340+ </item>
341+ <item>
342+ <spacer name="verticalSpacer">
343+ <property name="orientation">
344+ <enum>Qt::Vertical</enum>
345+ </property>
346+ <property name="sizeHint" stdset="0">
347+ <size>
348+ <width>0</width>
349+ <height>1</height>
350+ </size>
351+ </property>
352+ </spacer>
353+ </item>
354+ </layout>
355+ </item>
356+ </layout>
357+ </widget>
358+ </item>
359+ </layout>
360+ </item>
361+ </layout>
362+ </widget>
363 </widget>
364 </item>
365 </layout>
366 </widget>
367+ <customwidgets>
368+ <customwidget>
369+ <class>SearchBox</class>
370+ <extends>QLineEdit</extends>
371+ <header>ubuntuone.controlpanel.gui.qt.share_links_search</header>
372+ </customwidget>
373+ </customwidgets>
374 <resources/>
375 <connections/>
376 </ui>
377
378=== modified file 'data/qt/ubuntuone.qss'
379--- data/qt/ubuntuone.qss 2012-08-22 16:30:59 +0000
380+++ data/qt/ubuntuone.qss 2012-08-28 00:37:19 +0000
381@@ -19,6 +19,12 @@
382 border-color: #333333;
383 }
384
385+QFrame#frame_share_file {
386+ border-top-style: solid;
387+ border-width: 1px;
388+ border-color: #727272;
389+}
390+
391 UbuntuOneWizard,
392 QFrame#frame_header {
393 background: white;
394@@ -316,6 +322,21 @@
395 color: #df2d1f;
396 }
397
398+FilesPopup > QListView {
399+ border-style: solid;
400+ border-width: 1px;
401+ border-color: #727272;
402+}
403+
404+FilesPopup > QListView::item {
405+ padding: 5px;
406+}
407+
408+FilesPopup > QListView::item:selected {
409+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
410+ stop: 0 #dd4814,stop: 1.0 #b93f14);
411+}
412+
413 QAbstractItemView {
414 border-style: solid;
415 border-top-width: 1px;
416@@ -362,3 +383,43 @@
417 padding-bottom: 5px;
418 /* end of hack */
419 }
420+
421+QPushButton#action_button {
422+ margin: 0px;
423+ padding: 5 5 5 5;
424+}
425+
426+QPushButton#open_in_browser {
427+ background: transparent;
428+ border: none;
429+ text-decoration: underline;
430+ color: #dd4814;
431+ padding-left: 0px;
432+ padding-right: 25px;
433+ background-image: url(:/external_icon_orange.png);
434+ background-repeat: no-repeat;
435+ background-position: right;
436+ background-origin: margin;
437+}
438+
439+QPushButton#back_to_file_list {
440+ background: transparent;
441+ border: none;
442+ color: #dd4814;
443+ padding-left: 10px;
444+ padding-right: 15px;
445+}
446+
447+QPushButton#enhanced_borderless {
448+ background: transparent;
449+ border: none;
450+ padding: 0 5 0 0;
451+}
452+
453+QLabel#lbl_path {
454+ color: gray;
455+}
456+
457+QLineEdit#line_copy_link {
458+ color: #dd4814;
459+}
460
461=== modified file 'ubuntuone/controlpanel/backend.py'
462--- ubuntuone/controlpanel/backend.py 2012-08-16 17:12:44 +0000
463+++ ubuntuone/controlpanel/backend.py 2012-08-28 00:37:19 +0000
464@@ -829,12 +829,47 @@
465
466 @log_call(logger.info)
467 @inlineCallbacks
468+ def get_public_files(self):
469+ """Trigger the action to get the public files."""
470+ yield self.sd_client.get_public_files()
471+
472+ @log_call(logger.info)
473+ @inlineCallbacks
474+ def set_public_files_list_handler(self, handler):
475+ """Return the handler to be called for the public files list."""
476+ result = yield self.sd_client.set_public_files_list_handler(handler)
477+ returnValue(result)
478+
479+ @log_call(logger.info)
480+ @inlineCallbacks
481 def get_shares(self):
482- """Obtain the data to create the sync menu."""
483+ """Get the information of the shares."""
484 result = yield self.sd_client.get_shares()
485 returnValue(result)
486
487 @log_call(logger.info)
488+ @inlineCallbacks
489+ def change_public_access(self, path, is_public):
490+ """Change the type access of a file."""
491+ yield self.sd_client.change_public_access(path, is_public)
492+
493+ @log_call(logger.info)
494+ @inlineCallbacks
495+ def set_public_access_changed_handler(self, handler):
496+ """Return the handler to be called when a access type change."""
497+ result = yield self.sd_client.set_public_access_changed_handler(
498+ handler)
499+ returnValue(result)
500+
501+ @log_call(logger.info)
502+ @inlineCallbacks
503+ def set_public_access_change_error_handler(self, handler):
504+ """Return the handler to be called on access type change error."""
505+ result = yield self.sd_client.set_public_access_change_error_handler(
506+ handler)
507+ returnValue(result)
508+
509+ @log_call(logger.info)
510 def shutdown(self):
511 """Stop this service."""
512 # do any other needed cleanup
513
514=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
515--- ubuntuone/controlpanel/gui/__init__.py 2012-08-20 21:02:06 +0000
516+++ ubuntuone/controlpanel/gui/__init__.py 2012-08-28 00:37:19 +0000
517@@ -105,6 +105,7 @@
518 COMPUTER_TO_CLOUD_TITLE = _('Syncing your computer with the cloud')
519 CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One')
520 CONTACTS = _('Thunderbird plug-in')
521+COPY_LINK = _('Copy link')
522 CREDENTIALS_ERROR = _('There was a problem while retrieving the credentials.')
523 DASHBOARD_BUTTON_TOOLTIP = _('View your personal details and service '
524 'summary')
525@@ -226,6 +227,7 @@
526 NO_DEVICES = _('No devices to show.')
527 NO_FOLDERS = _('No folders to show.')
528 NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
529+OPEN = _('Open')
530 OPEN_UBUNTU_ONE = _('Open Ubuntu One')
531 OPEN_UBUNTU_ONE_FOLDER = _('Open the Ubuntu One Folder')
532 PERCENTAGE_LABEL = _('%(percentage)s used')
533
534=== added file 'ubuntuone/controlpanel/gui/qt/share_file.py'
535--- ubuntuone/controlpanel/gui/qt/share_file.py 1970-01-01 00:00:00 +0000
536+++ ubuntuone/controlpanel/gui/qt/share_file.py 2012-08-28 00:37:19 +0000
537@@ -0,0 +1,63 @@
538+# -*- coding: utf-8 -*-
539+#
540+# Copyright 2012 Canonical Ltd.
541+#
542+# This program is free software: you can redistribute it and/or modify it
543+# under the terms of the GNU General Public License version 3, as published
544+# by the Free Software Foundation.
545+#
546+# This program is distributed in the hope that it will be useful, but
547+# WITHOUT ANY WARRANTY; without even the implied warranties of
548+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
549+# PURPOSE. See the GNU General Public License for more details.
550+#
551+# You should have received a copy of the GNU General Public License along
552+# with this program. If not, see <http://www.gnu.org/licenses/>.
553+
554+"""The UI for Share file widget."""
555+
556+import os
557+
558+from PyQt4 import QtGui, QtCore
559+
560+from ubuntuone.controlpanel import cache
561+from ubuntuone.controlpanel.gui.qt.share_links_search import (
562+ get_system_icon_for_filename,
563+)
564+from ubuntuone.controlpanel.gui.qt.ui import share_file_ui
565+from ubuntuone.controlpanel.logger import setup_logging
566+
567+
568+logger = setup_logging('qt.share_file')
569+
570+
571+class ShareFileWidget(cache.Cache, QtGui.QWidget):
572+ """Widget with the detail information about the shared file."""
573+
574+ linkDisabled = QtCore.pyqtSignal()
575+
576+ def __init__(self, file_path='', *args, **kwargs):
577+ super(ShareFileWidget, self).__init__(*args, **kwargs)
578+ self.ui = share_file_ui.Ui_Form()
579+ self.ui.setupUi(self)
580+ self.file_path = file_path
581+
582+ self.ui.lbl_filename.setText(os.path.basename(file_path))
583+ self.ui.lbl_path.setText(file_path)
584+ icon = get_system_icon_for_filename(os.path.expanduser(file_path))
585+ pixmap = icon.pixmap(24)
586+ self.ui.lbl_icon.setPixmap(pixmap)
587+
588+ self.ui.btn_open.clicked.connect(self.open_file)
589+ self.ui.btn_disable.clicked.connect(self.disable_link)
590+
591+ def open_file(self):
592+ """Open the specified file."""
593+ path = u'file://%s' % self.file_path
594+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(path))
595+
596+ def disable_link(self, val):
597+ """Change the access of the file to Not Public."""
598+ self.backend.change_public_access(
599+ os.path.expanduser(self.file_path), False)
600+ self.linkDisabled.emit()
601
602=== modified file 'ubuntuone/controlpanel/gui/qt/share_links.py'
603--- ubuntuone/controlpanel/gui/qt/share_links.py 2012-08-20 18:22:07 +0000
604+++ ubuntuone/controlpanel/gui/qt/share_links.py 2012-08-28 00:37:19 +0000
605@@ -16,27 +16,215 @@
606
607 """The user interface for the control panel for Ubuntu One."""
608
609+import os
610+
611+from PyQt4 import QtGui, QtCore
612+from twisted.internet.defer import inlineCallbacks
613+
614 from ubuntuone.controlpanel.logger import setup_logging
615 from ubuntuone.controlpanel.gui import (
616+ COPY_LINK,
617+ OPEN,
618 SEARCH_FILES,
619 SHARED_FILES,
620 )
621
622-from ubuntuone.controlpanel.gui.qt.ui import share_links_ui
623+# Unused import images_rc, pylint: disable=W0611
624+from ubuntuone.controlpanel.gui.qt.ui import (
625+ images_rc,
626+ share_links_ui,
627+)
628+# pylint: enable=W0611
629+from ubuntuone.controlpanel.gui.qt.share_file import ShareFileWidget
630+from ubuntuone.controlpanel.gui.qt.share_links_search import (
631+ get_system_icon_for_filename,
632+)
633 from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin
634
635
636 logger = setup_logging('qt.share_links')
637
638
639+FILE_NAME_COL = 0
640+PUBLIC_LINK_COL = 1
641+ACTIONS_COL = 2
642+
643+
644 class ShareLinksPanel(UbuntuOneBin):
645 """The Share Links Tab Panel widget"""
646
647 ui_class = share_links_ui
648 logger = logger
649+ _enhanced_line = None
650+ home_dir = ''
651
652 def _setup(self):
653 """Do some extra setupping for the UI."""
654 super(ShareLinksPanel, self)._setup()
655+ self.home_dir = ''
656 self.ui.search_files_lbl.setText(SEARCH_FILES)
657 self.ui.shared_group.setTitle(SHARED_FILES)
658+
659+ # Set enhanced line edits
660+ self._enhanced_line = EnhancedLineEdit(self.ui.line_search,
661+ self._line_close_btn, icon=":/delete_search.png",
662+ style='enhanced_borderless')
663+ self._enhanced_line.btn_operation.hide()
664+ self.ui.line_search.popup.popupHidden.connect(
665+ self._hide_line_btn_close_hide)
666+ self.ui.line_search.popup.popupShown.connect(
667+ self._hide_line_btn_close_show)
668+ EnhancedLineEdit(self.ui.line_copy_link, self._copy_link_from_line,
669+ text=COPY_LINK)
670+
671+ self.ui.line_search.itemSelected.connect(self.share_file)
672+ self.ui.back_to_file_list.clicked.connect(self._move_to_main_list)
673+ self.ui.open_in_browser.clicked.connect(self._open_in_browser)
674+
675+ # Connect backend signals
676+ self.backend.set_public_files_list_handler(self._load_public_files)
677+ self.backend.set_public_access_changed_handler(self._file_shared)
678+ self.backend.set_public_access_change_error_handler(
679+ lambda: self._set_is_processing(False))
680+ self.get_public_files()
681+
682+ @inlineCallbacks
683+ def share_file(self, file_path):
684+ """Clean the previous file share details and publish file_path."""
685+ if self.ui.hbox_share_file.count() > 0:
686+ widget = self.ui.hbox_share_file.takeAt(0).widget()
687+ widget.close()
688+ self.is_processing = True
689+ file_path = unicode(file_path)
690+ share_file_widget = ShareFileWidget(file_path)
691+ self.ui.hbox_share_file.addWidget(share_file_widget)
692+ share_file_widget.linkDisabled.connect(
693+ lambda: self.ui.line_copy_link.setText(''))
694+ yield self.backend.change_public_access(
695+ os.path.expanduser(file_path), True)
696+
697+ def _file_shared(self, info):
698+ """Receive the notification that the file has been published."""
699+ url = info.get("public_url")
700+ self.ui.line_copy_link.setText(url)
701+ self.ui.stacked_widget.setCurrentIndex(1)
702+ self.is_processing = False
703+
704+ def _open_in_browser(self):
705+ """Open the link in line_copy_link in the browser."""
706+ url = self.ui.line_copy_link.text()
707+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
708+
709+ def _copy_link_from_line(self):
710+ """Copy link into clipboard from line edit."""
711+ app = QtGui.QApplication.instance()
712+ app.clipboard().setText(self.ui.line_copy_link.text())
713+
714+ def _move_to_main_list(self):
715+ """Set the share files list as current widget."""
716+ self.ui.stacked_widget.setCurrentIndex(0)
717+ self.get_public_files()
718+
719+ @inlineCallbacks
720+ def get_public_files(self):
721+ """Request the list of public files."""
722+ self.is_processing = True
723+ self.home_dir = yield self.backend.get_home_dir()
724+ yield self.backend.get_public_files()
725+
726+ def _load_public_files(self, publicfiles):
727+ """Load the list of public files."""
728+ self.ui.tree_shared_files.clear()
729+ for pfile in publicfiles:
730+ item = QtGui.QTreeWidgetItem()
731+ path = pfile['path']
732+ public_url = pfile['public_url']
733+ name = os.path.basename(path)
734+ item.setText(FILE_NAME_COL, name)
735+ tooltip = path
736+ if tooltip.startswith(self.home_dir):
737+ tooltip = tooltip.replace(self.home_dir, '~', 1)
738+ item.setToolTip(FILE_NAME_COL, tooltip)
739+ icon = get_system_icon_for_filename(path)
740+ item.setIcon(FILE_NAME_COL, icon)
741+
742+ self.ui.tree_shared_files.setColumnWidth(PUBLIC_LINK_COL, 300)
743+ item.setSizeHint(FILE_NAME_COL, QtCore.QSize(-1, 35))
744+ self.ui.tree_shared_files.addTopLevelItem(item)
745+
746+ link = ('<a href="%s"><span style="font-size: 12px;'
747+ 'color: #dd4814";>%s</span></a>'
748+ % (public_url, public_url))
749+ label = QtGui.QLabel(link, self.ui.tree_shared_files)
750+ label.setOpenExternalLinks(True)
751+ self.ui.tree_shared_files.setItemWidget(item, PUBLIC_LINK_COL,
752+ label)
753+
754+ actions = ActionsButtons(path, public_url,
755+ self.ui.tree_shared_files)
756+ self.ui.tree_shared_files.setItemWidget(item, ACTIONS_COL, actions)
757+ self.is_processing = False
758+
759+ def _line_close_btn(self):
760+ """Close button in the line edit was pressed, hide the popup."""
761+ self.ui.line_search.popup.hide()
762+ self.ui.line_search.setFocus()
763+
764+ def _hide_line_btn_close_hide(self):
765+ """Hide the button inside the search line edit-"""
766+ self._enhanced_line.btn_operation.hide()
767+
768+ def _hide_line_btn_close_show(self):
769+ """Show the button inside the search line edit-"""
770+ self._enhanced_line.btn_operation.show()
771+
772+
773+class ActionsButtons(QtGui.QWidget):
774+ """Widget that contains the open and copy link actions on the list."""
775+
776+ def __init__(self, path, link, parent=None):
777+ super(ActionsButtons, self).__init__(parent)
778+ self.path = path
779+ self.link = link
780+
781+ hbox = QtGui.QHBoxLayout(self)
782+ btn_open = QtGui.QPushButton(OPEN)
783+ btn_copy = QtGui.QPushButton(COPY_LINK)
784+ btn_open.setObjectName('action_button')
785+ btn_copy.setObjectName('action_button')
786+ hbox.addSpacerItem(QtGui.QSpacerItem(1, 0,
787+ QtGui.QSizePolicy.Expanding))
788+ hbox.addWidget(btn_open)
789+ hbox.addWidget(btn_copy)
790+
791+ btn_open.clicked.connect(self.open)
792+ btn_copy.clicked.connect(self.copy)
793+
794+ def open(self):
795+ """Open the file."""
796+ file_path = u'file://%s' % self.path
797+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(file_path))
798+
799+ def copy(self):
800+ """Copy the public link of the file in the clipboard."""
801+ app = QtGui.QApplication.instance()
802+ app.clipboard().setText(self.link)
803+
804+
805+class EnhancedLineEdit(object):
806+ """Add a button inside the QLineEdit received."""
807+
808+ def __init__(self, line_edit, operation, text=None, icon=None, style=None):
809+ hbox = QtGui.QHBoxLayout(line_edit)
810+ hbox.setMargin(0)
811+ line_edit.setLayout(hbox)
812+ hbox.addStretch()
813+ self.btn_operation = QtGui.QPushButton(line_edit)
814+ if text:
815+ self.btn_operation.setText(text)
816+ if icon:
817+ self.btn_operation.setIcon(QtGui.QIcon(icon))
818+ if style:
819+ self.btn_operation.setObjectName(style)
820+ hbox.addWidget(self.btn_operation)
821+ self.btn_operation.clicked.connect(operation)
822
823=== added file 'ubuntuone/controlpanel/gui/qt/share_links_search.py'
824--- ubuntuone/controlpanel/gui/qt/share_links_search.py 1970-01-01 00:00:00 +0000
825+++ ubuntuone/controlpanel/gui/qt/share_links_search.py 2012-08-28 00:37:19 +0000
826@@ -0,0 +1,318 @@
827+# -*- coding: utf-8 *-*
828+
829+# Copyright 2012 Canonical Ltd.
830+#
831+# This program is free software: you can redistribute it and/or modify it
832+# under the terms of the GNU General Public License version 3, as published
833+# by the Free Software Foundation.
834+#
835+# This program is distributed in the hope that it will be useful, but
836+# WITHOUT ANY WARRANTY; without even the implied warranties of
837+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
838+# PURPOSE. See the GNU General Public License for more details.
839+#
840+# You should have received a copy of the GNU General Public License along
841+# with this program. If not, see <http://www.gnu.org/licenses/>.
842+
843+"""The search and popup widgets for the Share Links tab."""
844+
845+import os
846+
847+from PyQt4 import QtGui, QtCore
848+from twisted.internet.defer import inlineCallbacks
849+
850+from ubuntuone.controlpanel import cache
851+
852+
853+def get_system_icon_for_filename(file_path):
854+ """Return the icon used for the system to represent this file."""
855+ fileinfo = QtCore.QFileInfo(os.path.expanduser(file_path))
856+ icon_provider = QtGui.QFileIconProvider()
857+ icon = icon_provider.icon(fileinfo)
858+ return icon
859+
860+
861+# pylint: disable=C0103
862+
863+class SearchBox(QtGui.QLineEdit, cache.Cache):
864+ """Search widget for the synced files."""
865+
866+ itemSelected = QtCore.pyqtSignal(unicode)
867+
868+ def __init__(self, parent=None):
869+ super(SearchBox, self).__init__(parent)
870+ self.popup = FilesPopup()
871+ self.home_dir = ''
872+ self.temp_u1_files = []
873+ self.items_per_page = 0
874+ self.items_step = 20
875+ self.prefix = ''
876+ self._thread_explore = None
877+ self._pre_key_event = {
878+ QtCore.Qt.Key_Space: self._key_space_pressed,
879+ }
880+ self._post_key_event = {
881+ QtCore.Qt.Key_Escape: lambda *args: self.popup.hide(),
882+ QtCore.Qt.Key_Down: self._key_down_pressed,
883+ QtCore.Qt.Key_Up: self._key_up_pressed,
884+ QtCore.Qt.Key_Return: self._key_return_pressed,
885+ QtCore.Qt.Key_Enter: self._key_return_pressed,
886+ }
887+
888+ self.textChanged.connect(self.filter)
889+
890+ self._get_volumes_info()
891+
892+ @inlineCallbacks
893+ def _get_volumes_info(self):
894+ """Get the volumes info."""
895+ self.home_dir = yield self.backend.get_home_dir()
896+ self._thread_explore = ThreadExploreFolder(self.home_dir)
897+ info = yield self.backend.volumes_info()
898+ self._process_volumes_info(info)
899+
900+ def _process_volumes_info(self, info):
901+ """Get the volumes paths and process them."""
902+ folders = []
903+ for _, _, data in info:
904+ for d in data:
905+ folder = d.get('path', d.get('realpath'))
906+ folders.append(folder)
907+ self.get_folders_files(folders)
908+
909+ def get_folders_files(self, folders):
910+ """Get the list of files in each folder and index them."""
911+ self._thread_explore.set_folders(folders)
912+ self._thread_explore.folderDataObtained.connect(
913+ self._folder_content_obtained)
914+ self._thread_explore.start()
915+
916+ def _folder_content_obtained(self):
917+ """Receive the content of the folders from the thread."""
918+ files = self._get_filtered_list(self._thread_explore.u1_files)
919+ self.popup.load_items(files)
920+ text = self.text()
921+ self.filter(text)
922+
923+ def filter(self, text):
924+ """Filter the content of the popup with the text entered."""
925+ text = unicode(text)
926+ self.items_per_page = 0
927+ if text and (self.prefix == '' or self.prefix != text[:-1]):
928+ self.prefix = text
929+ self.temp_u1_files = self._thread_explore.u1_files
930+ self._show_filter()
931+ elif text != '':
932+ self.prefix = text
933+ self._show_filter()
934+ else:
935+ self.popup.hide()
936+
937+ def _show_filter(self):
938+ """Load the items in the popup and display it."""
939+ if not self.popup.isVisible():
940+ self.popup.setFixedWidth(self.width())
941+ point = self.parent().mapToGlobal(self.pos())
942+ self.popup.show()
943+ self.popup.move(point.x(), point.y() + self.height())
944+
945+ self.temp_u1_files = [filename for filename in self.temp_u1_files
946+ if os.path.basename(filename).find(self.prefix) > -1]
947+ files = self._get_filtered_list(self.temp_u1_files)
948+ self.popup.load_items(files)
949+
950+ def _get_filtered_list(self, filenames):
951+ """Get pages of results."""
952+ begin = self.items_per_page
953+ self.items_per_page += self.items_step
954+ files = [filename for filename in filenames[begin:self.items_per_page]]
955+ return files
956+
957+ def _key_space_pressed(self):
958+ """The user pressed the space key."""
959+ item = self.popup.list_widget.currentItem()
960+ widget = self.popup.list_widget.itemWidget(item)
961+ self.setText(widget.name)
962+ self.popup.hide()
963+ return True
964+
965+ def _key_down_pressed(self, current):
966+ """The user pressed the down key."""
967+ #While the current position is lower that the list size go to next
968+ if current != self.popup.list_widget.count() - 1:
969+ self.popup.list_widget.setCurrentRow(
970+ self.popup.list_widget.currentRow() + 1)
971+ #If the current position is greater than the amount of items in
972+ #the list - 6, then try to fetch more items in the list.
973+ if current >= (self.popup.list_widget.count() - 6):
974+ filenames = self._get_filtered_list(self.temp_u1_files)
975+ self.popup.fetch_more(filenames)
976+
977+ def _key_up_pressed(self, current):
978+ """The user pressed the up key."""
979+ #while the current position is greater than 0, go to previous
980+ if current > 0:
981+ self.popup.list_widget.setCurrentRow(
982+ self.popup.list_widget.currentRow() - 1)
983+
984+ def _key_return_pressed(self, current):
985+ """The user pressed the return key."""
986+ #If the user pressed enter, go to the item selected
987+ item = self.popup.list_widget.currentItem()
988+ self._set_selected_item(item)
989+
990+ def keyPressEvent(self, event):
991+ """Process the different behaviour for the keyPress event."""
992+ if self._pre_key_event.get(event.key(), lambda: False)():
993+ return
994+
995+ super(SearchBox, self).keyPressEvent(event)
996+ current = self.popup.list_widget.currentRow()
997+ self._post_key_event.get(event.key(), lambda *args: None)(current)
998+
999+ def focusOutEvent(self, event):
1000+ """Hide the popup when the window loses the focus."""
1001+ super(SearchBox, self).focusOutEvent(event)
1002+ self.popup.hide()
1003+
1004+ def _set_selected_item(self, item):
1005+ """Notify of the selected item."""
1006+ widget = self.popup.list_widget.itemWidget(item)
1007+ self.itemSelected.emit(widget.file_path)
1008+ self.setText('')
1009+ self.popup.hide()
1010+
1011+ def moveEvent(self, event):
1012+ """Move the popup when the windows is moved."""
1013+ super(SearchBox, self).moveEvent(event)
1014+ if self.popup.isVisible():
1015+ point = self.mapToGlobal(self.line_search.pos())
1016+ self.popup.move(point.x(), point.y() + self.line_search.height())
1017+
1018+
1019+class FileItem(QtGui.QLabel):
1020+ """Create a styled QLabel that will show the info."""
1021+
1022+ def __init__(self, file_path):
1023+ super(FileItem, self).__init__()
1024+ self.name = os.path.basename(file_path)
1025+ self.file_path = file_path
1026+ self.text_style = (u"<span style='color: {2};'>{0}</span><br>"
1027+ "<span style='font-size: 13px; color: {3};'>({1})</span>")
1028+ self.setText(self.text_style.format(
1029+ self.name, self.file_path, '#333333', 'grey'))
1030+
1031+ def set_selected(self):
1032+ """Set a style for the text when the item is selected."""
1033+ self.setText(self.text_style.format(
1034+ self.name, self.file_path, 'white', 'white'))
1035+
1036+ def set_not_selected(self):
1037+ """Set a style for the text when the item is not selected."""
1038+ self.setText(self.text_style.format(
1039+ self.name, self.file_path, '#333333', 'grey'))
1040+
1041+
1042+class FilesPopup(QtGui.QFrame):
1043+ """Filter popup where the file names are shown."""
1044+
1045+ popupShown = QtCore.pyqtSignal()
1046+ popupHidden = QtCore.pyqtSignal()
1047+
1048+ def __init__(self):
1049+ super(FilesPopup, self).__init__(None,
1050+ QtCore.Qt.FramelessWindowHint | QtCore.Qt.ToolTip)
1051+ vbox = QtGui.QVBoxLayout(self)
1052+ vbox.setContentsMargins(0, 0, 0, 0)
1053+ vbox.setSpacing(0)
1054+ self.list_widget = QtGui.QListWidget()
1055+ self.list_widget.setMinimumHeight(270)
1056+ vbox.addWidget(self.list_widget)
1057+
1058+ self.list_widget.currentItemChanged.connect(self._repaint_items)
1059+
1060+ def _repaint_items(self, current, previous):
1061+ """Set the proper style for the current and previous items."""
1062+ if current is not None:
1063+ widget = self.list_widget.itemWidget(current)
1064+ widget.set_selected()
1065+ if previous is not None:
1066+ widget = self.list_widget.itemWidget(previous)
1067+ widget.set_not_selected()
1068+
1069+ def load_items(self, file_items):
1070+ """Load the initial items."""
1071+ self.list_widget.clear()
1072+ for file_ in file_items:
1073+ item = QtGui.QListWidgetItem("\n")
1074+ file_widget = FileItem(file_)
1075+ self.list_widget.addItem(item)
1076+ self.list_widget.setItemWidget(item, file_widget)
1077+ icon = get_system_icon_for_filename(file_)
1078+ item.setIcon(icon)
1079+ if file_items:
1080+ self.list_widget.setCurrentRow(0)
1081+
1082+ def fetch_more(self, file_items):
1083+ """Add more items to the list on user scroll."""
1084+ for file_ in file_items:
1085+ item = QtGui.QListWidgetItem("\n")
1086+ file_widget = FileItem(file_)
1087+ self.list_widget.addItem(item)
1088+ self.list_widget.setItemWidget(item, file_widget)
1089+ icon = get_system_icon_for_filename(file_)
1090+ item.setIcon(icon)
1091+
1092+ def showEvent(self, event):
1093+ """Notify when the popup is shown."""
1094+ super(FilesPopup, self).showEvent(event)
1095+ self.popupShown.emit()
1096+
1097+ def hideEvent(self, event):
1098+ """Notify when the popup is hidden."""
1099+ super(FilesPopup, self).hideEvent(event)
1100+ self.popupHidden.emit()
1101+
1102+
1103+class ThreadExploreFolder(QtCore.QThread):
1104+ """Retrieve the data of the folders asynchronously."""
1105+
1106+ folderDataObtained = QtCore.pyqtSignal()
1107+
1108+ def __init__(self, home_dir=''):
1109+ super(ThreadExploreFolder, self).__init__()
1110+ self.home_dir = home_dir
1111+ self.u1_files = []
1112+ self.folders = []
1113+
1114+ def set_folders(self, folders):
1115+ """Set the folders to be explored."""
1116+ self.folders = folders
1117+
1118+ def run(self):
1119+ """Execute the thread to obtain the folders data."""
1120+ folders_data = []
1121+ for folder in self.folders:
1122+ folders_data += self.get_folder_info(folder)
1123+ temp_files = []
1124+ for file_ in folders_data:
1125+ if file_.startswith(self.home_dir):
1126+ new_path = file_[len(self.home_dir):]
1127+ if new_path.startswith(os.path.sep):
1128+ new_path = new_path[1:]
1129+ path = os.path.join('~', new_path)
1130+ temp_files.append(path)
1131+ else:
1132+ temp_files.append(file_)
1133+ folders_data = temp_files
1134+ folders_data.sort()
1135+ self.u1_files = folders_data
1136+ self.folderDataObtained.emit()
1137+
1138+ def get_folder_info(self, folder):
1139+ """Return the list of files in the proper folder."""
1140+ files_list = []
1141+ if os.path.exists(folder):
1142+ for root, _, files in os.walk(folder):
1143+ files_list.extend([os.path.join(root, f) for f in files])
1144+ return files_list
1145
1146=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
1147--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-08-20 14:09:45 +0000
1148+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-08-28 00:37:19 +0000
1149@@ -126,6 +126,7 @@
1150 'build_signed_iri',
1151 'change_device_settings',
1152 'change_file_sync_settings',
1153+ 'change_public_access',
1154 'change_replication_settings',
1155 'change_volume_settings',
1156 'connect_files',
1157@@ -137,12 +138,16 @@
1158 'enable_files',
1159 'file_sync_settings_info',
1160 'file_sync_status',
1161+ 'get_public_files',
1162 'login',
1163 'register',
1164 'remove_device',
1165 'replications_info',
1166 'restart_files',
1167 'restore_file_sync_settings',
1168+ 'set_public_access_changed_handler',
1169+ 'set_public_access_change_error_handler',
1170+ 'set_public_files_list_handler',
1171 'shutdown',
1172 'start_files',
1173 'stop_files',
1174@@ -379,3 +384,16 @@
1175 """The backend instance is correct."""
1176 if getattr(self.ui, 'backend', None) is not None:
1177 self.assertIsInstance(self.ui.backend, FakedControlPanelBackend)
1178+
1179+
1180+class FakeDesktopService(object):
1181+ """Fake QDesktopService."""
1182+
1183+ def __init__(self):
1184+ self.opened_url = None
1185+
1186+ # pylint: disable=C0103
1187+
1188+ def openUrl(self, url):
1189+ """Fake openUrl."""
1190+ self.opened_url = url
1191
1192=== added file 'ubuntuone/controlpanel/gui/qt/tests/test_share_file.py'
1193--- ubuntuone/controlpanel/gui/qt/tests/test_share_file.py 1970-01-01 00:00:00 +0000
1194+++ ubuntuone/controlpanel/gui/qt/tests/test_share_file.py 2012-08-28 00:37:19 +0000
1195@@ -0,0 +1,76 @@
1196+# -*- coding: utf-8 -*-
1197+#
1198+# Copyright 2012 Canonical Ltd.
1199+#
1200+# This program is free software: you can redistribute it and/or modify it
1201+# under the terms of the GNU General Public License version 3, as published
1202+# by the Free Software Foundation.
1203+#
1204+# This program is distributed in the hope that it will be useful, but
1205+# WITHOUT ANY WARRANTY; without even the implied warranties of
1206+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1207+# PURPOSE. See the GNU General Public License for more details.
1208+#
1209+# You should have received a copy of the GNU General Public License along
1210+# with this program. If not, see <http://www.gnu.org/licenses/>.
1211+
1212+"""Tests for the share file widget."""
1213+
1214+import os
1215+
1216+from PyQt4 import QtGui, QtCore
1217+
1218+from ubuntuone.controlpanel.gui.qt import share_file as gui
1219+from ubuntuone.controlpanel.gui.qt.tests import (
1220+ BaseTestCase,
1221+ FakeDesktopService,
1222+)
1223+
1224+# Access to a protected member
1225+# Instance of 'ControlBackend' has no '_called' member
1226+# pylint: disable=W0212, E1103
1227+
1228+
1229+class ShareFileWidgetTestCase(BaseTestCase):
1230+ """Test the ShareFileWidget class."""
1231+
1232+ innerclass_ui = gui.share_file_ui
1233+ innerclass_name = "Ui_Form"
1234+ class_ui = gui.ShareFileWidget
1235+ file_path = u'/home/ñandú/Ubuntu One/my_file.txt'
1236+ kwargs = {'file_path': file_path}
1237+ logger = gui.logger
1238+
1239+ def test_init(self):
1240+ """The share file widget initialization."""
1241+ self.assertEqual(self.ui.file_path, self.file_path)
1242+ self.assertEqual(self.ui.ui.lbl_filename.text(),
1243+ os.path.basename(self.file_path))
1244+ self.assertEqual(self.ui.ui.lbl_path.text(), self.file_path)
1245+
1246+ def test_open_file(self):
1247+ """Test that open file is called properly."""
1248+ fake_desktop_service = FakeDesktopService()
1249+ self.patch(QtGui, "QDesktopServices", fake_desktop_service)
1250+ self.ui.ui.btn_open.click()
1251+
1252+ expected = QtCore.QUrl(u'file://%s' % self.file_path)
1253+ self.assertEqual(expected, fake_desktop_service.opened_url)
1254+
1255+ def test_disable_link(self):
1256+ """Test the disable link action."""
1257+ data = []
1258+
1259+ def fake_change_access(path, access):
1260+ """Fake change access type callback."""
1261+ data.append(path)
1262+ data.append(access)
1263+
1264+ self.ui.linkDisabled.connect(self._set_called)
1265+ self.patch(self.ui.backend, "change_public_access",
1266+ fake_change_access)
1267+ self.ui.ui.btn_disable.click()
1268+
1269+ expected = [os.path.expanduser(self.file_path), False]
1270+ self.assertEqual(data, expected)
1271+ self.assertEqual(self._called, ((), {}))
1272
1273=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_share_links.py'
1274--- ubuntuone/controlpanel/gui/qt/tests/test_share_links.py 2012-08-20 18:24:20 +0000
1275+++ ubuntuone/controlpanel/gui/qt/tests/test_share_links.py 2012-08-28 00:37:19 +0000
1276@@ -14,15 +14,25 @@
1277 # You should have received a copy of the GNU General Public License along
1278 # with this program. If not, see <http://www.gnu.org/licenses/>.
1279
1280-"""Tests for the account tab."""
1281+"""Tests for the Share Links tab."""
1282+
1283+import os
1284+
1285+from PyQt4 import QtGui, QtCore
1286
1287 from ubuntuone.controlpanel.gui import (
1288 SEARCH_FILES,
1289 SHARED_FILES,
1290 )
1291+from ubuntuone.controlpanel.gui.tests import USER_HOME
1292 from ubuntuone.controlpanel.gui.qt import share_links as gui
1293-from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1294-
1295+from ubuntuone.controlpanel.gui.qt.tests import (
1296+ BaseTestCase,
1297+ FakeDesktopService,
1298+)
1299+
1300+
1301+# pylint: disable=W0212
1302
1303 class ShareLinksTestCase(BaseTestCase):
1304 """Test the qt control panel."""
1305@@ -35,3 +45,151 @@
1306 """Check that the strings are properly setted."""
1307 self.assertEqual(self.ui.ui.search_files_lbl.text(), SEARCH_FILES)
1308 self.assertEqual(self.ui.ui.shared_group.title(), SHARED_FILES)
1309+ self.assertIsInstance(self.ui._enhanced_line, gui.EnhancedLineEdit)
1310+ self.assertEqual(self.ui._enhanced_line.btn_operation.text(), '')
1311+ self.assertFalse(self.ui._enhanced_line.btn_operation.isVisible())
1312+
1313+ def test_share_file(self):
1314+ """Check that the state of the widgets on share_file."""
1315+ path = '/home/user/Ubuntu One/file1.txt'
1316+ self.ui.share_file(path)
1317+ self.assertTrue(self.ui.is_processing)
1318+ widget = self.ui.ui.hbox_share_file.takeAt(0).widget()
1319+ self.assertIsInstance(widget, gui.ShareFileWidget)
1320+ self.assertEqual(widget.ui.lbl_filename.text(),
1321+ os.path.basename(path))
1322+ self.assertEqual(widget.ui.lbl_path.text(), path)
1323+
1324+ def test_share_file_actions(self):
1325+ """Check the behaviour of share_file buttons."""
1326+ path = '/home/user/Ubuntu One/file1.txt'
1327+ self.ui.share_file(path)
1328+ widget = self.ui.ui.hbox_share_file.takeAt(0).widget()
1329+ self.ui.ui.line_copy_link.setText('link')
1330+ self.assertEqual(self.ui.ui.line_copy_link.text(), 'link')
1331+ widget.linkDisabled.emit()
1332+ self.assertNotEqual(self.ui.ui.line_copy_link.text(), 'link')
1333+
1334+ def test_file_shared(self):
1335+ """Check the behavior of the widgets after the file is shared."""
1336+ info = {'public_url': 'http://ubuntuone.com/asd123'}
1337+ self.ui._file_shared(info)
1338+ self.assertEqual(self.ui.ui.line_copy_link.text(), info['public_url'])
1339+ self.assertEqual(self.ui.ui.stacked_widget.currentIndex(), 1)
1340+ self.assertFalse(self.ui.is_processing)
1341+
1342+ def test_open_in_browser(self):
1343+ """Test the execution of open_in_browser."""
1344+ fake_desktop_service = FakeDesktopService()
1345+ self.patch(QtGui, "QDesktopServices", fake_desktop_service)
1346+ url = 'http://ubuntuone.com/asd123'
1347+ self.ui.ui.line_copy_link.setText(url)
1348+ self.ui._open_in_browser()
1349+ expected = QtCore.QUrl(url)
1350+ self.assertEqual(expected, fake_desktop_service.opened_url)
1351+
1352+ def test_copy_link_from_line(self):
1353+ """Test the execution of copy_link_from_line."""
1354+ url = 'http://ubuntuone.com/asd123'
1355+ self.ui.ui.line_copy_link.setText(url)
1356+ self.ui._copy_link_from_line()
1357+ clip = QtGui.QApplication.instance().clipboard()
1358+ self.assertEqual(url, clip.text())
1359+
1360+ def test_move_to_main_list(self):
1361+ """Test that the stacked widget shows the proper index."""
1362+ self.ui._move_to_main_list()
1363+ self.assertEqual(self.ui.ui.stacked_widget.currentIndex(), 0)
1364+
1365+ def test_get_public_files(self):
1366+ """Test that the proper actions are executed on files requested.."""
1367+ self.ui.get_public_files()
1368+ self.assertTrue(self.ui.is_processing)
1369+ self.assertEqual(self.ui.ui.stacked_widget.currentIndex(), 0)
1370+ self.assertEqual(self.ui.home_dir, USER_HOME)
1371+
1372+ def test_line_close_btn(self):
1373+ """Check that the popup is hidden."""
1374+ self.ui.ui.line_search.popup.show()
1375+ self.addCleanup(self.ui.ui.line_search.popup.hide)
1376+ self.ui._line_close_btn()
1377+ self.assertFalse(self.ui.ui.line_search.popup.isVisible())
1378+
1379+ def test_hide_line_btn_close_hide(self):
1380+ """Check the state of the inline button."""
1381+ self.ui._enhanced_line.btn_operation.show()
1382+ self.ui.ui.line_search.popup.popupHidden.emit()
1383+ self.assertFalse(self.ui._enhanced_line.btn_operation.isVisible())
1384+
1385+ def test_hide_line_btn_close_show(self):
1386+ """Check the state of the inline button."""
1387+ self.ui.ui.line_search.popup.popupShown.emit()
1388+ self.assertTrue(self.ui._enhanced_line.btn_operation.isVisible())
1389+
1390+ def test_load_public_files(self):
1391+ """Test if the list of public files is loaded properly."""
1392+ publicfiles = [
1393+ {'path': '/home/file1', 'public_url': 'http:ubuntuone.com/asd123'},
1394+ {'path': '/home/file2', 'public_url': 'http:ubuntuone.com/qwe456'},
1395+ ]
1396+ self.ui._load_public_files(publicfiles)
1397+ item = self.ui.ui.tree_shared_files.topLevelItem(0)
1398+ self.assertEqual(item.text(0), os.path.basename('/home/file1'))
1399+ self.assertEqual(item.toolTip(0), '/home/file1')
1400+ label = self.ui.ui.tree_shared_files.itemWidget(item, 1)
1401+ link = ('<a href="%s"><span style="font-size: 12px;'
1402+ 'color: #dd4814";>%s</span></a>'
1403+ % ('http:ubuntuone.com/asd123', 'http:ubuntuone.com/asd123'))
1404+ self.assertEqual(link, label.text())
1405+ actions = self.ui.ui.tree_shared_files.itemWidget(item, 2)
1406+ self.assertIsInstance(actions, gui.ActionsButtons)
1407+
1408+ item = self.ui.ui.tree_shared_files.topLevelItem(1)
1409+ self.assertEqual(item.text(0), os.path.basename('/home/file2'))
1410+ self.assertEqual(item.toolTip(0), '/home/file2')
1411+ label = self.ui.ui.tree_shared_files.itemWidget(item, 1)
1412+ link = ('<a href="%s"><span style="font-size: 12px;'
1413+ 'color: #dd4814";>%s</span></a>'
1414+ % ('http:ubuntuone.com/qwe456', 'http:ubuntuone.com/qwe456'))
1415+ self.assertEqual(link, label.text())
1416+ actions = self.ui.ui.tree_shared_files.itemWidget(item, 2)
1417+ self.assertIsInstance(actions, gui.ActionsButtons)
1418+
1419+
1420+class ActionsButtonsTestCase(BaseTestCase):
1421+ """Test the Actions Buttons."""
1422+
1423+ def test_open(self):
1424+ """Test the open method."""
1425+ path = '/home/file1'
1426+ link = 'http://ubuntuone.com/asd123'
1427+ actions = gui.ActionsButtons(path, link)
1428+ fake_desktop_service = FakeDesktopService()
1429+ self.patch(QtGui, "QDesktopServices", fake_desktop_service)
1430+ actions.open()
1431+ file_path = QtCore.QUrl(u'file://%s' % path)
1432+ self.assertEqual(file_path, fake_desktop_service.opened_url)
1433+
1434+ def test_copy(self):
1435+ """Test that the link is copied into the clipboard.."""
1436+ path = '/home/file1'
1437+ link = 'http://ubuntuone.com/asd123'
1438+ actions = gui.ActionsButtons(path, link)
1439+ fake_desktop_service = FakeDesktopService()
1440+ self.patch(QtGui, "QDesktopServices", fake_desktop_service)
1441+ actions.copy()
1442+ clip = QtGui.QApplication.instance().clipboard()
1443+ self.assertEqual(link, clip.text())
1444+
1445+
1446+class EnhancedLineEditTestCase(BaseTestCase):
1447+ """Test the EnhancedLineEdit."""
1448+
1449+ def test_initialize(self):
1450+ """Test initialization."""
1451+ line = QtGui.QLineEdit()
1452+ enhanced = gui.EnhancedLineEdit(line, self._set_called, 'text')
1453+ self.assertEqual(line.layout().count(), 2)
1454+ self.assertFalse(self._called)
1455+ enhanced.btn_operation.click()
1456+ self.assertEqual(self._called, ((False, ), {}))
1457
1458=== added file 'ubuntuone/controlpanel/gui/qt/tests/test_share_links_search.py'
1459--- ubuntuone/controlpanel/gui/qt/tests/test_share_links_search.py 1970-01-01 00:00:00 +0000
1460+++ ubuntuone/controlpanel/gui/qt/tests/test_share_links_search.py 2012-08-28 00:37:19 +0000
1461@@ -0,0 +1,302 @@
1462+# -*- coding: utf-8 -*-
1463+
1464+# Copyright 2012 Canonical Ltd.
1465+#
1466+# This program is free software: you can redistribute it and/or modify it
1467+# under the terms of the GNU General Public License version 3, as published
1468+# by the Free Software Foundation.
1469+#
1470+# This program is distributed in the hope that it will be useful, but
1471+# WITHOUT ANY WARRANTY; without even the implied warranties of
1472+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1473+# PURPOSE. See the GNU General Public License for more details.
1474+#
1475+# You should have received a copy of the GNU General Public License along
1476+# with this program. If not, see <http://www.gnu.org/licenses/>.
1477+
1478+"""Tests for the Share Links Search."""
1479+
1480+import os
1481+
1482+from twisted.internet import defer
1483+
1484+from ubuntuone.controlpanel.gui.tests import USER_HOME
1485+from ubuntuone.controlpanel.gui.qt import share_links_search as gui
1486+from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1487+
1488+
1489+# pylint: disable=W0212
1490+
1491+class SearchBoxTestCase(BaseTestCase):
1492+ """Test the qt control panel."""
1493+
1494+ class_ui = gui.SearchBox
1495+
1496+ @defer.inlineCallbacks
1497+ def setUp(self):
1498+ yield super(SearchBoxTestCase, self).setUp()
1499+
1500+ self.patch(self.ui._thread_explore, "get_folder_info",
1501+ self.fake_get_folder_info)
1502+ self.patch(self.ui._thread_explore, "start",
1503+ lambda *args, **kwargs: self.ui._thread_explore.run())
1504+ self.folder_info = {
1505+ 'folder1': [
1506+ os.path.join(USER_HOME, 'ubuntu', 'file1'),
1507+ os.path.join(USER_HOME, 'ubuntu', 'file2'),
1508+ os.path.join(USER_HOME, 'one', 'file3'),
1509+ ],
1510+ 'folder2': [
1511+ os.path.join(USER_HOME, 'test', 'asd'),
1512+ os.path.join('other_path', 'test', 'qwe'),
1513+ os.path.join(USER_HOME, 'blabla', 'iop'),
1514+ ]
1515+ }
1516+
1517+ def fake_get_folder_info(self, folder):
1518+ """Fake get_folder_info."""
1519+ return self.folder_info.get(folder, [])
1520+
1521+ def test_initialization(self):
1522+ """Check that the widget is build properly"""
1523+ self.assertEqual(self.ui.home_dir, USER_HOME)
1524+ self.assertEqual(self.ui._thread_explore.u1_files, [])
1525+ self.assertEqual(self.ui.temp_u1_files, [])
1526+ self.assertEqual(self.ui.items_per_page, 0)
1527+ self.assertEqual(self.ui.items_step, 20)
1528+ self.assertEqual(self.ui.prefix, '')
1529+
1530+ def test_key_down_pressed(self):
1531+ """Check the proper action are executed on key down pressed."""
1532+ data1 = [{'path': 'folder1'}]
1533+ data2 = [{'realpath': 'folder2'}]
1534+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1535+ self.ui.popup.list_widget.setCurrentRow(1)
1536+ current = self.ui.popup.list_widget.currentRow()
1537+ self.ui._key_down_pressed(current)
1538+ self.assertEqual(self.ui.popup.list_widget.currentRow(), 2)
1539+
1540+ def test_key_down_pressed_load_more_items(self):
1541+ """Check the proper action are executed on key down pressed."""
1542+ data = []
1543+
1544+ def fake_fetch_more(filenames):
1545+ """Fake fetch_more."""
1546+ data.append(True)
1547+
1548+ self.patch(self.ui.popup, "fetch_more", fake_fetch_more)
1549+ data1 = [{'path': 'folder1'}]
1550+ data2 = [{'realpath': 'folder2'}]
1551+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2),
1552+ (0, 0, data1), (0, 0, data2), (0, 0, data1), (0, 0, data2),
1553+ (0, 0, data1), (0, 0, data2), (0, 0, data1), (0, 0, data2),
1554+ (0, 0, data1), (0, 0, data2), (0, 0, data1), (0, 0, data2)])
1555+ self.assertEqual(self.ui.popup.list_widget.count(), 20)
1556+ self.ui.popup.list_widget.setCurrentRow(
1557+ self.ui.popup.list_widget.count() - 5)
1558+ current = self.ui.popup.list_widget.currentRow()
1559+ self.ui._key_down_pressed(current)
1560+ self.assertEqual(data, [True])
1561+
1562+ def test_key_up_pressed(self):
1563+ """Check the proper action are executed on key up pressed."""
1564+ data1 = [{'path': 'folder1'}]
1565+ data2 = [{'realpath': 'folder2'}]
1566+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1567+ self.ui.popup.list_widget.setCurrentRow(1)
1568+ current = self.ui.popup.list_widget.currentRow()
1569+ self.ui._key_up_pressed(current)
1570+ self.assertEqual(self.ui.popup.list_widget.currentRow(), 0)
1571+
1572+ def test_key_up_pressed_stay_in_0(self):
1573+ """Check the proper action are executed on key up pressed."""
1574+ data1 = [{'path': 'folder1'}]
1575+ data2 = [{'realpath': 'folder2'}]
1576+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1577+ self.ui.popup.list_widget.setCurrentRow(0)
1578+ current = self.ui.popup.list_widget.currentRow()
1579+ self.assertEqual(self.ui.popup.list_widget.currentRow(), 0)
1580+ self.ui._key_up_pressed(current)
1581+ self.assertEqual(self.ui.popup.list_widget.currentRow(), 0)
1582+
1583+ def test_key_return_pressed(self):
1584+ """Check the proper action are executed on key return pressed."""
1585+ data1 = [{'path': 'folder1'}]
1586+ data2 = [{'realpath': 'folder2'}]
1587+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1588+ self.ui.popup.list_widget.setCurrentRow(1)
1589+ current = self.ui.popup.list_widget.currentRow()
1590+ self.ui._key_return_pressed(current)
1591+
1592+ def test_key_space_pressed(self):
1593+ """Check the proper action are executed on key space pressed."""
1594+ data = []
1595+
1596+ def fake_set_text(text):
1597+ """fake setText."""
1598+ data.append(text)
1599+
1600+ self.patch(self.ui, "setText", fake_set_text)
1601+ data1 = [{'path': 'folder1'}]
1602+ data2 = [{'realpath': 'folder2'}]
1603+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1604+ self.ui.popup.list_widget.setCurrentRow(1)
1605+
1606+ self.ui._key_space_pressed()
1607+ expected = ['iop']
1608+ self.assertEqual(expected, data)
1609+
1610+ def test_process_volumes_info(self):
1611+ """Check that _process_volumes_info obtain the proper info."""
1612+ data1 = [{'path': 'folder1'}]
1613+ data2 = [{'realpath': 'folder2'}]
1614+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1615+ expected = [
1616+ 'other_path/test/qwe',
1617+ '~/blabla/iop',
1618+ '~/one/file3',
1619+ '~/test/asd',
1620+ '~/ubuntu/file1',
1621+ '~/ubuntu/file2']
1622+ self.assertEqual(self.ui._thread_explore.u1_files, expected)
1623+ self.assertEqual(self.ui.popup.list_widget.count(), 6)
1624+
1625+ def test_filter(self):
1626+ """Check the results of the filter."""
1627+ # This tests the behavior of:
1628+ # filter
1629+ # _show_filter
1630+ # _get_filtered_list
1631+ self.patch(self.ui.popup, "isVisible", lambda: True)
1632+ data1 = [{'path': 'folder1'}]
1633+ data2 = [{'realpath': 'folder2'}]
1634+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1635+ self.ui.filter('p')
1636+ expected = ['~/blabla/iop']
1637+ self.assertEqual(expected, self.ui.temp_u1_files)
1638+
1639+ self.ui.filter('i')
1640+ expected = [
1641+ '~/blabla/iop',
1642+ '~/one/file3',
1643+ '~/ubuntu/file1',
1644+ '~/ubuntu/file2']
1645+ self.assertEqual(expected, self.ui.temp_u1_files)
1646+
1647+ def test_set_selected_item(self):
1648+ """Check the notification of the selected item."""
1649+ self.ui.itemSelected.connect(self._set_called)
1650+ self.patch(self.ui.popup, "isVisible", lambda: True)
1651+ data1 = [{'path': 'folder1'}]
1652+ data2 = [{'realpath': 'folder2'}]
1653+ self.ui._process_volumes_info([(0, 0, data1), (0, 0, data2)])
1654+ self.ui.popup.list_widget.setCurrentRow(0)
1655+ item = self.ui.popup.list_widget.currentItem()
1656+ self.ui._set_selected_item(item)
1657+ self.assertEqual(self._called, ((u'other_path/test/qwe',), {}))
1658+ self.assertEqual(self.ui.text(), '')
1659+
1660+
1661+class FileItemTestCase(BaseTestCase):
1662+ """Test the File Item."""
1663+
1664+ class_ui = gui.FileItem
1665+ file_path = '/home/tester/ubuntu/file1.txt'
1666+ kwargs = {'file_path': file_path}
1667+
1668+ def test_default(self):
1669+ """Check the default style of the item"""
1670+ name = os.path.basename(self.file_path)
1671+ style = self.ui.text_style.format(name, self.file_path,
1672+ '#333333', 'grey')
1673+ self.assertEqual(self.ui.text(), style)
1674+
1675+ def test_selected(self):
1676+ """Check the default style of the item"""
1677+ self.ui.set_selected()
1678+ name = os.path.basename(self.file_path)
1679+ style = self.ui.text_style.format(name, self.file_path,
1680+ 'white', 'white')
1681+ self.assertEqual(self.ui.text(), style)
1682+
1683+ def test_not_selected(self):
1684+ """Check the default style of the item"""
1685+ self.ui.set_not_selected()
1686+ name = os.path.basename(self.file_path)
1687+ style = self.ui.text_style.format(name, self.file_path,
1688+ '#333333', 'grey')
1689+ self.assertEqual(self.ui.text(), style)
1690+
1691+
1692+class FilesPopupTestCase(BaseTestCase):
1693+
1694+ """FilesPopup tests."""
1695+
1696+ class_ui = gui.FilesPopup
1697+ text_style = (u"<span style='color: {2};'>{0}</span><br>"
1698+ "<span style='font-size: 13px; color: {3};'>({1})</span>")
1699+
1700+ def test_load_items(self):
1701+ """Tests that the items are loaded properly."""
1702+ items = [
1703+ '/home/tester/file1',
1704+ '/home/tester/file2',
1705+ '/home/tester/file3',
1706+ ]
1707+
1708+ self.assertEqual(self.ui.list_widget.count(), 0)
1709+ self.ui.load_items(items)
1710+ self.assertEqual(self.ui.list_widget.count(), 3)
1711+ # Check that we erase the list on reload
1712+ self.ui.load_items(items)
1713+ self.assertEqual(self.ui.list_widget.count(), 3)
1714+
1715+ def test_fetch_more(self):
1716+ """Tests that the items are loaded properly."""
1717+ items = [
1718+ '/home/tester/file1',
1719+ '/home/tester/file2',
1720+ '/home/tester/file3',
1721+ ]
1722+
1723+ self.assertEqual(self.ui.list_widget.count(), 0)
1724+ self.ui.load_items(items)
1725+ self.assertEqual(self.ui.list_widget.count(), 3)
1726+ self.ui.fetch_more(items)
1727+ self.assertEqual(self.ui.list_widget.count(), 6)
1728+
1729+ def test_repaint_items(self):
1730+ """Check the style of the items change acording to the selection."""
1731+ items = [
1732+ '/home/tester/file1',
1733+ '/home/tester/file2',
1734+ '/home/tester/file3',
1735+ ]
1736+
1737+ self.ui.load_items(items)
1738+ current = self.ui.list_widget.item(0)
1739+ widget = self.ui.list_widget.itemWidget(current)
1740+ next_ = self.ui.list_widget.item(1)
1741+ widget2 = self.ui.list_widget.itemWidget(next_)
1742+ name = os.path.basename('/home/tester/file1')
1743+ style = self.text_style.format(name, '/home/tester/file1',
1744+ 'white', 'white')
1745+ name2 = os.path.basename('/home/tester/file2')
1746+ style2 = self.text_style.format(name2, '/home/tester/file2',
1747+ '#333333', 'grey')
1748+ self.assertEqual(widget.text(), style)
1749+ self.assertEqual(widget2.text(), style2)
1750+
1751+ self.ui.list_widget.setCurrentRow(1)
1752+ current = self.ui.list_widget.item(1)
1753+ widget = self.ui.list_widget.itemWidget(current)
1754+ previous = self.ui.list_widget.item(0)
1755+ widget2 = self.ui.list_widget.itemWidget(previous)
1756+ name = os.path.basename('/home/tester/file2')
1757+ style = self.text_style.format(name, '/home/tester/file2',
1758+ 'white', 'white')
1759+ name2 = os.path.basename('/home/tester/file1')
1760+ style2 = self.text_style.format(name2, '/home/tester/file1',
1761+ '#333333', 'grey')
1762+ self.assertEqual(widget.text(), style)
1763+ self.assertEqual(widget2.text(), style2)
1764
1765=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
1766--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-08-20 14:09:45 +0000
1767+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-08-28 00:37:19 +0000
1768@@ -31,7 +31,10 @@
1769 UPLOADING,
1770 )
1771 from ubuntuone.controlpanel.gui.qt import systray
1772-from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
1773+from ubuntuone.controlpanel.gui.qt.tests import (
1774+ BaseTestCase,
1775+ FakeDesktopService,
1776+)
1777 from ubuntuone.controlpanel.tests import ROOT_PATH
1778
1779
1780@@ -59,19 +62,6 @@
1781 super(FakeMainWindow, self).__init__()
1782
1783
1784-class FakeDesktopService(object):
1785-
1786- """Fake QDesktopService."""
1787-
1788- data = {}
1789-
1790- @classmethod
1791- def openUrl(cls, url):
1792- """Fake openUrl."""
1793- FakeDesktopService.data['cls'] = cls
1794- FakeDesktopService.data['url'] = url
1795-
1796-
1797 class SystrayTestCase(BaseTestCase):
1798
1799 """Test the notification area icon."""
1800@@ -85,7 +75,8 @@
1801 self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
1802 self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1803 yield super(SystrayTestCase, self).setUp()
1804- self.patch(QtGui, "QDesktopServices", FakeDesktopService)
1805+ self.fake_desktop_service = FakeDesktopService()
1806+ self.patch(QtGui, "QDesktopServices", self.fake_desktop_service)
1807
1808 def assert_status_correct(self, status_bd, status_ui, action,
1809 callback=None):
1810@@ -278,30 +269,26 @@
1811 def test_open_u1_folder_action(self):
1812 """Test open_u1_folder_action."""
1813 self.ui.open_u1_folder.trigger()
1814- self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1815 expected_url = QtCore.QUrl(u'file://%s' % ROOT_PATH)
1816- self.assertEqual(FakeDesktopService.data['url'], expected_url)
1817+ self.assertEqual(self.fake_desktop_service.opened_url, expected_url)
1818
1819 def test_get_more_storage_action(self):
1820 """Test get_more_storage."""
1821 self.ui.get_more_storage.trigger()
1822- self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1823 expected_url = QtCore.QUrl(systray.GET_STORAGE_LINK)
1824- self.assertEqual(FakeDesktopService.data['url'], expected_url)
1825+ self.assertEqual(self.fake_desktop_service.opened_url, expected_url)
1826
1827 def test_go_to_web_action(self):
1828 """Test go_to_web."""
1829 self.ui.go_to_web.trigger()
1830- self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1831 expected_url = QtCore.QUrl(systray.DASHBOARD)
1832- self.assertEqual(FakeDesktopService.data['url'], expected_url)
1833+ self.assertEqual(self.fake_desktop_service.opened_url, expected_url)
1834
1835 def test_get_help_action(self):
1836 """Test get_help_online."""
1837 self.ui.get_help_online.trigger()
1838- self.assertEqual(FakeDesktopService.data['cls'], FakeDesktopService)
1839 expected_url = QtCore.QUrl(systray.GET_SUPPORT_LINK)
1840- self.assertEqual(FakeDesktopService.data['url'], expected_url)
1841+ self.assertEqual(self.fake_desktop_service.opened_url, expected_url)
1842
1843 def test_initialization(self):
1844 """Test that everything initializes correctly."""
1845@@ -353,7 +340,8 @@
1846 self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
1847 self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1848 yield super(TransfersMenuTestCase, self).setUp()
1849- self.patch(QtGui, "QDesktopServices", FakeDesktopService)
1850+ self.fake_desktop_service = FakeDesktopService()
1851+ self.patch(QtGui, "QDesktopServices", self.fake_desktop_service)
1852 self.patch(self.ui.transfers._parent.backend, "sync_menu",
1853 self.fake_sync_menu)
1854 self.recent_transfers = []
1855@@ -558,7 +546,8 @@
1856 self.patch(systray.TrayIcon, "startTimer", lambda s, x: None)
1857 self.patch(systray.TransfersMenu, "startTimer", lambda s, x: None)
1858 yield super(SharesTestCase, self).setUp()
1859- self.patch(QtGui, "QDesktopServices", FakeDesktopService)
1860+ self.fake_desktop_service = FakeDesktopService()
1861+ self.patch(QtGui, "QDesktopServices", self.fake_desktop_service)
1862 self.patch(self.ui.backend, "get_shares", self.fake_get_backend)
1863 self._shares_info = []
1864
1865@@ -596,4 +585,4 @@
1866 share_action = actions[7]
1867 share_action.trigger()
1868 expected = QtCore.QUrl(systray.ACCEPT_SHARES % volume_id)
1869- self.assertEqual(FakeDesktopService.data['url'], expected)
1870+ self.assertEqual(self.fake_desktop_service.opened_url, expected)
1871
1872=== modified file 'ubuntuone/controlpanel/sd_client/__init__.py'
1873--- ubuntuone/controlpanel/sd_client/__init__.py 2012-08-08 18:44:57 +0000
1874+++ ubuntuone/controlpanel/sd_client/__init__.py 2012-08-28 00:37:19 +0000
1875@@ -209,3 +209,23 @@
1876 def sync_menu(self):
1877 """Get the sync menu data from syncdaemon."""
1878 return self.proxy.sync_menu()
1879+
1880+ def get_public_files(self):
1881+ """Trigger an action to get the list of public files."""
1882+ return self.proxy.get_public_files()
1883+
1884+ def set_public_files_list_handler(self, handler):
1885+ """Set the public files list handler."""
1886+ return self.proxy.connect_signal('PublicFilesList', handler)
1887+
1888+ def change_public_access(self, path, is_public):
1889+ """Change the access type of the file."""
1890+ return self.proxy.change_public_access(path, is_public)
1891+
1892+ def set_public_access_changed_handler(self, handler):
1893+ """Set the status handler function."""
1894+ return self.proxy.connect_signal('PublicAccessChanged', handler)
1895+
1896+ def set_public_access_change_error_handler(self, handler):
1897+ """Set the status handler function."""
1898+ return self.proxy.connect_signal('PublicAccessChangeError', handler)
1899
1900=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
1901--- ubuntuone/controlpanel/tests/test_backend.py 2012-08-08 18:36:45 +0000
1902+++ ubuntuone/controlpanel/tests/test_backend.py 2012-08-28 00:37:19 +0000
1903@@ -161,6 +161,8 @@
1904 'is_error': '', 'is_connected': 'True', 'is_online': '',
1905 }
1906 self.status_changed_handler = None
1907+ self.public_files_list_handler = None
1908+ self.public_access_changed_handler = None
1909 self.subscribed_folders = []
1910 self.subscribed_shares = []
1911 self.actions = []
1912@@ -168,6 +170,13 @@
1913 self.folders = []
1914 self.volumes_refreshed = False
1915 self.menu_data = {'recent-transfers': (), 'uploading': ()}
1916+ self.publicfiles = [
1917+ {'path': '/home/file1', 'public_url': 'http:ubuntuone.com/asd123'},
1918+ {'path': '/home/file2', 'public_url': 'http:ubuntuone.com/qwe456'},
1919+ ]
1920+ self.public_access_info = {
1921+ 'public_url': 'http:ubuntuone.com/zxc789'
1922+ }
1923
1924 def get_throttling_limits(self):
1925 """Return the sample speed limits."""
1926@@ -331,6 +340,24 @@
1927 """Return the sync menu data."""
1928 return self.menu_data
1929
1930+ def get_public_files(self):
1931+ """Trigger the action to get the public files."""
1932+ if self.public_files_list_handler is not None:
1933+ self.public_files_list_handler(self.publicfiles)
1934+
1935+ def set_public_files_list_handler(self, handler):
1936+ """Return the handler to be called for the public files list."""
1937+ self.public_files_list_handler = handler
1938+
1939+ def change_public_access(self, path, is_public):
1940+ """Change the type access of a file."""
1941+ if self.public_access_changed_handler is not None and is_public:
1942+ self.public_access_changed_handler(self.public_access_info)
1943+
1944+ def set_public_access_changed_handler(self, handler):
1945+ """Return the handler to be called when a access type change."""
1946+ self.public_access_changed_handler = handler
1947+
1948
1949 class MockReplicationClient(CallRecorder):
1950 """A mock replication_client module."""
1951@@ -1709,3 +1736,40 @@
1952 result = yield self.be.sync_menu()
1953 expected = {'recent-transfers': (), 'uploading': ()}
1954 self.assertEqual(result, expected)
1955+
1956+ @inlineCallbacks
1957+ def test_get_public_files(self):
1958+ """Check that we get list of public files."""
1959+ data = []
1960+
1961+ def get_public_files_handler(publicfiles):
1962+ """Callback for get_public_files."""
1963+ data.append(publicfiles)
1964+
1965+ self.be.set_public_files_list_handler(get_public_files_handler)
1966+ yield self.be.get_public_files()
1967+ expected = [[{'path': '/home/file1',
1968+ 'public_url': 'http:ubuntuone.com/asd123'},
1969+ {'path': '/home/file2',
1970+ 'public_url': 'http:ubuntuone.com/qwe456'}]]
1971+ self.assertEqual(expected, data)
1972+
1973+ @inlineCallbacks
1974+ def test_get_shares(self):
1975+ """Check that we get the list of shares."""
1976+ result = yield self.be.get_shares()
1977+ self.assertEqual(result, [])
1978+
1979+ @inlineCallbacks
1980+ def test_change_public_access_set_public(self):
1981+ """Check that we get a notification when the access type change."""
1982+ data = []
1983+
1984+ def public_access_change_handler(info):
1985+ """Callback for get_public_files."""
1986+ data.append(info)
1987+
1988+ self.be.set_public_access_changed_handler(public_access_change_handler)
1989+ yield self.be.change_public_access('file1', True)
1990+ expected = {'public_url': 'http:ubuntuone.com/zxc789'}
1991+ self.assertEqual([expected], data)
1992
1993=== modified file 'ubuntuone/controlpanel/tests/test_sd_client.py'
1994--- ubuntuone/controlpanel/tests/test_sd_client.py 2012-03-29 21:37:22 +0000
1995+++ ubuntuone/controlpanel/tests/test_sd_client.py 2012-08-28 00:37:19 +0000
1996@@ -746,3 +746,31 @@
1997 """Handle error when retrieving current syncdaemon status."""
1998 self.patch(self.sd.proxy, 'get_status', self.fake_fail)
1999 yield self.assertFailure(self.sd.get_current_status(), CustomError)
2000+
2001+
2002+class PublicFilesTestCase(BaseTestCase):
2003+ """Test for the public files syncdaemon client methods."""
2004+
2005+ @inlineCallbacks
2006+ def test_set_public_files_list_handler(self):
2007+ """Connect a handler to the public files signal."""
2008+ sample_handler = object()
2009+ yield self.sd.set_public_files_list_handler(sample_handler)
2010+ self.assertEqual(sample_handler,
2011+ self.sd.proxy.called['PublicFilesList'])
2012+
2013+ @inlineCallbacks
2014+ def test_set_public_access_changed_handler(self):
2015+ """Connect a handler to the public access changed signal."""
2016+ sample_handler = object()
2017+ yield self.sd.set_public_access_changed_handler(sample_handler)
2018+ self.assertEqual(sample_handler,
2019+ self.sd.proxy.called['PublicAccessChanged'])
2020+
2021+ @inlineCallbacks
2022+ def test_set_public_access_change_error_handler(self):
2023+ """Connect a handler to the public access change error signal."""
2024+ sample_handler = object()
2025+ yield self.sd.set_public_access_change_error_handler(sample_handler)
2026+ self.assertEqual(sample_handler,
2027+ self.sd.proxy.called['PublicAccessChangeError'])

Subscribers

People subscribed via source and target branches