Merge lp:~mikemc/ubuntuone-control-panel/fix-sync-status into lp:ubuntuone-control-panel

Proposed by Mike McCracken on 2012-09-05
Status: Merged
Approved by: Alejandro J. Cura on 2012-09-06
Approved revision: 359
Merged at revision: 356
Proposed branch: lp:~mikemc/ubuntuone-control-panel/fix-sync-status
Merge into: lp:ubuntuone-control-panel
Diff against target: 313 lines (+92/-56)
9 files modified
ubuntuone/controlpanel/backend.py (+21/-17)
ubuntuone/controlpanel/dbus_service.py (+1/-2)
ubuntuone/controlpanel/dbustests/test_dbus_service.py (+12/-25)
ubuntuone/controlpanel/gui/qt/filesyncstatus.py (+1/-1)
ubuntuone/controlpanel/gui/qt/systray.py (+1/-1)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+1/-0)
ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py (+4/-3)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+4/-4)
ubuntuone/controlpanel/tests/test_backend.py (+47/-3)
To merge this branch: bzr merge lp:~mikemc/ubuntuone-control-panel/fix-sync-status
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve on 2012-09-06
Roberto Alsina (community) 2012-09-05 Approve on 2012-09-06
Review via email: mp+122974@code.launchpad.net

Commit Message

