Merge lp:~facundo/magicicada-gui/show-md into lp:magicicada-gui

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 44
Merged at revision: 47
Proposed branch: lp:~facundo/magicicada-gui/show-md
Merge into: lp:magicicada-gui
Diff against target: 348 lines (+123/-24)
4 files modified
magicicada/dbusiface.py (+30/-3)
magicicada/syncdaemon.py (+9/-0)
magicicada/tests/test_dbusiface.py (+49/-13)
magicicada/tests/test_syncdaemon.py (+35/-8)
To merge this branch: bzr merge lp:~facundo/magicicada-gui/show-md
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+27406@code.launchpad.net

Description of the change

Get and deliver metadata for a given path

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Docstring """Processes...""" needs fixing to """Process""". Same for "Gets" and "Returns".

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

I was just thinking... what if the "code" for syncdaemon.get_metadata is the path per se? I think is key enough to guarantee that the metadata result is the one that the GUI/user is waiting for.

I just realized this when implementing the GUI part. Let me know what you think.

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

2 more things are needed:

(a) Could you please return a python dictionary instead a dbus dictionary? I'd want to keep dbus out the GTK layer.

(b) If the path doesn't exist, we'd need a special value, not a string saying "Not a valid path". This way we can ease the future translations, and we can detect this special case in a proper way.

Revision history for this message
Facundo Batista (facundo) wrote :

After talking through IRC, what will do is:

- Will use "path" itself as the "code".

- The constant used in dbusiface.py in case of no path will be NOT_SYNCHED_PATH

- Will return a Py dict, instead a str

lp:~facundo/magicicada-gui/show-md updated
44. By Facundo Batista

Some docstring fixes and small changes.

Revision history for this message
Facundo Batista (facundo) wrote :

I just make it to return the DBus types to the GUI.

Naty does not want it, but I thought duck typing should work... even thinking that, I tried to make tests to check that the types were converted, but...

>>> isinstance(dbus.String(u'a'), unicode)
True

and

>>> isinstance(dbus.Dictionary({dbus.String('a'): dbus.String(3)}), dict)
True

So, actually, it would be too tough and too fake to assert that, and it should work rightaway.

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

