Skip to content

Commit

Permalink
fix(#19): Playlist picture upload implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
user4me committed Jan 19, 2023
1 parent f130c4b commit d4c7b04
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 13 deletions.
2 changes: 2 additions & 0 deletions src/main/java/api/deezer/http/HttpClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api.deezer.http;

import java.io.IOException;
import java.io.OutputStream;

/**
* Executes HTTP requests.
Expand All @@ -14,4 +15,5 @@ public interface HttpClient {
* @throws IOException if errors occur.
*/
HttpResponse execute(HttpRequest httpRequest) throws IOException;

}
8 changes: 8 additions & 0 deletions src/main/java/api/deezer/http/HttpRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ public interface HttpRequest {
* @return request URL params.
*/
Map<String, String> getParams();

/**
* File as bytes.
*
* @return the file as bytes
*/
HttpRequestFilePart[] getFileParts();

}
55 changes: 55 additions & 0 deletions src/main/java/api/deezer/http/HttpRequestFilePart.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package api.deezer.http;

public class HttpRequestFilePart {

private String name = null;
private String filename = null;
private String contentType = null;
private String contentTransferEncoding = null;
private byte[] value = null;

public HttpRequestFilePart(final String name, final byte[] value) {
this.name = name;
this.value = value;
}

public static HttpRequestFilePart image(final String name, final byte[] value){
HttpRequestFilePart image = new HttpRequestFilePart(name, value);
image.setContentType("image/png");
image.setFilename("image.png");
return image;
}

public String getName() {
return name;
}

public String getFilename() {
return filename;
}

public void setFilename(String filename) {
this.filename = filename;
}

public String getContentType() {
return contentType;
}

public void setContentType(String contentType) {
this.contentType = contentType;
}

public String getContentTransferEncoding() {
return contentTransferEncoding;
}

public void setContentTransferEncoding(String contentTransferEncoding) {
this.contentTransferEncoding = contentTransferEncoding;
}

public byte[] getValue() {
return value;
}

}
9 changes: 9 additions & 0 deletions src/main/java/api/deezer/http/impl/DeezerPostRequest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package api.deezer.http.impl;

import api.deezer.converters.PojoConverter;
import api.deezer.http.HttpRequestFilePart;

import java.util.HashMap;
import java.util.Map;

Expand All @@ -17,4 +20,10 @@ public DeezerPostRequest(String url, Map<String, String> params, Class<Response>
super(url, params, responseClass);
params.put("request_method", "post");
}

public DeezerPostRequest(String url, Map<String, String> params, Class<Response> responseClass, HttpRequestFilePart[] parts) {
super(url, params, new PojoConverter<>(responseClass), parts);
params.put("request_method", "post");
}

}
20 changes: 20 additions & 0 deletions src/main/java/api/deezer/http/impl/DeezerRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import api.deezer.converters.PojoConverter;
import api.deezer.exceptions.DeezerException;
import api.deezer.http.HttpClient;
import api.deezer.http.HttpRequestFilePart;
import api.deezer.http.HttpRequest;
import api.deezer.http.HttpResponse;
import api.deezer.validators.DeezerResponseValidator;
Expand All @@ -28,6 +29,11 @@ public abstract class DeezerRequest<Response> implements HttpRequest {
*/
private final Map<String, String> params;

/**
* File
*/
private final HttpRequestFilePart[] parts;

/**
* Converts Deezer response.
*/
Expand All @@ -52,8 +58,17 @@ public DeezerRequest(String url, Map<String, String> params, Function<String, Re
}

public DeezerRequest(String url, Map<String, String> params, Predicate<HttpResponse> responseValidator, Function<String, Response> responseConverter) {
this(url, params, responseValidator, responseConverter, null);
}

public DeezerRequest(String url, Map<String, String> params, Function<String, Response> responseConverter, HttpRequestFilePart[] parts) {
this(url, params, new DeezerResponseValidator(), responseConverter, parts);
}

public DeezerRequest(String url, Map<String, String> params, Predicate<HttpResponse> responseValidator, Function<String, Response> responseConverter, HttpRequestFilePart[] parts) {
this.url = url;
this.params = params;
this.parts = parts;
this.responseValidator = responseValidator;
this.responseConverter = responseConverter;
}
Expand All @@ -76,6 +91,11 @@ public Response execute() throws DeezerException {
}
}

@Override
public HttpRequestFilePart[] getFileParts() {
return parts;
}

@Override
public String getUrl() {
return url;
Expand Down
72 changes: 59 additions & 13 deletions src/main/java/api/deezer/http/impl/DefaultHttpClient.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package api.deezer.http.impl;

import api.deezer.http.HttpClient;
import api.deezer.http.HttpRequestFilePart;
import api.deezer.http.HttpRequest;
import api.deezer.http.HttpResponse;
import api.deezer.http.utils.URLParamsEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
Expand All @@ -36,16 +34,11 @@ public HttpResponse execute(HttpRequest httpRequest) throws IOException {

HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(httpRequest.getUrl()).openConnection();
connection.setRequestMethod(httpRequest.getRequestMethod());
connection.setDoOutput(true);

Map<String, String> params = httpRequest.getParams();
if (params != null && !params.isEmpty()) {
try (DataOutputStream output = new DataOutputStream(connection.getOutputStream())) {
output.writeBytes(URLParamsEncoder.encode(params));
output.flush();
}
if (httpRequest.getFileParts() == null || httpRequest.getFileParts().length == 0) {
connection = prepareRegularRequest(httpRequest);
} else {
connection = prepareMultipartRequest(httpRequest);
}

try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
Expand All @@ -66,4 +59,57 @@ public HttpResponse execute(HttpRequest httpRequest) throws IOException {
}
}
}

private static HttpURLConnection prepareRegularRequest(HttpRequest httpRequest) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(httpRequest.getUrl()).openConnection();
connection.setRequestMethod(httpRequest.getRequestMethod());
connection.setDoOutput(true);
Map<String, String> params = httpRequest.getParams();
if (params != null && !params.isEmpty()) {
try (DataOutputStream output = new DataOutputStream(connection.getOutputStream())) {
output.writeBytes(URLParamsEncoder.encode(params));
output.flush();
}
}
return connection;
}


