Merge lp:~karni/ubuntuone-music-java-library/content-streaming into lp:ubuntuone-music-java-library

Proposed by Michał Karnicki
Status: Merged
Merged at revision: 8
Proposed branch: lp:~karni/ubuntuone-music-java-library/content-streaming
Merge into: lp:ubuntuone-music-java-library
Prerequisite: lp:~karni/ubuntuone-music-java-library/playlist-ops-and-art
Diff against target: 1175 lines (+766/-62)
17 files modified
src/main/com/ubuntuone/api/music/U1MusicAPI.java (+214/-47)
src/main/com/ubuntuone/api/music/client/ResourceClient.java (+13/-7)
src/main/com/ubuntuone/api/music/client/StreamingClient.java (+79/-0)
src/main/com/ubuntuone/api/music/request/U1DownloadListener.java (+58/-0)
src/main/com/ubuntuone/api/music/util/CancelTrigger.java (+66/-0)
src/main/com/ubuntuone/api/music/util/OnCancelListener.java (+31/-0)
src/main/com/ubuntuone/api/music/util/OnProgressListener.java (+165/-0)
src/main/com/ubuntuone/api/music/util/RequestListener.java (+2/-0)
src/test/com/ubuntuone/api/music/DeletePlaylistTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetAlbumsTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetArtTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetArtistsTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetPlaylistTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetPlaylistsTest.java (+1/-1)
src/test/com/ubuntuone/api/music/GetSongStreamTest.java (+130/-0)
src/test/com/ubuntuone/api/music/GetSongsTest.java (+1/-1)
src/test/com/ubuntuone/api/music/PutPlaylistTest.java (+1/-1)
To merge this branch: bzr merge lp:~karni/ubuntuone-music-java-library/content-streaming
Reviewer Review Type Date Requested Status
Chad Miller Pending
Review via email: mp+110940@code.launchpad.net

Description of the change