Nice!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'magicicada/dbusiface.py'
2--- magicicada/dbusiface.py 2010-06-05 15:57:34 +0000
3+++ magicicada/dbusiface.py 2010-06-14 18:14:25 +0000
4@@ -50,11 +50,19 @@
5 "(Move)\(share_id=(.*?), node_id=(.*?), old_parent_id=(.*?), "
6 "new_parent_id=(.*?), new_name=(.*?)\)")
7
8+# DBus exceptions store the type inside, as a string :|
9+DBUSERR_NOREPLY = 'org.freedesktop.DBus.Error.NoReply'
10+DBUSERR_NAMENOOWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
11+DBUSERR_PYKEYERROR = 'org.freedesktop.DBus.Python.KeyError'
12+
13+# some constants
14+NOT_SYNCHED_PATH = "Not a valid path!"
15+
16
17 def _is_retry_exception(err):
18 """Check if the exception is a retry one."""
19 if isinstance(err, dbus.exceptions.DBusException):
20- if err.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
21+ if err.get_dbus_name() == DBUSERR_NOREPLY:
22 return True
23 return False
24
25@@ -323,8 +331,7 @@
26 try:
27 self._bus.get_name_owner('com.ubuntuone.SyncDaemon')
28 except dbus.exceptions.DBusException, err:
29- if err.get_dbus_name() != \
30- 'org.freedesktop.DBus.Error.NameHasNoOwner':
31+ if err.get_dbus_name() != DBUSERR_NAMENOOWNER:
32 raise
33 started = False
34 else:
35@@ -381,3 +388,23 @@
36 d = self.sync_daemon_tool.list_shared()
37 d.addCallback(process)
38 return d
39+
40+ @retryable
41+ def get_metadata(self, path):
42+ """Return the raw metadata."""
43+ logger.info("Getting metadata for %r", path)
44+
45+ def fix_failure(failure):
46+ """Get the failure and return a nice message."""
47+ if failure.check(dbus.exceptions.DBusException):
48+ if failure.value.get_dbus_name() == DBUSERR_PYKEYERROR:
49+ return NOT_SYNCHED_PATH
50+ return failure
51+
52+ def process(metadata):
53+ logger.debug("Got metadata for path %r: %r", path, metadata)
54+ return dict(metadata)
55+
56+ d = self.sync_daemon_tool.get_metadata(path)
57+ d.addCallbacks(process, fix_failure)
58+ return d
59
60=== modified file 'magicicada/syncdaemon.py'
61--- magicicada/syncdaemon.py 2010-06-11 20:09:11 +0000
62+++ magicicada/syncdaemon.py 2010-06-14 18:14:25 +0000
63@@ -104,6 +104,7 @@
64 self.on_folders_changed_callback = NO_OP
65 self.on_shares_to_me_changed_callback = NO_OP
66 self.on_shares_to_others_changed_callback = NO_OP
67+ self.on_metadata_ready_callback = None # mandatory
68
69 # mq needs to be polled to know progress
70 self._mqcaller = None
71@@ -282,3 +283,11 @@
72 """Tell the SyncDaemon that the user wants it to disconnect."""
73 logger.info("Telling u1.SD to disconnect")
74 self.dbus.disconnect()
75+
76+ def get_metadata(self, path):
77+ """Get the metadata for given path."""
78+ if self.on_metadata_ready_callback is None:
79+ raise ValueError("Missing the mandatory cback for get_metadata.")
80+
81+ d = self.dbus.get_metadata(path)
82+ d.addCallback(lambda resp: self.on_metadata_ready_callback(path, resp))
83
84=== modified file 'magicicada/tests/test_dbusiface.py'
85--- magicicada/tests/test_dbusiface.py 2010-06-03 03:58:02 +0000
86+++ magicicada/tests/test_dbusiface.py 2010-06-14 18:14:25 +0000
87@@ -43,7 +43,7 @@
88 del self._callbacks[(dbus_interface, signal_name)]
89
90 def get_name_owner(self, name):
91- """Fakes the response of the method."""
92+ """Fake the response of the method."""
93 assert name == 'com.ubuntuone.SyncDaemon'
94 if isinstance(self.fake_name_owner, str):
95 return self.fake_name_owner
96@@ -69,7 +69,10 @@
97 return defer.Deferred()
98 methname, response = self._fake_response
99 assert methname == name
100- return response
101+ if isinstance(response, Exception):
102+ return defer.fail(response)
103+ else:
104+ return defer.succeed(response)
105 return f
106
107
108@@ -104,9 +107,8 @@
109 return called_args
110
111 def fake_sdt_response(self, method_name, response):
112- """Fakes SDT answer in deferred mode."""
113- self.dbus.sync_daemon_tool._fake_response = (method_name,
114- defer.succeed(response))
115+ """Fake SDT answer in deferred mode."""
116+ self.dbus.sync_daemon_tool._fake_response = (method_name, response)
117
118 class TestSignalHooking(SafeTests):
119 """Signal hooking tests.
120@@ -188,7 +190,7 @@
121
122
123 class TestDataProcessingStatus(SafeTests):
124- """Processes Status before sending it to SyncDaemon."""
125+ """Process Status before sending it to SyncDaemon."""
126
127 @defer.inlineCallbacks
128 def test_get_status(self):
129@@ -224,7 +226,7 @@
130
131
132 class TestDataProcessingNameOwner(SafeTests):
133- """Processes Name Owner data before sending it to SyncDaemon."""
134+ """Process Name Owner data before sending it to SyncDaemon."""
135
136 def test_name_owner_changed_no_syncdaemon(self):
137 """Test name owner changed callback."""
138@@ -245,7 +247,7 @@
139
140
141 class TestDataProcessingCQ(SafeTests):
142- """Processes CQ data before sending it to SyncDaemon."""
143+ """Process CQ data before sending it to SyncDaemon."""
144
145 @defer.inlineCallbacks
146 def test_nodata(self):
147@@ -288,7 +290,7 @@
148
149
150 class TestDataProcessingMQ(SafeTests):
151- """Processes MQ data before sending it to SyncDaemon."""
152+ """Process MQ data before sending it to SyncDaemon."""
153
154 @defer.inlineCallbacks
155 def test_nodata(self):
156@@ -463,7 +465,7 @@
157
158
159 class TestDataProcessingFolders(SafeTests):
160- """Processes Folders data before sending it to SyncDaemon."""
161+ """Process Folders data before sending it to SyncDaemon."""
162
163 @defer.inlineCallbacks
164 def test_nodata(self):
165@@ -521,9 +523,29 @@
166 self.get_msd_called("on_sd_folders_changed")
167
168
169+class TestDataProcessingMetadata(SafeTests):
170+ """Process Metadata data before sending it to SyncDaemon."""
171+
172+ @defer.inlineCallbacks
173+ def test_info_ok(self):
174+ """Test get metadata and see response."""
175+ md = dbus.Dictionary({'a': 3, 'c': 4}, signature=dbus.Signature('ss'))
176+ self.fake_sdt_response('get_metadata', md)
177+ rcv = yield self.dbus.get_metadata('path')
178+ self.assertEqual(rcv, dict(a=3, c=4))
179+
180+ @defer.inlineCallbacks
181+ def test_info_bad(self):
182+ """Test get metadata and get the error."""
183+ exc = dbus.exceptions.DBusException(
184+ name='org.freedesktop.DBus.Python.KeyError')
185+ self.fake_sdt_response('get_metadata', exc)
186+ rcv = yield self.dbus.get_metadata('not a real path')
187+ self.assertEqual(rcv, dbusiface.NOT_SYNCHED_PATH)
188+
189
190 class TestDataProcessingShares(SafeTests):
191- """Processes Shares data before sending it to SyncDaemon."""
192+ """Process Shares data before sending it to SyncDaemon."""
193
194 @defer.inlineCallbacks
195 def test_sharestome_nodata(self):
196@@ -720,6 +742,11 @@
197 self.dbus.get_folders()
198 self.assertTrue(self.handler.check_inf("Getting folders"))
199
200+ def test_get_metadata(self):
201+ """Test call to metadata."""
202+ self.dbus.get_metadata('path')
203+ self.assertTrue(self.handler.check_inf("Getting metadata for u'path'"))
204+
205 def test_get_shares_to_me(self):
206 """Test call to shares to me."""
207 self.dbus.get_shares_to_me()
208@@ -847,6 +874,15 @@
209 self.assertTrue(self.handler.check_dbg(" Folders data: %r" % d))
210
211 @defer.inlineCallbacks
212+ def test_metadata_processing(self):
213+ """Test get metadata."""
214+ d = dict(lot_of_data="I don't care")
215+ self.fake_sdt_response('get_metadata', d)
216+ yield self.dbus.get_metadata('path')
217+ self.assertTrue(self.handler.check_dbg(
218+ "Got metadata for path u'path': %r" % d))
219+
220+ @defer.inlineCallbacks
221 def test_sharestome_processing(self):
222 """Test get shares to me with one."""
223 d = dict(accepted=u'True', access_level=u'View', free_bytes=u'123456',
224@@ -877,7 +913,7 @@
225 """Test the retry decorator."""
226
227 class Helper(object):
228- """Fails some times, finally succeeds."""
229+ """Fail some times, finally succeed."""
230 def __init__(self, limit, excep=None):
231 self.cant = 0
232 self.limit = limit
233@@ -915,7 +951,7 @@
234 self.assertTrue(dbusiface._is_retry_exception(err))
235
236 def get_decorated_func(self, func):
237- """Executes the test calling the received function."""
238+ """Execute the test calling the received function."""
239
240 @dbusiface.retryable
241 def f():
242
243=== modified file 'magicicada/tests/test_syncdaemon.py'
244--- magicicada/tests/test_syncdaemon.py 2010-06-11 20:09:11 +0000
245+++ magicicada/tests/test_syncdaemon.py 2010-06-14 18:14:25 +0000
246@@ -133,7 +133,7 @@
247
248 @defer.inlineCallbacks
249 def test_initial_value(self):
250- """Fills the status info initially."""
251+ """Fill the status info initially."""
252 called = []
253 def fake():
254 """Fake method."""
255@@ -167,7 +167,7 @@
256 return deferred
257
258 def test_status_changed_affects_cuurent_status(self):
259- """Makes changes to see how status are reflected."""
260+ """Make changes to see how status are reflected."""
261 # one set of values
262 self.sd.on_sd_status_changed('name1', 'description1', False, True,
263 False, 'queues1', 'connection1')
264@@ -261,7 +261,7 @@
265
266 @defer.inlineCallbacks
267 def test_initial_value(self):
268- """Fills the content queue info initially."""
269+ """Fill the content queue info initially."""
270 called = []
271 self.sd.dbus.get_content_queue = lambda: called.append(True)
272 yield self.sd._get_initial_data()
273@@ -341,7 +341,7 @@
274
275 @defer.inlineCallbacks
276 def test_initial_value(self):
277- """Fills the meta queue info initially."""
278+ """Fill the meta queue info initially."""
279 called = []
280 def f():
281 """Helper function."""
282@@ -777,11 +777,38 @@
283 self.assertTrue(self.hdlr.check_inf("SD Name Owner changed: True"))
284
285
286+class MetadataTests(BaseTest):
287+ """Get Metadata info."""
288+
289+ def test_get_metadata_no_callback_set(self):
290+ """It's mandatory to set the callback for this response."""
291+ self.assertRaises(ValueError, self.sd.get_metadata, 'path')
292+
293+ def test_get_metadata_ok(self):
294+ """Get the metadata for given path."""
295+ called = []
296+ self.sd.dbus.get_metadata = lambda p: defer.succeed('foo')
297+ self.sd.on_metadata_ready_callback = lambda *a: called.extend(a)
298+ self.sd.get_metadata('path')
299+ self.assertEqual(called, ['path', 'foo'])
300+
301+ def test_get_metadata_double(self):
302+ """Get the metadata twice."""
303+ called = []
304+ fake_md = {'path1': 'foo', 'path2': 'bar'}
305+ self.sd.dbus.get_metadata = lambda p: defer.succeed(fake_md[p])
306+ self.sd.on_metadata_ready_callback = lambda *a: called.append(a)
307+ self.sd.get_metadata('path1')
308+ self.sd.get_metadata('path2')
309+ self.assertEqual(called[0], ('path1', 'foo'))
310+ self.assertEqual(called[1], ('path2', 'bar'))
311+
312+
313 class FoldersTests(BaseTest):
314 """Folders checking."""
315
316 def test_foldercreated_callback(self):
317- """Gets the new data after the folders changed."""
318+ """Get the new data after the folders changed."""
319 # set the callback
320 called = []
321 self.sd.dbus.get_folders = lambda: called.append(True)
322@@ -794,7 +821,7 @@
323
324 @defer.inlineCallbacks
325 def test_initial_value(self):
326- """Fills the folder info initially."""
327+ """Fill the folder info initially."""
328 called = []
329 self.sd.dbus.get_folders = lambda: called.append(True)
330 yield self.sd._get_initial_data()
331@@ -817,7 +844,7 @@
332 """Shares checking."""
333
334 def test_shares_changed_callback(self):
335- """Gets the new data after the shares changed."""
336+ """Get the new data after the shares changed."""
337 # set the callback
338 called = []
339 self.sd.dbus.get_shares_to_me = lambda: called.append(True)
340@@ -831,7 +858,7 @@
341
342 @defer.inlineCallbacks
343 def test_initial_value(self):
344- """Fills the folder info initially."""
345+ """Fill the folder info initially."""
346 called = []
347 self.sd.dbus.get_shares_to_me = lambda: called.append(True)
348 self.sd.dbus.get_shares_to_others = lambda: called.append(True)

Subscribers

People subscribed via source and target branches