private static HttpURLConnection prepareMultipartRequest(HttpRequest httpRequest) throws IOException {
Map<String, String> params = httpRequest.getParams();
String urlParams = "";
if (params != null && !params.isEmpty()) {
urlParams = "?" + URLParamsEncoder.encode(params);
}
HttpURLConnection connection = (HttpURLConnection) new URL(httpRequest.getUrl() + urlParams).openConnection();
connection.setRequestMethod(httpRequest.getRequestMethod());
connection.setDoOutput(true);
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "----" + System.currentTimeMillis() + "----";
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
for (HttpRequestFilePart part : httpRequest.getFileParts()) {
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
outputStream.writeBytes("Content-Disposition: form-data; name=\"" + part.getName() + "\"");
if (part.getFilename() != null)
outputStream.writeBytes("; filename=\"" + part.getFilename() + "\"");
outputStream.writeBytes(lineEnd);

if (part.getContentType() != null)
outputStream.writeBytes("Content-Type: " + part.getContentType() + lineEnd);
if (part.getContentTransferEncoding() != null)
outputStream.writeBytes("Content-Transfer-Encoding: " + part.getContentTransferEncoding() + lineEnd);
outputStream.writeBytes(lineEnd);
outputStream.write(part.getValue());
outputStream.writeBytes(lineEnd);
}
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
outputStream.flush();
}
return connection;
}



}
12 changes: 12 additions & 0 deletions src/main/java/api/deezer/objects/Infos.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public class Infos {
@SerializedName("open")
private Boolean isOpen;

@SerializedName("upload_token")
private String uploadToken;

/**
* An array of available offers in the current country
*/
Expand Down Expand Up @@ -64,13 +67,22 @@ public void setOffers(List<Offer> offers) {
this.offers = offers;
}

public String getUploadToken() {
return uploadToken;
}

public void setUploadToken(String uploadToken) {
this.uploadToken = uploadToken;
}

@Override
public String toString() {
return "Infos{" +
"country_iso='" + country_iso + '\'' +
", country='" + country + '\'' +
", open=" + isOpen +
", offers=" + offers +
", uploadToken='" + uploadToken + + '\'' +
'}';
}
}
21 changes: 21 additions & 0 deletions src/main/java/api/deezer/requests/PlaylistRequests.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import api.deezer.converters.ListConverter;
import api.deezer.converters.TracksDataConverter;
import api.deezer.http.HttpRequestFilePart;
import api.deezer.http.impl.DeezerDeleteRequest;
import api.deezer.http.impl.DeezerGetRequest;
import api.deezer.http.impl.DeezerPostRequest;
Expand Down Expand Up @@ -102,6 +103,26 @@ public PaginationRequest<TrackData> getRadio(long playlistId) {
);
}

/**
* Adds tracks to playlist
*
* @param playlistId playlist ID.
* @param uploadToken the upload token provided by {@link InfosRequests#get()}.
* @param image the image
* @return <i>true</i> if successful.
*/
public DeezerRequest<Boolean> uploadPicture(long playlistId, final String uploadToken, byte[] image) {
Map<String, String> params = accessTokenParam();
params.put("upload_token", uploadToken);

return new DeezerPostRequest<>(
property("playlist.picture", playlistId),
params,
Boolean.class,
new HttpRequestFilePart[]{ HttpRequestFilePart.image("file", image) }
);
}

/**
* Adds tracks to playlist
*
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/deezer/api.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ playlist.seen=https://api.deezer.com/playlist/%d/seen
playlist.fans=https://api.deezer.com/playlist/%d/fans
playlist.tracks=https://api.deezer.com/playlist/%d/tracks
playlist.radio=https://api.deezer.com/playlist/%d/radio
playlist.picture=https://upload.deezer.com/playlist/%d
radio.get=https://api.deezer.com/radio
search.album=https://api.deezer.com/search/album
search.artist=https://api.deezer.com/search/artist
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/api/deezer/requests/PlaylistRequestsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import api.deezer.objects.data.UserData;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.assertEquals;

class PlaylistRequestsTest {
Expand Down Expand Up @@ -55,6 +57,14 @@ void getRadio() {
assertEquals("1", request.getParams().get("index"));
}

@Test
void uploadPicture() {
DeezerRequest<Boolean> request = deezerApi.playlist().uploadPicture(908622995, "abcdefg", "abcdefg".getBytes(StandardCharsets.UTF_8));
assertEquals("https://upload.deezer.com/playlist/908622995", request.getUrl());
assertEquals("post", request.getParams().get("request_method"));
assertEquals("abcdefg", request.getParams().get("upload_token"));
}

@Test
void addTracks() {
DeezerRequest<Boolean> request = deezerApi.playlist().addTracks(908622995, 1111111111111L, 222L, 333L);
Expand Down

0 comments on commit d4c7b04

Please sign in to comment.