This branch adds getSongStream and downloadSong methods, as well as handling non-200 http responses.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/main/com/ubuntuone/api/music/U1MusicAPI.java'
2--- src/main/com/ubuntuone/api/music/U1MusicAPI.java 2012-06-19 01:09:20 +0000
3+++ src/main/com/ubuntuone/api/music/U1MusicAPI.java 2012-06-19 01:09:20 +0000
4@@ -33,13 +33,16 @@
5
6 import javax.net.ssl.SSLException;
7
8+import org.apache.http.Header;
9 import org.apache.http.HttpEntity;
10+import org.apache.http.HttpHeaders;
11 import org.apache.http.HttpResponse;
12 import org.apache.http.HttpStatus;
13 import org.apache.http.client.HttpClient;
14 import org.apache.http.client.methods.HttpDelete;
15 import org.apache.http.client.methods.HttpGet;
16 import org.apache.http.client.methods.HttpPost;
17+import org.apache.http.util.EntityUtils;
18 import org.codehaus.jackson.JsonParseException;
19
20 import com.ubuntu.sso.authorizer.Authorizer;
21@@ -47,6 +50,7 @@
22 import com.ubuntuone.api.music.client.BaseClient;
23 import com.ubuntuone.api.music.client.Failure;
24 import com.ubuntuone.api.music.client.ResourceClient;
25+import com.ubuntuone.api.music.client.StreamingClient;
26 import com.ubuntuone.api.music.json.U1AlbumJson;
27 import com.ubuntuone.api.music.json.U1ArtistJson;
28 import com.ubuntuone.api.music.json.U1PlaylistJson;
29@@ -55,12 +59,19 @@
30 import com.ubuntuone.api.music.model.U1Album;
31 import com.ubuntuone.api.music.model.U1Artist;
32 import com.ubuntuone.api.music.model.U1Song;
33+import com.ubuntuone.api.music.request.U1DownloadListener;
34+import com.ubuntuone.api.music.util.CancelTrigger;
35+import com.ubuntuone.api.music.util.CancelTrigger.RequestCanceledException;
36+import com.ubuntuone.api.music.util.OnProgressListener;
37+import com.ubuntuone.api.music.util.OnProgressListener.ProgressInputStream;
38+import com.ubuntuone.api.music.util.RequestListener;
39 import com.ubuntuone.api.music.util.RequestListener.U1AlbumRequestListener;
40 import com.ubuntuone.api.music.util.RequestListener.U1ArtRequestListener;
41 import com.ubuntuone.api.music.util.RequestListener.U1ArtistRequestListener;
42 import com.ubuntuone.api.music.util.RequestListener.U1PlaylistRequestListener;
43 import com.ubuntuone.api.music.util.RequestListener.U1PlaylistSongRequestListener;
44 import com.ubuntuone.api.music.util.RequestListener.U1SongRequestListener;
45+import com.ubuntuone.api.music.util.RequestListener.U1StreamRequestListener;
46 import com.ubuntuone.api.music.util.RequestListener.U1VoidRequestListener;
47 import com.ubuntuone.api.music.util.ResponseHeader;
48 import com.ubuntuone.api.music.util.StreamUtils;
49@@ -78,27 +89,35 @@
50 public static final String PLAYLISTS = "playlists";
51
52 protected static final String RESOURCE_HOST = "one.ubuntu.com";
53+ // TODO karni: Set this to proper streaming host when streaming handler is ready.
54+ protected static final String STREAMING_HOST = "streaming.one.ubuntu.com";
55
56 private final ResourceClient resourceClient;
57+ private final StreamingClient streamingClient;
58
59 private static Logger logger = Logger.getLogger(U1MusicAPI.LOGGER_NAME);
60
61- public U1MusicAPI(String scheme, String resourceHost,
62+ public U1MusicAPI(String userAgent, String scheme,
63+ String resourceHost, String streamingHost,
64 HttpClient httpClient, Authorizer authorizer) {
65 logger = Logger.getLogger(U1MusicAPI.class.getCanonicalName());
66 logger.setLevel(Level.INFO);
67
68- this.resourceClient = new ResourceClient(
69+ this.resourceClient = new ResourceClient(userAgent,
70 scheme, resourceHost, httpClient, authorizer);
71- }
72-
73- public U1MusicAPI(String resourceHost, HttpClient httpClient,
74- Authorizer authorizer) {
75- this(BaseClient.HTTPS, resourceHost, httpClient, authorizer);
76- }
77-
78- public U1MusicAPI(HttpClient httpClient, Authorizer authorizer) {
79- this(BaseClient.HTTPS, RESOURCE_HOST, httpClient, authorizer);
80+ this.streamingClient = new StreamingClient(userAgent,
81+ scheme, streamingHost, httpClient, authorizer);
82+ }
83+
84+ public U1MusicAPI(String userAgent, String resourceHost,
85+ String steramingHost, HttpClient httpClient, Authorizer authorizer) {
86+ this(userAgent, BaseClient.HTTPS, resourceHost, steramingHost,
87+ httpClient, authorizer);
88+ }
89+
90+ public U1MusicAPI(String userAgent, HttpClient httpClient, Authorizer authorizer) {
91+ this(userAgent, BaseClient.HTTPS, RESOURCE_HOST, STREAMING_HOST,
92+ httpClient, authorizer);
93 }
94
95 public void getArtists(U1ArtistRequestListener callback) {
96@@ -111,16 +130,15 @@
97 if (statusCode == HttpStatus.SC_OK) {
98 U1ArtistJson.fromJson(getContentInputStream(response), callback);
99 } else {
100- // TODO handleNon200HttpResponse(response, "Could not get artists.", callback);
101+ handleHttpResponse(response, "Could not get artists.", callback);
102 }
103 } catch (JsonParseException jpe) {
104 // Received corrupt JSON. At this stage, response != null
105- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
106 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
107 final String date = getHeaderValue(ResponseHeader.DATE, response);
108
109 callback.onFailure(new Failure("Could not get artists. Corrupt JSON.",
110- jpe, 0, oopsId, bzrRev, date));
111+ jpe, 0, null, bzrRev, date));
112 } catch (OutOfMemoryError e) {
113 callback.onFailure(new Failure("Out of memory!", e));
114 throw e; // We're not expected to recover from this.
115@@ -150,16 +168,15 @@
116 if (statusCode == HttpStatus.SC_OK) {
117 U1AlbumJson.fromJson(getContentInputStream(response), callback);
118 } else {
119- // TODO handleNon200HttpResponse(response, "Could not get albums.", callback);
120+ handleHttpResponse(response, "Could not get albums.", callback);
121 }
122 } catch (JsonParseException jpe) {
123 // Received corrupt JSON. At this stage, response != null
124- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
125 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
126 final String date = getHeaderValue(ResponseHeader.DATE, response);
127
128 callback.onFailure(new Failure("Could not get albums. Corrupt JSON.",
129- jpe, 0, oopsId, bzrRev, date));
130+ jpe, 0, null, bzrRev, date));
131 } catch (OutOfMemoryError e) {
132 callback.onFailure(new Failure("Out of memory!", e));
133 throw e; // We're not expected to recover from this.
134@@ -189,16 +206,15 @@
135 if (statusCode == HttpStatus.SC_OK) {
136 U1SongJson.fromJson(getContentInputStream(response), callback);
137 } else {
138- // TODO handleNon200HttpResponse(response, "Could not get songs.", callback);
139+ handleHttpResponse(response, "Could not get songs.", callback);
140 }
141 } catch (JsonParseException jpe) {
142 // Received corrupt JSON. At this stage, response != null
143- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
144 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
145 final String date = getHeaderValue(ResponseHeader.DATE, response);
146
147 callback.onFailure(new Failure("Could not get songs. Corrupt JSON.",
148- jpe, 0, oopsId, bzrRev, date));
149+ jpe, 0, null, bzrRev, date));
150 } catch (OutOfMemoryError e) {
151 callback.onFailure(new Failure("Out of memory!", e));
152 throw e; // We're not expected to recover from this.
153@@ -228,16 +244,15 @@
154 if (statusCode == HttpStatus.SC_OK) {
155 U1PlaylistJson.fromJson(getContentInputStream(response), callback);
156 } else {
157- // TODO handleNon200HttpResponse(response, "Could not get playlists.", callback);
158+ handleHttpResponse(response, "Could not get playlists.", callback);
159 }
160 } catch (JsonParseException jpe) {
161 // Received corrupt JSON. At this stage, response != null
162- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
163 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
164 final String date = getHeaderValue(ResponseHeader.DATE, response);
165
166 callback.onFailure(new Failure("Could not get playlists. Corrupt JSON.",
167- jpe, 0, oopsId, bzrRev, date));
168+ jpe, 0, null, bzrRev, date));
169 } catch (OutOfMemoryError e) {
170 callback.onFailure(new Failure("Out of memory!", e));
171 throw e; // We're not expected to recover from this.
172@@ -267,16 +282,15 @@
173 if (statusCode == HttpStatus.SC_OK) {
174 U1PlaylistSongJson.fromJson(getContentInputStream(response), callback);
175 } else {
176- // TODO handleNon200HttpResponse(response, "Could not get playlist.", callback);
177+ handleHttpResponse(response, "Could not get playlist.", callback);
178 }
179 } catch (JsonParseException jpe) {
180 // Received corrupt JSON. At this stage, response != null
181- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
182 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
183 final String date = getHeaderValue(ResponseHeader.DATE, response);
184
185 callback.onFailure(new Failure("Could not get playlist. Corrupt JSON.",
186- jpe, 0, oopsId, bzrRev, date));
187+ jpe, 0, null, bzrRev, date));
188 } catch (OutOfMemoryError e) {
189 callback.onFailure(new Failure("Out of memory!", e));
190 throw e; // We're not expected to recover from this.
191@@ -307,16 +321,15 @@
192 if (statusCode == HttpStatus.SC_OK) {
193 U1PlaylistSongJson.fromJson(getContentInputStream(response), callback);
194 } else {
195- // TODO handleNon200HttpResponse(response, "Could not get playlist.", callback);
196+ handleHttpResponse(response, "Could not create playlist.", callback);
197 }
198 } catch (JsonParseException jpe) {
199 // Received corrupt JSON. At this stage, response != null
200- final String oopsId = getHeaderValue(ResponseHeader.X_OOPS_ID, response);
201 final String bzrRev = getHeaderValue(ResponseHeader.X_BZR_REVISION_NUMBER, response);
202 final String date = getHeaderValue(ResponseHeader.DATE, response);
203
204 callback.onFailure(new Failure("Could not create playlist. Corrupt JSON.",
205- jpe, 0, oopsId, bzrRev, date));
206+ jpe, 0, null, bzrRev, date));
207 } catch (OutOfMemoryError e) {
208 callback.onFailure(new Failure("Out of memory!", e));
209 throw e; // We're not expected to recover from this.
210@@ -346,7 +359,7 @@
211 if (statusCode == HttpStatus.SC_OK) {
212 callback.onSuccess(null);
213 } else {
214- // TODO handleNon200HttpResponse(response, "Could not get playlist.", callback);
215+ handleHttpResponse(response, "Could not delete playlist.", callback);
216 }
217 } catch (SSLException sslException) {
218 callback.onFailure(new Failure("SSL connection problem. This may be intermittent issue, " +
219@@ -361,7 +374,7 @@
220 callback.onFailure(new Failure("Could not delete playlist (signing exception).", signingException));
221 } finally {
222 callback.onFinish();
223- }
224+ }
225 }
226
227 private void getArt(String path, String filePath, U1ArtRequestListener callback) {
228@@ -382,7 +395,7 @@
229 out.close();
230 callback.onSuccess(artFile);
231 } else {
232- // TODO handleNon200HttpResponse(response, "Could not get playlist.", callback);
233+ handleHttpResponse(response, "Could not get art.", callback);
234 }
235 } catch (SSLException sslException) {
236 callback.onFailure(new Failure("SSL connection problem. This may be intermittent issue, " +
237@@ -400,20 +413,186 @@
238 }
239 }
240
241- public void getArt(U1Artist artist, String filePath, U1ArtRequestListener callback) {
242+ public void getArt(U1Artist artist, String filePath,
243+ U1ArtRequestListener callback) {
244 String path = resourceClient.getArtPath(ARTISTS, artist.getId());
245 getArt(path, filePath, callback);
246 }
247
248- public void getArt(U1Album album, String filePath, U1ArtRequestListener callback) {
249+ public void getArt(U1Album album, String filePath,
250+ U1ArtRequestListener callback) {
251 String path = resourceClient.getArtPath(ALBUMS, album.getId());
252 getArt(path, filePath, callback);
253 }
254
255- public void getArt(U1Song song, String filePath, U1ArtRequestListener callback) {
256+ public void getArt(U1Song song, String filePath,
257+ U1ArtRequestListener callback) {
258 String path = resourceClient.getArtPath(ALBUMS, song.getAlbumId());
259 getArt(path, filePath, callback);
260 }
261+
262+ public void getSongStream(U1Song song, long offset,
263+ U1StreamRequestListener callback) {
264+ callback.onStart();
265+ String path = streamingClient.getPath(song.getId());
266+ HttpResponse response = null;
267+ try {
268+ response = streamingClient.download(path, offset, null);
269+ final int statusCode = getStatusCode(response);
270+ if (statusCode == HttpStatus.SC_OK) {
271+ callback.onSuccess(getContentInputStream(response));
272+ } else {
273+ handleHttpResponse(response, "Could not get song stream.", callback);
274+ }
275+ } catch (SSLException sslException) {
276+ callback.onFailure(new Failure("SSL connection problem. This may be intermittent issue, " +
277+ "please try again later.", sslException));
278+ } catch (IOException ioException) {
279+ // Failed to read the response.
280+ callback.onFailure(new Failure("Could not get song stream (network error).", ioException));
281+ } catch (URISyntaxException uriSyntaxException) {
282+ // Failed due to invalid URL within the library code.
283+ throw new IllegalStateException(uriSyntaxException);
284+ } catch (AuthorizerException signingException) {
285+ callback.onFailure(new Failure("Could not get song stream (signing exception).", signingException));
286+ } catch (RequestCanceledException e) {
287+ // Ignore.
288+ } finally {
289+ callback.onFinish();
290+ }
291+ }
292+
293+ // TODO karni: add tests
294+ public void downloadSong(U1Song song, String localPath,
295+ U1DownloadListener callback, CancelTrigger cancelTrigger) {
296+ callback.onStart();
297+ final String contentPath = streamingClient.getPath(song.getId());
298+ HttpResponse response = null;
299+ int statusCode = 0;
300+ try {
301+ final File file = new File(localPath);
302+ file.getParentFile().mkdirs();
303+ long offset = file.exists() ? file.length() : 0L;
304+
305+ if (offset > 0) {
306+ // Attempt to resume download.
307+ response = streamingClient.download(
308+ contentPath, offset, cancelTrigger);
309+ statusCode = getStatusCode(response);
310+ if (statusCode != HttpStatus.SC_PARTIAL_CONTENT) {
311+ HttpEntity entity = response.getEntity();
312+ if (entity != null) {
313+ EntityUtils.consume(entity);
314+ }
315+ offset = 0;
316+ }
317+ }
318+
319+ if (statusCode != HttpStatus.SC_PARTIAL_CONTENT) {
320+ response = streamingClient.download(
321+ contentPath, offset, cancelTrigger);
322+ statusCode = getStatusCode(response);
323+ }
324+
325+ if (statusCode >= 200 && statusCode < 300) {
326+ final boolean isResuming = offset > 0;
327+ logger.info(String.format("%s download to %s",
328+ isResuming ? "Resuming" : "Starting", file.getPath()));
329+
330+ final Header contentLength = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
331+ final long size = Long.valueOf(contentLength.getValue());
332+ final ProgressInputStream in = new ProgressInputStream(
333+ getContentInputStream(response), offset, offset + size, (OnProgressListener) callback);
334+
335+ final FileOutputStream stream = new FileOutputStream(file, isResuming);
336+ in.writeTo(stream);
337+ callback.onSuccess(true);
338+ } else if (cancelTrigger != null && cancelTrigger.isCancelled()) {
339+ // Do nothing, request has been cancelled.
340+ } else {
341+ handleHttpResponse(response, "Could not download song.", callback);
342+ }
343+ } catch (RequestCanceledException canceledException) {
344+ callback.onCancel();
345+ } catch (SSLException sslException) {
346+ callback.onFailure(new Failure("SSL connection problem. This may be intermittent issue, " +
347+ "please try again later.", sslException));
348+ } catch (IOException ioException) {
349+ // Failed to read the response.
350+ callback.onFailure(new Failure("Could not download song (network error).", ioException));
351+ } catch (URISyntaxException uriSyntaxException) {
352+ // Failed due to invalid URL within the library code.
353+ throw new IllegalStateException(uriSyntaxException);
354+ } catch (AuthorizerException signingException) {
355+ callback.onFailure(new Failure("Could not download song (signing exception).", signingException));
356+ } finally {
357+ callback.onFinish();
358+ }
359+ }
360+
361+ private void handleHttpResponse(HttpResponse response,
362+ String message, RequestListener<?> callback) {
363+ final int statusCode = getStatusCode(response);
364+
365+ switch (statusCode) {
366+ case HttpStatus.SC_TEMPORARY_REDIRECT:
367+ callback.onFailure(new Failure(
368+ String.format("%s Redirected.", message), statusCode));
369+ break;
370+ case HttpStatus.SC_UNAUTHORIZED:
371+ callback.onFailure(new Failure(
372+ String.format("%s Unauthorized. Please log in again.",
373+ message), statusCode));
374+ break;
375+ case HttpStatus.SC_FORBIDDEN:
376+ callback.onFailure(new Failure(
377+ String.format("%s Forbidden.", message), statusCode));
378+ break;
379+ case HttpStatus.SC_BAD_REQUEST:
380+ callback.onFailure(new Failure(
381+ String.format("%s Bad request.", message), statusCode));
382+ break;
383+ case HttpStatus.SC_NOT_FOUND:
384+ callback.onFailure(new Failure(
385+ String.format("%s Resource not found.", message), statusCode));
386+ break;
387+ case HttpStatus.SC_REQUEST_TIMEOUT:
388+ callback.onFailure(new Failure(
389+ String.format("%s Request timeout.", message), statusCode));
390+ break;
391+ case HttpStatus.SC_INTERNAL_SERVER_ERROR:
392+ final String oopsId = getHeaderValue(
393+ ResponseHeader.X_OOPS_ID, response);
394+ final String bzrRev = getHeaderValue(
395+ ResponseHeader.X_BZR_REVISION_NUMBER, response);
396+ final String date = getHeaderValue(
397+ ResponseHeader.DATE, response);
398+
399+ callback.onFailure(new Failure(
400+ String.format("%s Server problem.", message), statusCode,
401+ oopsId, bzrRev, date));
402+
403+ break;
404+ case HttpStatus.SC_SERVICE_UNAVAILABLE:
405+ callback.onFailure(new Failure(
406+ String.format("%s Service temporarily unavailable.",
407+ message), statusCode));
408+ break;
409+ default:
410+ callback.onFailure(new Failure(
411+ String.format("%s Response failure (%d).",
412+ message, statusCode), statusCode));
413+ break;
414+ }
415+ HttpEntity entity = response.getEntity();
416+ if (entity != null) {
417+ try {
418+ EntityUtils.consume(entity);
419+ } catch (IOException e) {
420+ // Ignore.
421+ }
422+ }
423+ }
424
425 private static final int getStatusCode(HttpResponse response) {
426 return response != null ? response.getStatusLine().getStatusCode() : null;
427@@ -428,16 +607,4 @@
428 throws IllegalStateException, IOException {
429 return response != null ? response.getEntity().getContent() : null;
430 }
431-
432- @SuppressWarnings("unused")
433- private static final void consumeContent(HttpEntity entity) {
434- if (entity != null) {
435- try {
436- final InputStream in = entity.getContent();
437- if (in != null) in.close();
438- } catch (Exception e) {
439- // Ignore.
440- }
441- }
442- }
443 }
444
445=== modified file 'src/main/com/ubuntuone/api/music/client/ResourceClient.java'
446--- src/main/com/ubuntuone/api/music/client/ResourceClient.java 2012-06-19 01:09:20 +0000
447+++ src/main/com/ubuntuone/api/music/client/ResourceClient.java 2012-06-19 01:09:20 +0000
448@@ -43,9 +43,16 @@
449
450 public class ResourceClient extends BaseClient
451 {
452- public ResourceClient(String scheme, String hostname,
453+ private final String CONNECTION = "CLOSE";
454+ private final String ACCEPT = "application/json";
455+ private final String UTF8 = "UTF-8";
456+
457+ private String userAgent;
458+
459+ public ResourceClient(String userAgent, String scheme, String hostname,
460 HttpClient httpClient, Authorizer authorizer) {
461 super(scheme, hostname, httpClient, authorizer);
462+ this.userAgent = userAgent;
463 }
464
465 public String getPath(String type) {
466@@ -78,22 +85,21 @@
467 final HttpClient httpClient = getHttpClient();
468 String query = null;
469 if (params != null) {
470- query = URLEncodedUtils.format(params, "UTF-8");
471+ query = URLEncodedUtils.format(params, UTF8);
472 }
473 final URI uri = new URI(getHost().getSchemeName(), getHost().getHostName(), path, query, null);
474
475 // SignPost OAuth only knows how to sign HttpUriRequest.
476 final HttpUriRequest request =
477 HttpUriRequestFactory.buildUriRequest(method, uri.toString());
478- request.addHeader(HttpHeaders.USER_AGENT, "u1-music-java-library");
479- request.addHeader(HttpHeaders.CONNECTION, "close");
480- request.addHeader(HttpHeaders.ACCEPT, "application/json");
481+ request.addHeader(HttpHeaders.USER_AGENT, userAgent);
482+ request.addHeader(HttpHeaders.CONNECTION, CONNECTION);
483+ request.addHeader(HttpHeaders.ACCEPT, ACCEPT);
484 if (data != null && HttpPut.METHOD_NAME.equals(method)) {
485- ((HttpPut) request).setEntity(new StringEntity(data, "UTF-8"));
486+ ((HttpPut) request).setEntity(new StringEntity(data, UTF8));
487 }
488
489 sign(request);
490- // System.out.println("Resource client: " + request.getRequestLine().toString());
491 return httpClient.execute(getHost(), request);
492 }
493
494
495=== added file 'src/main/com/ubuntuone/api/music/client/StreamingClient.java'
496--- src/main/com/ubuntuone/api/music/client/StreamingClient.java 1970-01-01 00:00:00 +0000
497+++ src/main/com/ubuntuone/api/music/client/StreamingClient.java 2012-06-19 01:09:20 +0000
498@@ -0,0 +1,79 @@
499+/*
500+ * Ubuntu One Music Java library - communicate with Ubuntu One music API
501+ *
502+ * Copyright 2012 Canonical Ltd.
503+ *
504+ * This file is part of Ubuntu One Files Java library.
505+ *
506+ * This program is free software: you can redistribute it and/or modify
507+ * it under the terms of the GNU Affero General Public License as
508+ * published by the Free Software Foundation, either version 3 of the
509+ * License, or (at your option) any later version.
510+ *
511+ * This program is distributed in the hope that it will be useful,
512+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
513+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
514+ * GNU Affero General Public License for more details.
515+ *
516+ * You should have received a copy of the GNU Affero General Public License
517+ * along with this program. If not, see http://www.gnu.org/licenses
518+ */
519+
520+package com.ubuntuone.api.music.client;
521+
522+import java.io.IOException;
523+import java.net.URI;
524+import java.net.URISyntaxException;
525+
526+import javax.net.ssl.SSLException;
527+
528+import org.apache.http.HttpHeaders;
529+import org.apache.http.HttpResponse;
530+import org.apache.http.client.HttpClient;
531+import org.apache.http.client.methods.HttpGet;
532+
533+import com.ubuntu.sso.authorizer.Authorizer;
534+import com.ubuntu.sso.authorizer.AuthorizerException;
535+import com.ubuntuone.api.music.util.CancelTrigger;
536+import com.ubuntuone.api.music.util.CancelTrigger.RequestCanceledException;
537+
538+public class StreamingClient extends BaseClient
539+{
540+ private final String CONNECTION = "CLOSE";
541+ private final String RANGE_FMT = "bytes=%d-";
542+
543+ private String userAgent;
544+
545+ public StreamingClient(String userAgent, String scheme, String hostname,
546+ HttpClient httpClient, Authorizer authorizer) {
547+ super(scheme, hostname, httpClient, authorizer);
548+ this.userAgent = userAgent;
549+ }
550+
551+ @Override
552+ public String getPath(String songId) {
553+ // TODO karni: Set this to proper streaming path when streaming handler is ready.
554+ return String.format("/stream/%s/", songId);
555+ }
556+
557+ public HttpResponse download(String path, long offset,
558+ CancelTrigger cancelTrigger) throws IOException, SSLException,
559+ AuthorizerException, URISyntaxException, RequestCanceledException {
560+ final HttpClient httpClient = getHttpClient();
561+
562+ final URI uri = new URI(getHost().getSchemeName(), getHost().getHostName(), path, null);
563+
564+ final HttpGet httpGet = new HttpGet(uri.toString());
565+ if (cancelTrigger != null) {
566+ cancelTrigger.setRequest(httpGet);
567+ }
568+ httpGet.addHeader(HttpHeaders.USER_AGENT, userAgent);
569+ httpGet.addHeader(HttpHeaders.CONNECTION, CONNECTION);
570+ if (offset > 0) {
571+ httpGet.addHeader(HttpHeaders.RANGE, String.format(RANGE_FMT, offset));
572+ }
573+
574+ sign(httpGet);
575+ return httpClient.execute(getHost(), httpGet);
576+ }
577+}
578
579=== added file 'src/main/com/ubuntuone/api/music/request/U1DownloadListener.java'
580--- src/main/com/ubuntuone/api/music/request/U1DownloadListener.java 1970-01-01 00:00:00 +0000
581+++ src/main/com/ubuntuone/api/music/request/U1DownloadListener.java 2012-06-19 01:09:20 +0000
582@@ -0,0 +1,58 @@
583+/*
584+ * Ubuntu One Music Java library - communicate with Ubuntu One music API
585+ *
586+ * Copyright 2012 Canonical Ltd.
587+ *
588+ * This file is part of Ubuntu One Files Java library.
589+ *
590+ * This program is free software: you can redistribute it and/or modify
591+ * it under the terms of the GNU Affero General Public License as
592+ * published by the Free Software Foundation, either version 3 of the
593+ * License, or (at your option) any later version.
594+ *
595+ * This program is distributed in the hope that it will be useful,
596+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
597+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
598+ * GNU Affero General Public License for more details.
599+ *
600+ * You should have received a copy of the GNU Affero General Public License
601+ * along with this program. If not, see http://www.gnu.org/licenses
602+ */
603+
604+package com.ubuntuone.api.music.request;
605+
606+import com.ubuntuone.api.music.client.Failure;
607+import com.ubuntuone.api.music.util.OnCancelListener;
608+import com.ubuntuone.api.music.util.OnProgressListener;
609+import com.ubuntuone.api.music.util.RequestListener;
610+
611+public abstract class U1DownloadListener implements RequestListener<Boolean>,
612+ OnProgressListener, OnCancelListener
613+{
614+ @Override
615+ public void onStart() {
616+ }
617+
618+ @Override
619+ public final void onSuccess(Boolean success) {
620+ onSuccess();
621+ }
622+
623+ public abstract void onSuccess();
624+
625+ @Override
626+ public void onFailure(Failure failure) {
627+ }
628+
629+ @Override
630+ public void onProgress(long bytes, long total) {
631+ }
632+
633+ @Override
634+ public void onCancel() {
635+ }
636+
637+ @Override
638+ public void onFinish() {
639+ }
640+}
641
642=== added file 'src/main/com/ubuntuone/api/music/util/CancelTrigger.java'
643--- src/main/com/ubuntuone/api/music/util/CancelTrigger.java 1970-01-01 00:00:00 +0000
644+++ src/main/com/ubuntuone/api/music/util/CancelTrigger.java 2012-06-19 01:09:20 +0000
645@@ -0,0 +1,66 @@
646+/*
647+ * Ubuntu One Files Java library - communicate with Ubuntu One storage API
648+ *
649+ * Copyright (C) 2012 Canonical Ltd.
650+ *
651+ * This file is part of Ubuntu One Files Java library.
652+ *
653+ * This program is free software: you can redistribute it and/or modify
654+ * it under the terms of the GNU Affero General Public License as
655+ * published by the Free Software Foundation, either version 3 of the
656+ * License, or (at your option) any later version.
657+ *
658+ * This program is distributed in the hope that it will be useful,
659+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
660+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
661+ * GNU Affero General Public License for more details.
662+ *
663+ * You should have received a copy of the GNU Affero General Public License
664+ * along with this program. If not, see http://www.gnu.org/licenses
665+ */
666+
667+package com.ubuntuone.api.music.util;
668+
669+import org.apache.http.client.methods.HttpUriRequest;
670+
671+public class CancelTrigger
672+{
673+ private boolean isCanceled = false;
674+
675+ private HttpUriRequest request;
676+
677+ public CancelTrigger() {
678+ }
679+
680+ public CancelTrigger(HttpUriRequest request)
681+ throws RequestCanceledException {
682+ if (isCanceled) {
683+ throw new RequestCanceledException();
684+ }
685+ this.request = request;
686+ }
687+
688+ public void setRequest(HttpUriRequest request)
689+ throws RequestCanceledException {
690+ if (isCanceled) {
691+ throw new RequestCanceledException();
692+ }
693+ this.request = request;
694+ }
695+
696+ public void onCancel() {
697+ isCanceled = true;
698+ if (request != null) {
699+ request.abort();
700+ }
701+ }
702+
703+ public boolean isCancelled() {
704+ return isCanceled;
705+ }
706+
707+ public static class RequestCanceledException extends Throwable
708+ {
709+ private static final long serialVersionUID = 4941090948169937930L;
710+ }
711+}
712
713=== added file 'src/main/com/ubuntuone/api/music/util/OnCancelListener.java'
714--- src/main/com/ubuntuone/api/music/util/OnCancelListener.java 1970-01-01 00:00:00 +0000
715+++ src/main/com/ubuntuone/api/music/util/OnCancelListener.java 2012-06-19 01:09:20 +0000
716@@ -0,0 +1,31 @@
717+/*
718+ * Ubuntu One Files Java library - communicate with Ubuntu One storage API
719+ *
720+ * Copyright (C) 2012 Canonical Ltd.
721+ *
722+ * This file is part of Ubuntu One Files Java library.
723+ *
724+ * This program is free software: you can redistribute it and/or modify
725+ * it under the terms of the GNU Affero General Public License as
726+ * published by the Free Software Foundation, either version 3 of the
727+ * License, or (at your option) any later version.
728+ *
729+ * This program is distributed in the hope that it will be useful,
730+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
731+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
732+ * GNU Affero General Public License for more details.
733+ *
734+ * You should have received a copy of the GNU Affero General Public License
735+ * along with this program. If not, see http://www.gnu.org/licenses
736+ */
737+
738+package com.ubuntuone.api.music.util;
739+
740+
741+public interface OnCancelListener
742+{
743+ /**
744+ * Called when the request has been cancelled (with a {@link CancelTrigger}.
745+ */
746+ public void onCancel();
747+}
748
749=== added file 'src/main/com/ubuntuone/api/music/util/OnProgressListener.java'
750--- src/main/com/ubuntuone/api/music/util/OnProgressListener.java 1970-01-01 00:00:00 +0000
751+++ src/main/com/ubuntuone/api/music/util/OnProgressListener.java 2012-06-19 01:09:20 +0000
752@@ -0,0 +1,165 @@
753+/*
754+ * Ubuntu One Files Java library - communicate with Ubuntu One storage API
755+ *
756+ * Copyright (C) 2012 Canonical Ltd.
757+ *
758+ * This file is part of Ubuntu One Files Java library.
759+ *
760+ * This program is free software: you can redistribute it and/or modify
761+ * it under the terms of the GNU Affero General Public License as
762+ * published by the Free Software Foundation, either version 3 of the
763+ * License, or (at your option) any later version.
764+ *
765+ * This program is distributed in the hope that it will be useful,
766+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
767+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
768+ * GNU Affero General Public License for more details.
769+ *
770+ * You should have received a copy of the GNU Affero General Public License
771+ * along with this program. If not, see http://www.gnu.org/licenses
772+ */
773+
774+package com.ubuntuone.api.music.util;
775+
776+import java.io.IOException;
777+import java.io.InputStream;
778+import java.io.OutputStream;
779+
780+import org.apache.http.HttpEntity;
781+import org.apache.http.entity.HttpEntityWrapper;
782+
783+public interface OnProgressListener
784+{
785+ /**
786+ * If the request is a transfer (upload or download), implement this method
787+ * to get call backs with the transfer progress.
788+ *
789+ * @param bytes
790+ * The number of bytes transferred.
791+ * @param total
792+ * The total number of bytes to transfer.
793+ */
794+ public abstract void onProgress(long bytes, long total);
795+
796+ // Decrease this value for more fine grained progress call backs.
797+ static final long PROGRESS_INTERVAL = 400L;
798+
799+ /**
800+ * An {@link HttpEntity} used for uploading content with feedback.
801+ */
802+ public static class ProgressHttpEntity extends HttpEntityWrapper
803+ {
804+ private static final int CHUNK_SIZE = 1024 * 8;
805+
806+ private HttpEntity entity;
807+
808+ private OnProgressListener listener;
809+
810+ public ProgressHttpEntity(HttpEntity entity, OnProgressListener listener) {
811+ super(entity);
812+ this.entity = entity;
813+ this.listener = listener != null ? listener : new DummyListener();
814+ }
815+
816+ public void writeTo(OutputStream out) throws IllegalStateException, IOException {
817+ final InputStream in = entity.getContent();
818+ long lastCallback = 0, now = 0;
819+
820+ byte buffer[] = new byte[CHUNK_SIZE];
821+ long length = entity.getContentLength();
822+ long total = 0L;
823+ int bytes = 0;
824+
825+ while ((bytes = in.read(buffer, 0, CHUNK_SIZE)) > 0) {
826+ out.write(buffer, 0, bytes);
827+ total += bytes;
828+ now = System.currentTimeMillis();
829+ if (now - lastCallback >= getProgressInterval()) {
830+ listener.onProgress(total, length);
831+ lastCallback = now;
832+ }
833+ }
834+ if (total == length) {
835+ listener.onProgress(total, length);
836+ }
837+ out.flush();
838+ out.close();
839+
840+ in.close();
841+ }
842+
843+ /**
844+ * Override for different update frequency interval.
845+ *
846+ * @return
847+ */
848+ public long getProgressInterval() {
849+ return PROGRESS_INTERVAL;
850+ }
851+ }
852+
853+ /**
854+ * An {@link InputStream} used for downloading content with feedback.
855+ */
856+ public static class ProgressInputStream
857+ {
858+ private static final int CHUNK_SIZE = 1024 * 8;
859+
860+ private InputStream stream;
861+ // Bytes already saved (for partial content requests).
862+ private long bytes;
863+
864+ private long length;
865+
866+ private OnProgressListener listener;
867+
868+ public ProgressInputStream(InputStream stream, long bytes, long length,
869+ OnProgressListener listener) {
870+ this.stream = stream;
871+ this.bytes = bytes;
872+ this.length = length;
873+ this.listener = listener != null ? listener : new DummyListener();
874+ }
875+
876+ public void writeTo(OutputStream out) throws IllegalStateException, IOException {
877+ long lastCallback = 0, now = 0;
878+
879+ byte buffer[] = new byte[CHUNK_SIZE];
880+ long total = this.bytes;
881+ int bytes = 0;
882+
883+ while ((bytes = stream.read(buffer, 0, CHUNK_SIZE)) > 0) {
884+ out.write(buffer, 0, bytes);
885+ total += bytes;
886+ now = System.currentTimeMillis();
887+ if (now - lastCallback >= getProgressInterval()) {
888+ listener.onProgress(total, length);
889+ lastCallback = now;
890+ }
891+ }
892+ if (total == length) {
893+ listener.onProgress(total, length);
894+ }
895+ out.flush();
896+ out.close();
897+
898+ stream.close();
899+ }
900+
901+ /**
902+ * Override for different update frequency interval.
903+ *
904+ * @return
905+ */
906+ public long getProgressInterval() {
907+ return PROGRESS_INTERVAL;
908+ }
909+ }
910+
911+ public class DummyListener implements OnProgressListener {
912+ @Override
913+ public void onProgress(long bytes, long total) {
914+ // Do nothing.
915+ }
916+ }
917+}
918
919=== modified file 'src/main/com/ubuntuone/api/music/util/RequestListener.java'
920--- src/main/com/ubuntuone/api/music/util/RequestListener.java 2012-06-19 01:09:20 +0000
921+++ src/main/com/ubuntuone/api/music/util/RequestListener.java 2012-06-19 01:09:20 +0000
922@@ -22,6 +22,7 @@
923 package com.ubuntuone.api.music.util;
924
925 import java.io.File;
926+import java.io.InputStream;
927
928 import org.apache.http.HttpStatus;
929
930@@ -76,4 +77,5 @@
931 public interface U1PlaylistSongRequestListener extends RequestListener<U1PlaylistSong> {};
932 public interface U1ArtRequestListener extends RequestListener<File> {};
933 public interface U1VoidRequestListener extends RequestListener<Void> {};
934+ public interface U1StreamRequestListener extends RequestListener<InputStream> {};
935 }
936\ No newline at end of file
937
938=== modified file 'src/test/com/ubuntuone/api/music/DeletePlaylistTest.java'
939--- src/test/com/ubuntuone/api/music/DeletePlaylistTest.java 2012-06-19 01:09:20 +0000
940+++ src/test/com/ubuntuone/api/music/DeletePlaylistTest.java 2012-06-19 01:09:20 +0000
941@@ -69,7 +69,7 @@
942 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
943
944 httpClient = context.mock(HttpClient.class);
945- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
946+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
947
948 requestCallback = context.mock(U1VoidRequestListener.class);
949 }
950
951=== modified file 'src/test/com/ubuntuone/api/music/GetAlbumsTest.java'
952--- src/test/com/ubuntuone/api/music/GetAlbumsTest.java 2012-06-19 01:09:20 +0000
953+++ src/test/com/ubuntuone/api/music/GetAlbumsTest.java 2012-06-19 01:09:20 +0000
954@@ -72,7 +72,7 @@
955 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
956
957 httpClient = context.mock(HttpClient.class);
958- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
959+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
960
961 albumCallback = context.mock(U1AlbumRequestListener.class);
962 }
963
964=== modified file 'src/test/com/ubuntuone/api/music/GetArtTest.java'
965--- src/test/com/ubuntuone/api/music/GetArtTest.java 2012-06-19 01:09:20 +0000
966+++ src/test/com/ubuntuone/api/music/GetArtTest.java 2012-06-19 01:09:20 +0000
967@@ -79,7 +79,7 @@
968 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
969
970 httpClient = context.mock(HttpClient.class);
971- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
972+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
973
974 artCallback = context.mock(U1ArtRequestListener.class);
975 }
976
977=== modified file 'src/test/com/ubuntuone/api/music/GetArtistsTest.java'
978--- src/test/com/ubuntuone/api/music/GetArtistsTest.java 2012-06-19 01:09:20 +0000
979+++ src/test/com/ubuntuone/api/music/GetArtistsTest.java 2012-06-19 01:09:20 +0000
980@@ -72,7 +72,7 @@
981 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
982
983 httpClient = context.mock(HttpClient.class);
984- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
985+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
986
987 artistCallback = context.mock(U1ArtistRequestListener.class);
988 }
989
990=== modified file 'src/test/com/ubuntuone/api/music/GetPlaylistTest.java'
991--- src/test/com/ubuntuone/api/music/GetPlaylistTest.java 2012-06-19 01:09:20 +0000
992+++ src/test/com/ubuntuone/api/music/GetPlaylistTest.java 2012-06-19 01:09:20 +0000
993@@ -71,7 +71,7 @@
994 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
995
996 httpClient = context.mock(HttpClient.class);
997- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
998+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
999
1000 playlistSongCallback = context.mock(U1PlaylistSongRequestListener.class);
1001 }
1002
1003=== modified file 'src/test/com/ubuntuone/api/music/GetPlaylistsTest.java'
1004--- src/test/com/ubuntuone/api/music/GetPlaylistsTest.java 2012-06-19 01:09:20 +0000
1005+++ src/test/com/ubuntuone/api/music/GetPlaylistsTest.java 2012-06-19 01:09:20 +0000
1006@@ -72,7 +72,7 @@
1007 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
1008
1009 httpClient = context.mock(HttpClient.class);
1010- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
1011+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
1012
1013 playlistCallback = context.mock(U1PlaylistRequestListener.class);
1014 }
1015
1016=== added file 'src/test/com/ubuntuone/api/music/GetSongStreamTest.java'
1017--- src/test/com/ubuntuone/api/music/GetSongStreamTest.java 1970-01-01 00:00:00 +0000
1018+++ src/test/com/ubuntuone/api/music/GetSongStreamTest.java 2012-06-19 01:09:20 +0000
1019@@ -0,0 +1,130 @@
1020+/*
1021+ * Ubuntu One Music Java library - communicate with Ubuntu One music API
1022+ *
1023+ * Copyright 2012 Canonical Ltd.
1024+ *
1025+ * This file is part of Ubuntu One Files Java library.
1026+ *
1027+ * This program is free software: you can redistribute it and/or modify
1028+ * it under the terms of the GNU Affero General Public License as
1029+ * published by the Free Software Foundation, either version 3 of the
1030+ * License, or (at your option) any later version.
1031+ *
1032+ * This program is distributed in the hope that it will be useful,
1033+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1034+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1035+ * GNU Affero General Public License for more details.
1036+ *
1037+ * You should have received a copy of the GNU Affero General Public License
1038+ * along with this program. If not, see http://www.gnu.org/licenses
1039+ */
1040+
1041+package com.ubuntuone.api.music;
1042+
1043+import static com.ubuntuone.test.util.SameHttpRequestMatcher.sameRequest;
1044+
1045+import java.io.ByteArrayInputStream;
1046+import java.io.IOException;
1047+import java.io.InputStream;
1048+
1049+import org.apache.http.HttpHost;
1050+import org.apache.http.HttpResponse;
1051+import org.apache.http.HttpStatus;
1052+import org.apache.http.client.HttpClient;
1053+import org.apache.http.client.methods.HttpGet;
1054+import org.apache.http.entity.InputStreamEntity;
1055+import org.jmock.Expectations;
1056+import org.jmock.Mockery;
1057+import org.jmock.Sequence;
1058+import org.jmock.integration.junit4.JMock;
1059+import org.jmock.integration.junit4.JUnit4Mockery;
1060+import org.junit.Before;
1061+import org.junit.Test;
1062+import org.junit.runner.RunWith;
1063+
1064+import com.ubuntuone.api.music.client.BaseClient;
1065+import com.ubuntuone.api.music.client.Failure;
1066+import com.ubuntuone.api.music.model.U1Song;
1067+import com.ubuntuone.api.music.util.RequestListener.U1StreamRequestListener;
1068+import com.ubuntuone.music.util.U1SongBuilder;
1069+import com.ubuntuone.music.util.Util;
1070+import com.ubuntuone.test.util.DummyAuthorizer;
1071+
1072+@RunWith(JMock.class)
1073+public class GetSongStreamTest
1074+{
1075+ private final Mockery context = new JUnit4Mockery();
1076+
1077+ private HttpResponse httpResponse;
1078+
1079+ private HttpHost httpHost =
1080+ new HttpHost(U1MusicAPI.STREAMING_HOST, -1, BaseClient.HTTPS);
1081+
1082+ private HttpClient httpClient;
1083+ private U1MusicAPI musicApi;
1084+
1085+ private U1StreamRequestListener streamCallback;
1086+
1087+ @Before
1088+ public void setUp() throws IOException {
1089+ httpResponse = Util.buildResponse(HttpStatus.SC_OK);
1090+
1091+ httpClient = context.mock(HttpClient.class);
1092+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
1093+
1094+ streamCallback = context.mock(U1StreamRequestListener.class);
1095+ }
1096+
1097+ @Test
1098+ public void testGetSongStreamLifeCycleCallbacks() {
1099+ U1Song song = new U1SongBuilder().build();
1100+ long offset = 0L;
1101+
1102+ final Sequence requestSequence = context.sequence("lifecycle");
1103+ context.checking(new Expectations() {{
1104+ oneOf(streamCallback).onStart();
1105+ inSequence(requestSequence);
1106+
1107+ ignoring(httpClient);
1108+ allowing(streamCallback).onSuccess(with(any(InputStream.class)));
1109+ allowing(streamCallback).onFailure(with(any(Failure.class)));
1110+
1111+ oneOf(streamCallback).onFinish();
1112+ inSequence(requestSequence);
1113+ }});
1114+
1115+ musicApi.getSongStream(song, offset, streamCallback);
1116+ }
1117+
1118+ @Test
1119+ public void testGetSongStream() throws IOException {
1120+ final U1Song song = new U1SongBuilder().build();
1121+ final long offset = 0L;
1122+
1123+ final HttpGet httpRequest =
1124+ new HttpGet("https://streaming.one.ubuntu.com/stream/" + song.getId() + "/");
1125+
1126+ String songContent = "song_content";
1127+ final InputStream instream = new ByteArrayInputStream(songContent.getBytes("UTF-8"));
1128+ long length = songContent.length();
1129+ httpResponse.setEntity(new InputStreamEntity(instream, length));
1130+
1131+ final Sequence requestSequence = context.sequence("lifecycle");
1132+ context.checking(new Expectations() {{
1133+ oneOf(streamCallback).onStart();
1134+ inSequence(requestSequence);
1135+
1136+ oneOf(httpClient).execute(with(httpHost), with(sameRequest(httpRequest)));
1137+ inSequence(requestSequence);
1138+ will(returnValue(httpResponse));
1139+
1140+ oneOf(streamCallback).onSuccess(with(instream));
1141+ inSequence(requestSequence);
1142+
1143+ oneOf(streamCallback).onFinish();
1144+ inSequence(requestSequence);
1145+ }});
1146+
1147+ musicApi.getSongStream(song, offset, streamCallback);
1148+ }
1149+}
1150
1151=== modified file 'src/test/com/ubuntuone/api/music/GetSongsTest.java'
1152--- src/test/com/ubuntuone/api/music/GetSongsTest.java 2012-06-19 01:09:20 +0000
1153+++ src/test/com/ubuntuone/api/music/GetSongsTest.java 2012-06-19 01:09:20 +0000
1154@@ -72,7 +72,7 @@
1155 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
1156
1157 httpClient = context.mock(HttpClient.class);
1158- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
1159+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
1160
1161 songCallback = context.mock(U1SongRequestListener.class);
1162 }
1163
1164=== modified file 'src/test/com/ubuntuone/api/music/PutPlaylistTest.java'
1165--- src/test/com/ubuntuone/api/music/PutPlaylistTest.java 2012-06-19 01:09:20 +0000
1166+++ src/test/com/ubuntuone/api/music/PutPlaylistTest.java 2012-06-19 01:09:20 +0000
1167@@ -72,7 +72,7 @@
1168 httpResponse = Util.buildResponse(HttpStatus.SC_OK);
1169
1170 httpClient = context.mock(HttpClient.class);
1171- musicApi = new U1MusicAPI(httpClient, new DummyAuthorizer());
1172+ musicApi = new U1MusicAPI("ua/1.0", httpClient, new DummyAuthorizer());
1173
1174 playlistSongCallback = context.mock(U1PlaylistSongRequestListener.class);
1175 }

Subscribers

People subscribed via source and target branches