- Fix sync indicator in control panel main window. (LP: #1044197)

Description of the Change

- Fix sync indicator in control panel main window. (LP: #1044197)

Added support in controlbackend for multiple status handlers.

Both the filsyncstatus.py widget in the main window and the menu item
in systray.py try to set the handler. This results in the
filesyncstatus widget no longer being updated, as the single variable
holding the callback in controlbackend is overwritten.

The issue does not appear on linux, because although the
controlbackend only has one variable, when it is changed, it wraps the
new callback and calls sd_client.set_status_changed_handler() with
that wrapper. On linux, the dbus implementation of the proxy that
sd_client uses *ADDS* the wrapper, while on the other platforms the
twisted implementation just overwrites the old handler.

This branch changes it so that we only ever set the sd_client callback
once, to a function in controlbackend that calls each of a list of
callbacks.

The filesyncstatus and systray widgets now call an
add_status_changed_handler function that adds to that list instead of
setting the single callback via a property.

Tests are included that use the new API, but would break if the new
API behaved the same as the old one.

IRL test shows this working on darwin. I had great difficulty getting
windows running from source, so I didn't do an IRL test on windows.

To post a comment you must log in.
Roberto Alsina (ralsina) wrote :

Looks good to me!

review: Approve
Alejandro J. Cura (alecu) wrote :

Looks great. I requested on IRC a small removal, will approve when that gets pushed.

review: Needs Fixing
359. By Mike McCracken on 2012-09-06

remove unnecessary property for accessing status_changed_handlers

Alejandro J. Cura (alecu) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/backend.py'
2--- ubuntuone/controlpanel/backend.py 2012-08-30 00:18:06 +0000
3+++ ubuntuone/controlpanel/backend.py 2012-09-06 16:37:20 +0000
4@@ -130,7 +130,7 @@
5
6 def __init__(self, shutdown_func=None):
7 """Initialize the web_client."""
8- self._status_changed_handler = None
9+ self._status_changed_handlers = []
10 self._credentials = None
11 self._volumes = {} # cache last known volume info
12
13@@ -139,6 +139,7 @@
14
15 self.login_client = CredentialsManagementTool()
16 self.sd_client = sd_client.SyncDaemonClient()
17+ self.is_sd_client_handler_set = False
18 self.wc = WebClient(self.get_credentials)
19
20 logger.info('ControlBackend: instance started.')
21@@ -202,24 +203,27 @@
22 else:
23 return result
24
25- def _set_status_changed_handler(self, handler):
26- """Set 'handler' to be called when file sync status changes."""
27- logger.debug('setting status_changed_handler to %r', handler)
28-
29- def process_and_callback(status):
30- """Process syncdaemon's status and callback 'handler'."""
31- result = self._process_file_sync_status(status)
32+ def process_status_and_call_handlers(self, status):
33+ """Process syncdaemon's status and call each of the handlers."""
34+ result = self._process_file_sync_status(status)
35+ for handler in self._status_changed_handlers:
36 handler(result)
37
38- self._status_changed_handler = handler
39- self.sd_client.set_status_changed_handler(process_and_callback)
40-
41- def _get_status_changed_handler(self):
42- """Return the handler to be called when file sync status changes."""
43- return self._status_changed_handler
44-
45- status_changed_handler = property(_get_status_changed_handler,
46- _set_status_changed_handler)
47+ def add_status_changed_handler(self, handler):
48+ """Add 'handler' to list of callbacks for file sync status changes."""
49+
50+ if handler in self._status_changed_handlers:
51+ logger.debug('Handler %r already in list of handlers, ignoring.' %
52+ handler)
53+ return
54+
55+ logger.debug('adding %r to list of status_changed_handlers', handler)
56+
57+ self._status_changed_handlers.append(handler)
58+ if not self.is_sd_client_handler_set:
59+ cb = self.process_status_and_call_handlers
60+ self.sd_client.set_status_changed_handler(cb)
61+ self.is_sd_client_handler_set = True
62
63 @inlineCallbacks
64 def _process_device_web_info(self, devices,
65
66=== modified file 'ubuntuone/controlpanel/dbus_service.py'
67--- ubuntuone/controlpanel/dbus_service.py 2012-01-23 20:15:09 +0000
68+++ ubuntuone/controlpanel/dbus_service.py 2012-09-06 16:37:20 +0000
69@@ -278,8 +278,7 @@
70 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
71 def file_sync_status(self):
72 """Get the status of the file sync service."""
73- if self.backend.status_changed_handler is None:
74- self.backend.status_changed_handler = self.process_status
75+ self.backend.add_status_changed_handler(self.process_status)
76
77 d = self.backend.file_sync_status()
78 d.addCallback(self.process_status)
79
80=== modified file 'ubuntuone/controlpanel/dbustests/test_dbus_service.py'
81--- ubuntuone/controlpanel/dbustests/test_dbus_service.py 2011-10-24 22:14:32 +0000
82+++ ubuntuone/controlpanel/dbustests/test_dbus_service.py 2012-09-06 16:37:20 +0000
83@@ -148,7 +148,7 @@
84
85 def __init__(self, shutdown_func=None):
86 MockBackend.shutdown_func = shutdown_func
87- self._status_changed_handler = []
88+ self._status_changed_handlers = []
89
90 def _process(self, result):
91 """Process the request with the given result."""
92@@ -157,23 +157,10 @@
93 return defer.fail(self.exception(result))
94 return defer.succeed(result)
95
96- def _set_status_changed_handler(self, handler):
97+ def add_status_changed_handler(self, handler):
98 """Set 'handler' to be called when file sync status changes."""
99- if handler is not None:
100- self._status_changed_handler.append(handler)
101- else:
102- self._status_changed_handler = []
103-
104- def _get_status_changed_handler(self):
105- """Return the handler to be called when file sync status changes."""
106- if len(self._status_changed_handler) > 0:
107- result = self._status_changed_handler[-1]
108- else:
109- result = None
110- return result
111-
112- status_changed_handler = property(_get_status_changed_handler,
113- _set_status_changed_handler)
114+ if handler not in self._status_changed_handlers:
115+ self._status_changed_handlers.append(handler)
116
117 def account_info(self):
118 """Get the user account info."""
119@@ -751,29 +738,29 @@
120 return self.assert_correct_status_signal(*args, expected_msg=arg)
121
122 def test_status_changed_handler(self):
123- """The status changed handler is properly set."""
124+ """There is no default status changed handler."""
125 be = MockBackend()
126 dbus_service.ControlPanelBackend(backend=be)
127-
128- self.assertEqual(be.status_changed_handler, None)
129+ # pylint: disable=W0212
130+ self.assertEqual(be._status_changed_handlers, [])
131
132 def test_status_changed_handler_after_status_requested(self):
133- """The status changed handler is properly set."""
134+ """file_sync_status sets the callback in the CP backend."""
135 be = MockBackend()
136 cpbe = dbus_service.ControlPanelBackend(backend=be)
137 cpbe.file_sync_status()
138-
139- self.assertEqual(be.status_changed_handler, cpbe.process_status)
140+ # pylint: disable=W0212
141+ self.assertEqual(be._status_changed_handlers, [cpbe.process_status])
142
143 def test_status_changed_handler_after_status_requested_twice(self):
144- """The status changed handler is properly set."""
145+ """The Handler is only added once after 2 calls to file_sync_status."""
146 be = MockBackend()
147 cpbe = dbus_service.ControlPanelBackend(backend=be)
148 cpbe.file_sync_status()
149 cpbe.file_sync_status()
150
151 # pylint: disable=W0212
152- self.assertEqual(be._status_changed_handler, [cpbe.process_status])
153+ self.assertEqual(be._status_changed_handlers, [cpbe.process_status])
154
155
156 class ShutdownTestCase(BaseTestCase):
157
158=== modified file 'ubuntuone/controlpanel/gui/qt/filesyncstatus.py'
159--- ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2012-08-08 18:36:45 +0000
160+++ ubuntuone/controlpanel/gui/qt/filesyncstatus.py 2012-09-06 16:37:20 +0000
161@@ -61,7 +61,7 @@
162 """Load specific tab info."""
163 info = yield self.backend.file_sync_status()
164 self.process_info(info)
165- self.backend.status_changed_handler = self.process_info
166+ self.backend.add_status_changed_handler(self.process_info)
167
168 @log_call(logger.debug)
169 def process_info(self, status):
170
171=== modified file 'ubuntuone/controlpanel/gui/qt/systray.py'
172--- ubuntuone/controlpanel/gui/qt/systray.py 2012-08-20 18:53:30 +0000
173+++ ubuntuone/controlpanel/gui/qt/systray.py 2012-09-06 16:37:20 +0000
174@@ -127,6 +127,7 @@
175 self.get_more_storage = None
176 self.transfers = None
177 self.open_u1_folder = None
178+ self.backend.add_status_changed_handler(self._process_status)
179
180 self.load_menu()
181 # Refresh the Shares every five minutes if needed.
182@@ -204,7 +205,6 @@
183 """Update Ubuntu One status."""
184 info = yield self.backend.file_sync_status()
185 self._process_status(info)
186- self.backend.status_changed_handler = self._process_status
187
188 def _process_status(self, status):
189 """Match status with signals."""
190
191=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
192--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-08-28 00:32:41 +0000
193+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-09-06 16:37:20 +0000
194@@ -123,6 +123,7 @@
195 next_result = []
196 exposed_methods = [
197 'account_info',
198+ 'add_status_changed_handler',
199 'build_signed_iri',
200 'change_device_settings',
201 'change_file_sync_settings',
202
203=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py'
204--- ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-08-08 18:36:45 +0000
205+++ ubuntuone/controlpanel/gui/qt/tests/test_filesyncstatus.py 2012-09-06 16:37:20 +0000
206@@ -109,10 +109,11 @@
207 actual_text = unicode(self.ui.ui.sync_status_label.text())
208 self.assertEqual(qt.FILE_SYNC_STARTING, actual_text)
209
210- def test_process_info_changed(self):
211+ def test_status_changed_handler_is_set(self):
212 """Backend's file sync status changed callback is connected."""
213- self.assertEqual(self.ui.backend.status_changed_handler,
214- self.ui.process_info)
215+ # pylint: disable=W0212
216+ self.assertEqual(self.ui.backend._called['add_status_changed_handler'],
217+ ((self.ui.process_info,), {}))
218
219 def test_file_sync_status_is_requested_on_load(self):
220 """The file sync status is requested to the backend."""
221
222=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
223--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-08-28 00:32:41 +0000
224+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-09-06 16:37:20 +0000
225@@ -107,11 +107,11 @@
226 actual_text = unicode(self.ui.status.text())
227 self.assertEqual(qt.FILE_SYNC_STARTING, actual_text)
228
229- def test_process_info_changed(self):
230+ def test_status_change_handler_set(self):
231 """Backend's file sync status changed callback is connected."""
232- self.ui.refresh_status()
233- self.assertEqual(self.ui.backend.status_changed_handler,
234- self.ui._process_status)
235+ # pylint: disable=W0212
236+ self.assertEqual(self.ui.backend._called['add_status_changed_handler'],
237+ ((self.ui._process_status,), {}))
238
239 def test_process_info_disabled(self):
240 """File sync status disabled update the label."""
241
242=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
243--- ubuntuone/controlpanel/tests/test_backend.py 2012-08-30 00:13:45 +0000
244+++ ubuntuone/controlpanel/tests/test_backend.py 2012-09-06 16:37:20 +0000
245@@ -1419,8 +1419,8 @@
246 self.assertEqual(self.was_disabled, self.be.file_sync_disabled)
247
248 def test_status_changed(self):
249- """The file_sync_status is the status changed handler."""
250- self.be.status_changed_handler = self._set_called
251+ """The status changed handler is called with a processed status."""
252+ self.be.add_status_changed_handler(self._set_called)
253 status = {'name': 'foo', 'description': 'bar', 'is_error': '',
254 'is_connected': '', 'is_online': '', 'queues': ''}
255 self.be.sd_client.status_changed_handler(status)
256@@ -1430,12 +1430,56 @@
257
258 def test_invalid_status_type(self):
259 """Check that the method return None with invalid types."""
260- self.be.status_changed_handler = self._set_called
261+ self.be.add_status_changed_handler(self._set_called)
262 status = 3
263 self.be.sd_client.status_changed_handler(status)
264
265 self.assertEqual(self._called, ((None,), {}))
266
267+ def test_add_same_status_handler_twice(self):
268+ """Should ignore adding same handler twice."""
269+ self.call_count = 0
270+
271+ def inc_count(status):
272+ """Fake Callback."""
273+ self.call_count += 1
274+
275+ self.addCleanup(delattr, self, "call_count")
276+
277+ self.be.add_status_changed_handler(inc_count)
278+ self.be.add_status_changed_handler(inc_count)
279+
280+ status = {'name': 'foo', 'description': 'bar', 'is_error': '',
281+ 'is_connected': '', 'is_online': '', 'queues': ''}
282+ self.be.sd_client.status_changed_handler(status)
283+
284+ self.assertEqual(self.call_count, 1)
285+ self.assertEqual(self.be._status_changed_handlers,
286+ [inc_count])
287+
288+ def test_add_two_status_handlers(self):
289+ """Should accept and call multiple status handlers."""
290+ self.call_count_a = 0
291+ self.call_count_b = 0
292+
293+ def inc_a(status):
294+ """Fake status handler #1"""
295+ self.call_count_a += 1
296+
297+ def inc_b(status):
298+ """Fake status handler #2"""
299+ self.call_count_b += 1
300+
301+ self.addCleanup(delattr, self, "call_count_a")
302+ self.addCleanup(delattr, self, "call_count_b")
303+
304+ self.be.add_status_changed_handler(inc_a)
305+ self.be.add_status_changed_handler(inc_b)
306+
307+ self.be.sd_client.status_changed_handler({})
308+ self.assertEqual(self.call_count_b, 1)
309+ self.assertEqual(self.call_count_a, 1)
310+
311
312 class BackendSyncStatusIfDisabledTestCase(BackendSyncStatusTestCase):
313 """Syncdaemon state for the backend when file sync is disabled."""

Subscribers

People subscribed via source and target branches