Merge lp:~facundo/magicicada-gui/more-api into lp:magicicada-gui
- more-api
- Merge into trunk
Proposed by
Facundo Batista
Status: | Merged | ||||
---|---|---|---|---|---|
Merge reported by: | Facundo Batista | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~facundo/magicicada-gui/more-api | ||||
Merge into: | lp:magicicada-gui | ||||
Diff against target: |
417 lines (+236/-31) 2 files modified
magicicada/syncdaemon.py (+81/-21) magicicada/tests/test_syncdaemon.py (+155/-10) |
||||
To merge this branch: | bzr merge lp:~facundo/magicicada-gui/more-api | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart | Approve | ||
Review via email: mp+25560@code.launchpad.net |
Commit message
Description of the change
More API implemented!
- on_started
- on_connected
- on_online
- content_queue
- meta_queue
To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote : | # |
Revision history for this message
Natalia Bidart (nataliabidart) wrote : | # |
I personally prefer name.startswith
Other than that, great job!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'magicicada/syncdaemon.py' |
2 | --- magicicada/syncdaemon.py 2010-05-18 22:00:50 +0000 |
3 | +++ magicicada/syncdaemon.py 2010-05-19 20:56:30 +0000 |
4 | @@ -29,11 +29,6 @@ |
5 | |
6 | from magicicada.helpers import NO_OP |
7 | |
8 | -# structures that hold content and queue information |
9 | -QueueData = collections.namedtuple('QueueData', 'operation path share node') |
10 | -State = collections.namedtuple('State', 'name description is_error ' |
11 | - 'is_connected is_online queues connection') |
12 | - |
13 | # regular expressions for parsing MetaQueue data |
14 | RE_OP_LISTDIR = re.compile("(ListDir)\(share_id=(.*?), node_id=(.*?), .*") |
15 | RE_OP_UNLINK = re.compile("(Unlink)\(share_id=(.*?), node_id=(.*?), .*") |
16 | @@ -50,19 +45,23 @@ |
17 | _attrs = ['name', 'description', 'is_error', 'is_connected', |
18 | 'is_online', 'queues', 'connection', 'is_started'] |
19 | |
20 | - def __init__(self, syncdaemon): |
21 | - self._sd = syncdaemon |
22 | + def __init__(self): |
23 | + # starting defaults |
24 | + self.name = '' |
25 | + self.description = '' |
26 | + self.is_error = False |
27 | + self.is_connected = False |
28 | + self.is_online = False |
29 | + self.queues = '' |
30 | + self.connection = '' |
31 | + self.is_started = False |
32 | |
33 | def __getattribute__(self, name): |
34 | """Return the value if there.""" |
35 | if name[0] == "_": |
36 | return object.__getattribute__(self, name) |
37 | else: |
38 | - if name == 'is_started': |
39 | - v = self._sd._get_started() |
40 | - else: |
41 | - v = self.__dict__.get(name) |
42 | - return v |
43 | + return self.__dict__[name] |
44 | |
45 | def _set(self, **data): |
46 | """Sets the attributes fromd data, if allowed.""" |
47 | @@ -80,16 +79,20 @@ |
48 | loop = DBusGMainLoop(set_as_default=True) |
49 | self._bus = bus = dbus.SessionBus(mainloop=loop) |
50 | self.sync_daemon_tool = tools.SyncDaemonTool(bus) |
51 | - self.current_state = State(self) |
52 | + self.current_state = State() |
53 | |
54 | # hook up for signals and store info for the shutdown |
55 | _signals = [ |
56 | (self._on_status_changed, 'Status', 'StatusChanged'), |
57 | (self._on_content_queue_changed, 'Status', 'ContentQueueChanged'), |
58 | + (self._on_name_owner_changed, None, 'NameOwnerChanged'), |
59 | ] |
60 | self._dbus_matches = [] |
61 | for method, dbus_lastname, signal_name in _signals: |
62 | - dbus_interface = 'com.ubuntuone.SyncDaemon.' + dbus_lastname |
63 | + if dbus_lastname is None: |
64 | + dbus_interface = None |
65 | + else: |
66 | + dbus_interface = 'com.ubuntuone.SyncDaemon.' + dbus_lastname |
67 | match = bus.add_signal_receiver(method, |
68 | dbus_interface=dbus_interface, |
69 | signal_name=signal_name) |
70 | @@ -99,11 +102,16 @@ |
71 | self.status_changed_callback = NO_OP |
72 | self.content_queue_changed_callback = NO_OP |
73 | self.meta_queue_changed_callback = NO_OP |
74 | + self.on_started_callback = NO_OP |
75 | + self.on_stopped_callback = NO_OP |
76 | + self.on_connected_callback = NO_OP |
77 | + self.on_disconnected_callback = NO_OP |
78 | + self.on_online_callback = NO_OP |
79 | + self.on_offline_callback = NO_OP |
80 | |
81 | # calls to obtain data from SDT |
82 | self._get_content_queue = self.sync_daemon_tool.waiting_content |
83 | self._get_meta_queue = self.sync_daemon_tool.waiting_metadata |
84 | - self._get_started = tools.is_running |
85 | self._do_start = self.sync_daemon_tool.start |
86 | self._do_quit = self.sync_daemon_tool.quit |
87 | self._do_connect = self.sync_daemon_tool.connect |
88 | @@ -128,6 +136,20 @@ |
89 | if self._mqcaller is not None and self._mqcaller.active(): |
90 | self._mqcaller.cancel() |
91 | |
92 | + def _on_name_owner_changed(self, name, oldowner, newowner): |
93 | + """Receives the NameOwnerChanged signal from DBus.""" |
94 | + if name == 'com.ubuntuone.SyncDaemon': |
95 | + old = bool(oldowner) |
96 | + new = bool(newowner) |
97 | + if old == new: |
98 | + raise ValueError("Owners should have changed! Old: %r " |
99 | + "New: %r" % (oldowner, newowner)) |
100 | + self.current_state._set(is_started=new) |
101 | + if new: |
102 | + self.on_started_callback() |
103 | + else: |
104 | + self.on_stopped_callback() |
105 | + |
106 | def _on_status_changed(self, state): |
107 | """Receives the StatusChanged signal and send its data.""" |
108 | name = state['name'] |
109 | @@ -137,12 +159,44 @@ |
110 | is_online = bool(state['is_online']) |
111 | queues = state['queues'] |
112 | connection = state['connection'] |
113 | + |
114 | + # check status changes to call callbacks |
115 | + if is_connected and not self.current_state.is_connected: |
116 | + self.on_connected_callback() |
117 | + if not is_connected and self.current_state.is_connected: |
118 | + self.on_disconnected_callback() |
119 | + if is_online and not self.current_state.is_online: |
120 | + self.on_online_callback() |
121 | + if not is_online and self.current_state.is_online: |
122 | + self.on_offline_callback() |
123 | + |
124 | + |
125 | + # set current state to new values and call status changed cb |
126 | self.current_state._set(**state) |
127 | self.status_changed_callback(name, description, is_error, |
128 | is_connected, is_online, |
129 | queues, connection) |
130 | + |
131 | + # if corresponds, supervise MQ |
132 | self._check_mq() |
133 | |
134 | + def _generate_cq_info(self, data): |
135 | + """Genereates an api friendly version of the data.""" |
136 | + all_items = [] |
137 | + for d in data: |
138 | + cq = QueueData(operation=d['operation'], path=d['path'], |
139 | + node=d['node'], share=d['share']) |
140 | + all_items.append(cq) |
141 | + return all_items |
142 | + |
143 | + @property |
144 | + def content_queue(self): |
145 | + """Returns the last known CQ info.""" |
146 | + if self._last_CQ_data is None: |
147 | + return [] |
148 | + else: |
149 | + return self._generate_cq_info(self._last_CQ_data) |
150 | + |
151 | def _process_cq(self, data): |
152 | """Processes ContentQueue data.""" |
153 | # if same data than before, abort notification; else store it for later |
154 | @@ -150,12 +204,7 @@ |
155 | return |
156 | self._last_CQ_data = data |
157 | |
158 | - all_items = [] |
159 | - for d in data: |
160 | - cq = QueueData(operation=d['operation'], path=d['path'], |
161 | - node=d['node'], share=d['share']) |
162 | - all_items.append(cq) |
163 | - |
164 | + all_items = self._generate_cq_info(data) |
165 | self.content_queue_changed_callback(all_items) |
166 | |
167 | def _on_content_queue_changed(self, _): |
168 | @@ -164,6 +213,17 @@ |
169 | d = self._get_content_queue() |
170 | d.addCallback(self._process_cq) |
171 | |
172 | + @property |
173 | + def meta_queue(self): |
174 | + """Returns the last known MQ info.""" |
175 | + if self._last_MQ_data is None: |
176 | + return [] |
177 | + else: |
178 | + all_items = [] |
179 | + for d in self._last_MQ_data: |
180 | + all_items.append(self._parse_mq(d)) |
181 | + return all_items |
182 | + |
183 | def _parse_mq(self, data): |
184 | """Parse MetaQueue string to extract its data.""" |
185 | if data in ('AccountInquiry', 'FreeSpaceInquiry', 'GetPublicFiles', |
186 | |
187 | === modified file 'magicicada/tests/test_syncdaemon.py' |
188 | --- magicicada/tests/test_syncdaemon.py 2010-05-18 19:53:38 +0000 |
189 | +++ magicicada/tests/test_syncdaemon.py 2010-05-19 20:56:30 +0000 |
190 | @@ -173,6 +173,44 @@ |
191 | self.assertEqual(received.share, original['share']) |
192 | self.assertEqual(received.node, original['node']) |
193 | |
194 | + def test_CQ_state_none(self): |
195 | + """Check the ContentQueue info, being none.""" |
196 | + self.assertEqual(len(self.sd.content_queue), 0) |
197 | + |
198 | + def test_CQ_state_one(self): |
199 | + """Check the ContentQueue info, being one.""" |
200 | + d = dict(operation='oper', path='path', share='share', node='node') |
201 | + self.sd._process_cq([d]) |
202 | + self.assertEqual(len(self.sd.content_queue), 1) |
203 | + |
204 | + # check the data |
205 | + cqit = self.sd.content_queue[0] |
206 | + self.assertEqual(cqit.operation, 'oper') |
207 | + self.assertEqual(cqit.path, 'path') |
208 | + self.assertEqual(cqit.share, 'share') |
209 | + self.assertEqual(cqit.node, 'node') |
210 | + |
211 | + def test_CQ_state_several(self): |
212 | + """Check the ContentQueue info, several calls, last one is bigger.""" |
213 | + c = dict(operation='oper1', path='path1', share='share1', node='node1') |
214 | + d = dict(operation='oper2', path='path2', share='share2', node='node2') |
215 | + self.sd._process_cq([c]) |
216 | + self.sd._process_cq([d]) |
217 | + self.sd._process_cq([c, d]) |
218 | + self.assertEqual(len(self.sd.content_queue), 2) |
219 | + |
220 | + # check the data |
221 | + cqit = self.sd.content_queue[0] |
222 | + self.assertEqual(cqit.operation, 'oper1') |
223 | + self.assertEqual(cqit.path, 'path1') |
224 | + self.assertEqual(cqit.share, 'share1') |
225 | + self.assertEqual(cqit.node, 'node1') |
226 | + cqit = self.sd.content_queue[1] |
227 | + self.assertEqual(cqit.operation, 'oper2') |
228 | + self.assertEqual(cqit.path, 'path2') |
229 | + self.assertEqual(cqit.share, 'share2') |
230 | + self.assertEqual(cqit.node, 'node2') |
231 | + |
232 | |
233 | class MetaQueueChangedTests(SignalsBaseTest): |
234 | """Check the MetaQueueChanged handling.""" |
235 | @@ -373,37 +411,74 @@ |
236 | deferred = defer.Deferred() |
237 | return deferred |
238 | |
239 | + def test_MQ_state_none(self): |
240 | + """Check the MetaQueue info, being none.""" |
241 | + self.assertEqual(len(self.sd.meta_queue), 0) |
242 | + |
243 | + def test_MQ_state_one(self): |
244 | + """Check the MetaQueue info, being one.""" |
245 | + self.sd._process_mq(['ListShares']) |
246 | + self.assertEqual(len(self.sd.meta_queue), 1) |
247 | + |
248 | + # check the data |
249 | + mqit = self.sd.meta_queue[0] |
250 | + self.assertEqual(mqit.operation, 'ListShares') |
251 | + self.assertEqual(mqit.path, None) |
252 | + self.assertEqual(mqit.share, None) |
253 | + self.assertEqual(mqit.node, None) |
254 | + |
255 | + def test_MQ_state_several(self): |
256 | + """Check the MetaQueue info, several calls, last one is bigger.""" |
257 | + cmd1 = 'MakeDir(share_id=a, parent_id=b, name=c, marker=d)' |
258 | + cmd2 = 'GetPublicFiles' |
259 | + self.sd._process_mq([cmd1]) |
260 | + self.sd._process_mq([cmd2]) |
261 | + self.sd._process_mq([cmd1, cmd2]) |
262 | + self.assertEqual(len(self.sd.meta_queue), 2) |
263 | + |
264 | + # check the data |
265 | + mqit = self.sd.meta_queue[0] |
266 | + self.assertEqual(mqit.operation, 'MakeDir') |
267 | + self.assertEqual(mqit.path, '/?.../c') |
268 | + self.assertEqual(mqit.share, 'a') |
269 | + self.assertEqual(mqit.node, None) |
270 | + mqit = self.sd.meta_queue[1] |
271 | + self.assertEqual(mqit.operation, 'GetPublicFiles') |
272 | + self.assertEqual(mqit.path, None) |
273 | + self.assertEqual(mqit.share, None) |
274 | + self.assertEqual(mqit.node, None) |
275 | + |
276 | |
277 | class StateTests(unittest.TestCase): |
278 | """Test State class.""" |
279 | |
280 | def test_initial(self): |
281 | """Initial state for vals.""" |
282 | - st = State(None) |
283 | - self.assertEqual(st.name, None) |
284 | + st = State() |
285 | + self.assertEqual(st.name, '') |
286 | |
287 | def test_set_one_value(self): |
288 | """Set one value.""" |
289 | - st = State(None) |
290 | + st = State() |
291 | st._set(name=55) |
292 | |
293 | # check the one is set, the rest not |
294 | self.assertEqual(st.name, 55) |
295 | - self.assertEqual(st.description, None) |
296 | + self.assertEqual(st.description, '') |
297 | |
298 | def test_set_two_values(self): |
299 | """Set two values.""" |
300 | - st = State(None) |
301 | + st = State() |
302 | st._set(name=55, description=77) |
303 | |
304 | # check those two are set, the rest not |
305 | self.assertEqual(st.name, 55) |
306 | self.assertEqual(st.description, 77) |
307 | - self.assertEqual(st.is_error, None) |
308 | + self.assertFalse(st.is_error) |
309 | |
310 | def test_bad_value(self): |
311 | """Set a value that should not.""" |
312 | - st = State(None) |
313 | + st = State() |
314 | self.assertRaises(AttributeError, st._set, not_really_allowed=44) |
315 | |
316 | |
317 | @@ -429,10 +504,15 @@ |
318 | f = lambda *a, **k: setattr(self, 'called', True) |
319 | setattr(obj, method_name, f) |
320 | |
321 | + def flag_called(self, obj, method_name): |
322 | + """Replace callback to flag called.""" |
323 | + f = lambda *a, **k: setattr(self, 'called', True) |
324 | + setattr(obj, method_name, f) |
325 | + |
326 | def test_defaults_are_callable(self): |
327 | """Check the attributes are callable.""" |
328 | meths = (self.sd._get_content_queue, self.sd._get_meta_queue, |
329 | - self.sd._get_started, self.sd._do_start, self.sd._do_quit, |
330 | + self.sd._do_start, self.sd._do_quit, |
331 | self.sd._do_connect, self.sd._do_disconnect) |
332 | for meth in meths: |
333 | self.assertTrue(callable(meth), "Meth %r is not callable" % meth) |
334 | @@ -471,12 +551,13 @@ |
335 | |
336 | def test_is_started_yes(self): |
337 | """Check is_started, True.""" |
338 | - self.sd._get_started = lambda: True |
339 | + # simulate the signal that indicates the name was registered |
340 | + self.sd._on_name_owner_changed('com.ubuntuone.SyncDaemon', '', 'yes') |
341 | self.assertTrue(self.sd.current_state.is_started) |
342 | |
343 | def test_is_started_no(self): |
344 | """Check is_started, False.""" |
345 | - self.sd._get_started = lambda: False |
346 | + self.sd._on_name_owner_changed('com.ubuntuone.SyncDaemon', 'yes', '') |
347 | self.assertFalse(self.sd.current_state.is_started) |
348 | |
349 | def test_start(self): |
350 | @@ -502,3 +583,67 @@ |
351 | self.mpatch_called(self.sd, '_do_disconnect') |
352 | self.sd.disconnect() |
353 | self.assertTrue(self.called) |
354 | + |
355 | + def test_on_started(self): |
356 | + """Called when SD started.""" |
357 | + self.flag_called(self.sd, 'on_started_callback') |
358 | + |
359 | + # simulate the signal that indicates the name was registered |
360 | + self.sd._on_name_owner_changed('com.ubuntuone.SyncDaemon', '', 'yes') |
361 | + self.assertTrue(self.called) |
362 | + |
363 | + def test_on_stopped(self): |
364 | + """Called when SD stopped.""" |
365 | + self.flag_called(self.sd, 'on_stopped_callback') |
366 | + |
367 | + # simulate the signal that indicates the name was registered |
368 | + self.sd._on_name_owner_changed('com.ubuntuone.SyncDaemon', 'yes', '') |
369 | + self.assertTrue(self.called) |
370 | + |
371 | + def test_on_connected(self): |
372 | + """Called when SD connected.""" |
373 | + self.flag_called(self.sd, 'on_connected_callback') |
374 | + |
375 | + # first signal with connected in True |
376 | + d = dict(name='name', description='description', is_error='', |
377 | + is_connected='True', is_online='', queues='queues', |
378 | + connection='connection') |
379 | + self.sd._on_status_changed(d) |
380 | + self.assertTrue(self.called) |
381 | + |
382 | + def test_on_disconnected(self): |
383 | + """Called when SD disconnected.""" |
384 | + self.flag_called(self.sd, 'on_disconnected_callback') |
385 | + |
386 | + # connect and disconnect |
387 | + d = dict(name='name', description='description', is_error='', |
388 | + is_connected='True', is_online='', queues='queues', |
389 | + connection='connection') |
390 | + self.sd._on_status_changed(d) |
391 | + d['is_connected'] = '' |
392 | + self.sd._on_status_changed(d) |
393 | + self.assertTrue(self.called) |
394 | + |
395 | + def test_on_online(self): |
396 | + """Called when SD goes online.""" |
397 | + self.flag_called(self.sd, 'on_online_callback') |
398 | + |
399 | + # first signal with online in True |
400 | + d = dict(name='name', description='description', is_error='', |
401 | + is_connected='True', is_online='True', queues='queues', |
402 | + connection='connection') |
403 | + self.sd._on_status_changed(d) |
404 | + self.assertTrue(self.called) |
405 | + |
406 | + def test_on_offline(self): |
407 | + """Called when SD goes offline.""" |
408 | + self.flag_called(self.sd, 'on_offline_callback') |
409 | + |
410 | + # go online and then offline |
411 | + d = dict(name='name', description='description', is_error='', |
412 | + is_connected='True', is_online='True', queues='queues', |
413 | + connection='connection') |
414 | + self.sd._on_status_changed(d) |
415 | + d['is_online'] = '' |
416 | + self.sd._on_status_changed(d) |
417 | + self.assertTrue(self.called) |
The resto of the simple API from the bug