Merge lp:~alecu/ubuntuone-client/delivery-call-api into lp:ubuntuone-client

Proposed by Alejandro J. Cura
Status: Work in progress
Proposed branch: lp:~alecu/ubuntuone-client/delivery-call-api
Merge into: lp:ubuntuone-client
Diff against target: 238 lines (+154/-6)
3 files modified
tests/syncdaemon/test_action_queue.py (+107/-1)
ubuntuone/syncdaemon/action_queue.py (+45/-1)
ubuntuone/syncdaemon/event_queue.py (+2/-4)
To merge this branch: bzr merge lp:~alecu/ubuntuone-client/delivery-call-api
Reviewer Review Type Date Requested Status
dobey Pending
Review via email: mp+144950@code.launchpad.net

Commit message

- Add call to newest-purchases web api (LP: #1103688).

To post a comment you must log in.

Unmerged revisions

1379. By Alejandro J. Cura

Add call to newest-purchases web api

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/syncdaemon/test_action_queue.py'
2--- tests/syncdaemon/test_action_queue.py 2013-01-17 19:16:53 +0000
3+++ tests/syncdaemon/test_action_queue.py 2013-01-25 14:59:21 +0000
4@@ -1,6 +1,6 @@
5 #-*- coding: utf-8 -*-
6 #
7-# Copyright 2009-2012 Canonical Ltd.
8+# Copyright 2009-2013 Canonical Ltd.
9 #
10 # This program is free software: you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License version 3, as published
12@@ -487,6 +487,112 @@
13 yield self.action_queue.get_webclient(self.fake_iri)
14 self.assertEqual(calls[1]["context_factory"], fake_context)
15
16+ def test_queue_music_delivery_results(self):
17+ """The music delivery results are queued."""
18+ albums = ["fake", "list", "of", "albums"]
19+ self.action_queue.music_delivery_results(albums)
20+ expected = ('AQ_MUSIC_DELIVERY_RESULTS', {'purchased_albums': albums})
21+ self.assertEqual([expected], self.action_queue.event_queue.events)
22+
23+
24+class MusicDeliveryLifeCycleTestCase(BasicTestCase):
25+ """Tests to check the lifecycle of the music delivery checks."""
26+
27+ def test_delivery_checker_initialization(self):
28+ """Check the initialization of the checker."""
29+ self.assertEqual(self.action_queue.delivery_checker.action_queue,
30+ self.action_queue)
31+
32+ def test_delivery_checker_started(self):
33+ """Check that the delivery checker is started on connection."""
34+ called = []
35+ self.patch(self.action_queue, "_lookup_srv", lambda: defer.Deferred())
36+ self.patch(self.action_queue.delivery_checker, "start",
37+ lambda: called.append(True))
38+ self.action_queue.connect()
39+ self.assertEqual(len(called), 1)
40+
41+ def test_delivery_checker_stopped(self):
42+ """Check that the delivery checker is stopped on disconnection."""
43+ called = []
44+ self.patch(self.action_queue.delivery_checker, "stop",
45+ lambda: called.append(True))
46+ self.action_queue._cleanup_connection_state()
47+ self.assertEqual(len(called), 1)
48+
49+
50+FAKE_DELIVERY_RESPONSE = """{
51+ "server_timestamp": 1234,
52+ "purchased_albums": [{
53+ "album_name": "7digital Essential: 50s Rock",
54+ "cloud_delivery_status": "COMPLETED",
55+ "timestamp_purchased": 1230,
56+ "open_url": "http://one.ubuntu.com/......",
57+ "tracks_udf_paths": [
58+ "Bill Haley/7digital Essential: 50s Rock/Rock Around the Clock.mp3",
59+ "Bo Diddley/7digital Essential: 50s Rock/I'm a Man.mp3",
60+ "Buddy Holly/7digital Essential: 50s Rock/Little Baby.mp3",
61+ "Chuck Berry/7digital Essential: 50s Rock/Johnny Be Good.mp3",
62+ "Chuck Berry/7digital Essential: 50s Rock/Roll Over Beethoven.mp3",
63+ "Chuck Berry/7digital Essential: 50s Rock/Sweet Little Rock&Roll.mp3",
64+ "Eddie Cochran/7digital Essential: 50s Rock/Let's Get Together.mp3",
65+ "Eddie Cochran/7digital Essential: 50s Rock/My Way.mp3",
66+ "Elvis Presley/7digital Essential: 50s Rock/Blue Suede Shoes.mp3",
67+ "Elvis Presley/7digital Essential: 50s Rock/Don't Be Cruel.mp3",
68+ "Elvis Presley/7digital Essential: 50s Rock/Shake, Rattle and Roll.mp3"
69+ ]}
70+ ]
71+}"""
72+
73+
74+class FakeAQ(object):
75+ """A fake action queue."""
76+
77+ def __init__(self, result=FAKE_DELIVERY_RESPONSE):
78+ self.result = result
79+ self.called = []
80+ self.delivery_results = None
81+
82+ def webcall(self, *args, **kwargs):
83+ """A fake web call."""
84+ self.called.append((args, kwargs))
85+ response = action_queue.txweb.Response(self.result)
86+ return defer.succeed(response)
87+
88+ def music_delivery_results(self, albums):
89+ """The music results are sent to the event queue."""
90+ self.delivery_results = albums
91+
92+
93+class MusicDeliveryCheckerTestCase(TwistedTestCase):
94+ """Tests for the MusicDeliveryChecker."""
95+
96+ @defer.inlineCallbacks
97+ def test_webservice_called_with_timestamp(self):
98+ """The webservice is called with the last server timestamp."""
99+ fakeaq = FakeAQ()
100+ checker = action_queue.MusicDeliveryChecker(fakeaq)
101+ expected = ((action_queue.MusicDeliveryChecker.ALBUM_DELIVERY_IRI %
102+ checker.last_server_timestamp,), {'method': 'GET'})
103+ yield checker.check_api()
104+ self.assertEqual(fakeaq.called, [expected])
105+
106+ @defer.inlineCallbacks
107+ def test_new_server_timestamp_is_stored(self):
108+ """The webservice response is json parsed and the timestamp stored."""
109+ fakeaq = FakeAQ()
110+ checker = action_queue.MusicDeliveryChecker(fakeaq)
111+ yield checker.check_api()
112+ self.assertEqual(checker.last_server_timestamp, 1234)
113+
114+ @defer.inlineCallbacks
115+ def test_event_queue_is_pushed(self):
116+ """The webservice response is pushed into the event queue."""
117+ fakeaq = FakeAQ()
118+ checker = action_queue.MusicDeliveryChecker(fakeaq)
119+ yield checker.check_api()
120+ self.assertEqual(len(fakeaq.delivery_results), 1)
121+
122
123 class TestLoggingStorageClient(TwistedTestCase):
124 """Tests for ensuring magic hash dont show in logs."""
125
126=== modified file 'ubuntuone/syncdaemon/action_queue.py'
127--- ubuntuone/syncdaemon/action_queue.py 2013-01-11 20:05:24 +0000
128+++ ubuntuone/syncdaemon/action_queue.py 2013-01-25 14:59:21 +0000
129@@ -1,6 +1,6 @@
130 # -*- coding: utf-8 -*-
131 #
132-# Copyright 2009-2012 Canonical Ltd.
133+# Copyright 2009-2013 Canonical Ltd.
134 #
135 # This program is free software: you can redistribute it and/or modify it
136 # under the terms of the GNU General Public License version 3, as published
137@@ -761,6 +761,43 @@
138 return getattr(self.fd, attr)
139
140
141+class MusicDeliveryChecker(object):
142+ """Checks the api for new albums delivered to the cloud."""
143+
144+ ALBUM_DELIVERY_IRI = (u"https://edge.one.ubuntu.com/" +
145+ u"music-store/api/1/user/newest-purchases?last_server_timestamp=%d")
146+
147+ def __init__(self, action_queue):
148+ self.action_queue = action_queue
149+ self.last_server_timestamp = 0
150+
151+ def start(self):
152+ """Start calling the api periodically."""
153+ # TODO: coming in following branches. See pad.lv/1103690
154+ # reactor.callLater(0, self.check_and_schedule_next_check)
155+
156+ def stop(self):
157+ """Stop calling the api."""
158+
159+ @defer.inlineCallbacks
160+ def check_and_schedule_next_check(self):
161+ """Check the api, and schedule the next check."""
162+ # TODO: coming in following branches. See pad.lv/1103690
163+ # on any error, wait 10 minutes
164+ # on success, wait 60 minutes
165+
166+ @defer.inlineCallbacks
167+ def check_api(self):
168+ """Do the actual checking."""
169+ iri = self.ALBUM_DELIVERY_IRI % self.last_server_timestamp
170+ logger.debug("Checking music delivery: %d", self.last_server_timestamp)
171+ response = yield self.action_queue.webcall(iri, method="GET")
172+ logger.debug("Music delivery returned %d bytes", len(response.content))
173+ parsed = json.loads(response.content)
174+ self.last_server_timestamp = int(parsed["server_timestamp"])
175+ self.action_queue.music_delivery_results(parsed["purchased_albums"])
176+
177+
178 class ActionQueue(ThrottlingStorageClientFactory, object):
179 """The ActionQueue itself."""
180
181@@ -813,6 +850,7 @@
182 self.commands = dict((x, y) for x, y in globals().iteritems()
183 if inspect.isclass(y) and
184 issubclass(y, ActionQueueCommand))
185+ self.delivery_checker = MusicDeliveryChecker(self)
186
187 def check_conditions(self):
188 """Check conditions in the locker, to release all the waiting ops."""
189@@ -843,6 +881,7 @@
190 self.client = None
191 self.connector = None
192 self.connect_in_progress = False
193+ self.delivery_checker.stop()
194
195 def _share_change_callback(self, info):
196 """Called by the client when notified that a share changed."""
197@@ -921,6 +960,10 @@
198 else:
199 return defer.succeed((self.host, self.port))
200
201+ def music_delivery_results(self, results):
202+ """Push the music delivery results into the event queue."""
203+ self.event_queue.push('AQ_MUSIC_DELIVERY_RESULTS',
204+ purchased_albums=results)
205
206 @defer.inlineCallbacks
207 def webcall(self, iri, **kwargs):
208@@ -968,6 +1011,7 @@
209 d = self._lookup_srv()
210 # DNS lookup always succeeds, proceed to actually connect
211 d.addCallback(self._make_connection)
212+ self.delivery_checker.start()
213
214 def buildProtocol(self, addr):
215 """Build the client and store it. Connect callbacks."""
216
217=== modified file 'ubuntuone/syncdaemon/event_queue.py'
218--- ubuntuone/syncdaemon/event_queue.py 2012-08-08 13:21:13 +0000
219+++ ubuntuone/syncdaemon/event_queue.py 2013-01-25 14:59:21 +0000
220@@ -1,9 +1,6 @@
221 # ubuntuone.syncdaemon.event_queue - Event queuing
222 #
223-# Authors: Facundo Batista <facundo@canonical.com>
224-# Manuel de la Pena <manuel@canonical.com>
225-#
226-# Copyright 2009-2012 Canonical Ltd.
227+# Copyright 2009-2013 Canonical Ltd.
228 #
229 # This program is free software: you can redistribute it and/or modify it
230 # under the terms of the GNU General Public License version 3, as published
231@@ -100,6 +97,7 @@
232 'AQ_CHANGE_PUBLIC_ACCESS_ERROR': ('share_id', 'node_id', 'error'),
233 'AQ_PUBLIC_FILES_LIST_OK': ('public_files',),
234 'AQ_PUBLIC_FILES_LIST_ERROR': ('error',),
235+ 'AQ_MUSIC_DELIVERY_RESULTS': ('purchased_albums',),
236 'AQ_DELTA_OK': ('volume_id', 'delta_content', 'end_generation',
237 'full', 'free_bytes'),
238 'AQ_DELTA_ERROR': ('volume_id', 'error'),

Subscribers

People subscribed via source and target branches