From 92adbe78c585252ce10caf67a564351fea119c93 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Fri, 31 Jul 2020 10:41:56 +0200 Subject: [PATCH 01/18] [ci skip] Bump to snapshot version --- api/pom.xml | 2 +- lib/pom.xml | 2 +- player/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index d0b03128..5bfe2abe 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.1 + 1.5.2-SNAPSHOT ../ diff --git a/lib/pom.xml b/lib/pom.xml index 33330e63..fca5278a 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.1 + 1.5.2-SNAPSHOT ../ diff --git a/player/pom.xml b/player/pom.xml index 8f32c11f..87042b9a 100644 --- a/player/pom.xml +++ b/player/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.1 + 1.5.2-SNAPSHOT ../ diff --git a/pom.xml b/pom.xml index 138e65c2..a8451549 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java pom - 1.5.1 + 1.5.2-SNAPSHOT librespot-java Java port of librespot, the Open Source Spotify client library From d3149d3843e066986524e14369c5871c22629810 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Wed, 23 Sep 2020 21:01:22 +0200 Subject: [PATCH 02/18] Added #waitReady() method to avoid using player before initialization --- .../xyz/gianlu/librespot/player/Player.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/Player.java b/player/src/main/java/xyz/gianlu/librespot/player/Player.java index a06b42e5..bbe0a642 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/Player.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/Player.java @@ -159,6 +159,31 @@ public void removeFromQueue(@NotNull String uri) { state.updated(); } + @NotNull + public Future waitReady() { + CompletableFuture future = new CompletableFuture<>(); + state.addListener(new DeviceStateHandler.Listener() { + @Override + public void ready() { + future.complete(Player.this); + } + + @Override + public void command(DeviceStateHandler.@NotNull Endpoint endpoint, DeviceStateHandler.@NotNull CommandBody data) throws InvalidProtocolBufferException { + } + + @Override + public void volumeChanged() { + } + + @Override + public void notActive() { + } + }); + + return future; + } + // ================================ // // ======== Internal state ======== // From f791fbad5d5b4e25fb0eac3909c7c31b36424278 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 27 Sep 2020 16:59:23 +0200 Subject: [PATCH 03/18] Updated disclaimer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e62a9204..7ee8f900 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ `librespot-java` is a port of [librespot](https://github.com/librespot-org/librespot), originally written in Rust, which has evolved into the most up-to-date open-source Spotify client. Additionally, this implementation provides a useful API to request metadata or control the player, more [here](api). ## Disclaimer! -We (the librespot-org organization and me) **DO NOT** encourage piracy and **DO NOT** support any form of downloader/recorder designed with the help of this repository. If you're brave enough to put at risk this entire project, just don't publish it. This is meant to provide support for all those devices that are not officially supported by Spotify. +We (the librespot-org organization and me) **DO NOT** encourage piracy and **DO NOT** support any form of downloader/recorder designed with the help of this repository and in general anything that goes against the Spotify ToS. If you're brave enough to put at risk this entire project, just don't publish it. This is meant to provide support for all those devices that are not officially supported by Spotify. ## Features This client is pretty much capable of playing anything that's available on Spotify. From e953129ed5f0dc4e9931660bd216267557d6010a Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sun, 4 Oct 2020 16:12:21 +0200 Subject: [PATCH 04/18] Make sure that cache files and queue entries are closed --- .../java/xyz/gianlu/librespot/audio/cdn/CdnManager.java | 5 +++++ .../gianlu/librespot/audio/storage/AudioFileStreaming.java | 6 ++++++ .../gianlu/librespot/player/playback/PlayerQueueEntry.java | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java index 3df431dd..fc2ea48f 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java @@ -344,6 +344,11 @@ private InternalStream(boolean retryOnChunkError) { public void close() { super.close(); executorService.shutdown(); + + try { + cacheHandler.close(); + } catch (IOException ignored) { + } } @Override diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java index dc5908b8..859091ac 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java @@ -156,6 +156,11 @@ public void close() { executorService.shutdown(); if (chunksBuffer != null) chunksBuffer.close(); + + try { + cacheHandler.close(); + } catch (IOException ignored) { + } } private class ChunksBuffer implements Closeable { @@ -194,6 +199,7 @@ AbsChunkedInputStream stream() { @Override public void close() { internalStream.close(); + AudioFileStreaming.this.close(); } private class InternalStream extends AbsChunkedInputStream { diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java index 5a44829e..e8b66b09 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java @@ -352,6 +352,11 @@ boolean closeIfUseless() { public void close() { closed = true; clearOutput(); + + try { + codec.close(); + } catch (IOException ignored) { + } } @Override From 1f4f5ebd29ef3f69d20a767dabe0cafd51875df2 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sun, 4 Oct 2020 18:05:17 +0200 Subject: [PATCH 05/18] Fixed NPE --- .../java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java b/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java index 84bd796f..a9575af4 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java @@ -223,7 +223,7 @@ public void close() throws IOException { joggStreamState.clear(); jorbisBlock.clear(); jorbisDspState.clear(); - jorbisInfo.clear(); + // jorbisInfo.clear(); joggSyncState.clear(); } } From dfc88a27c2ca4b9862931aee11c46c6546707c75 Mon Sep 17 00:00:00 2001 From: Jordi735 Date: Tue, 6 Oct 2020 14:23:40 +0200 Subject: [PATCH 06/18] Added cacheHandler checking (#253) + Added check in CdnManager.java + Added check in AudioFileStreaming.java --- .../java/xyz/gianlu/librespot/audio/cdn/CdnManager.java | 8 +++++--- .../librespot/audio/storage/AudioFileStreaming.java | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java index fc2ea48f..4b3ce875 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java @@ -345,9 +345,11 @@ public void close() { super.close(); executorService.shutdown(); - try { - cacheHandler.close(); - } catch (IOException ignored) { + if (cacheHandler != null) { + try { + cacheHandler.close(); + } catch (IOException ignored) { + } } } diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java index 859091ac..81205ac9 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java @@ -157,9 +157,11 @@ public void close() { if (chunksBuffer != null) chunksBuffer.close(); - try { - cacheHandler.close(); - } catch (IOException ignored) { + if (cacheHandler != null) { + try { + cacheHandler.close(); + } catch (IOException ignored) { + } } } From cdbd1d70ee29fdccd8a9e8021b88e977fbfb65eb Mon Sep 17 00:00:00 2001 From: Gianlu Date: Thu, 8 Oct 2020 10:50:18 +0200 Subject: [PATCH 07/18] Fixed NPE when skipping before song loaded --- .../xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java index e8b66b09..f3b5d83e 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java @@ -354,7 +354,7 @@ public void close() { clearOutput(); try { - codec.close(); + if (codec != null) codec.close(); } catch (IOException ignored) { } } From ad1702f2392c7e43d3b1a8d78446c2039b5ddf20 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Tue, 13 Oct 2020 10:05:02 +0200 Subject: [PATCH 08/18] Added pass through endpoint to API (#255) --- api/README.md | 6 ++ .../xyz/gianlu/librespot/api/ApiServer.java | 7 +- .../librespot/api/handlers/WebApiHandler.java | 64 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/xyz/gianlu/librespot/api/handlers/WebApiHandler.java diff --git a/api/README.md b/api/README.md index 04b131c1..ac95fa43 100644 --- a/api/README.md +++ b/api/README.md @@ -57,9 +57,15 @@ The currently available events are: - `connectionEstablished` Successfully reconnected - `panic` Entered the panic state, playback is stopped. This is usually recoverable. +### Web API pass through +Use any endpoint from the [public Web API](https://developer.spotify.com/documentation/web-api/reference/) by appending it to `/web-api/`, the request will be made to the API with the correct `Authorization` header and the result will be returned. +The method, body, and content type headers will pass through. Additionally, you can specify an `X-Spotify-Scope` header to override the requested scope, by default all will be requested. + ## Examples `curl -X POST -d "uri=spotify:track:xxxxxxxxxxxxxxxxxxxxxx&play=true" http://localhost:24879/player/load` `curl -X POST http://localhost:24879/metadata/track/spotify:track:xxxxxxxxxxxxxxxxxxxxxx` `curl -X POST http://localhost:24879/metadata/spotify:track:xxxxxxxxxxxxxxxxxxxxxx` + +`curl -X GET http://localhost:24879/web-api/v1/me/top/artists` diff --git a/api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java b/api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java index 1d5f5952..28eeacfb 100644 --- a/api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java +++ b/api/src/main/java/xyz/gianlu/librespot/api/ApiServer.java @@ -2,6 +2,8 @@ import io.undertow.Undertow; import io.undertow.server.RoutingHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.server.handlers.ResponseCodeHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -24,7 +26,10 @@ public ApiServer(int port, @NotNull String host, @NotNull SessionWrapper wrapper .post("/search/{query}", new SearchHandler(wrapper)) .post("/token/{scope}", new TokensHandler(wrapper)) .post("/profile/{user_id}/{action}", new ProfileHandler(wrapper)) - .get("/events", events); + .post("/web-api/{endpoint}", new WebApiHandler(wrapper)) + .get("/events", events) + .setFallbackHandler(new PathHandler(ResponseCodeHandler.HANDLE_404) + .addPrefixPath("/web-api", new WebApiHandler(wrapper))); wrapper.setListener(events); } diff --git a/api/src/main/java/xyz/gianlu/librespot/api/handlers/WebApiHandler.java b/api/src/main/java/xyz/gianlu/librespot/api/handlers/WebApiHandler.java new file mode 100644 index 00000000..4642831a --- /dev/null +++ b/api/src/main/java/xyz/gianlu/librespot/api/handlers/WebApiHandler.java @@ -0,0 +1,64 @@ +package xyz.gianlu.librespot.api.handlers; + +import io.undertow.server.HttpServerExchange; +import io.undertow.util.FileUtils; +import io.undertow.util.HeaderValues; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.api.SessionWrapper; +import xyz.gianlu.librespot.core.Session; +import xyz.gianlu.librespot.core.TokenProvider; + +public final class WebApiHandler extends AbsSessionHandler { + private static final String[] API_TOKENS_ALL = new String[]{"ugc-image-upload", "playlist-read-collaborative", "playlist-modify-private", "playlist-modify-public", "playlist-read-private", "user-read-playback-position", "user-read-recently-played", "user-top-read", "user-modify-playback-state", "user-read-currently-playing", "user-read-playback-state", "user-read-private", "user-read-email", "user-library-modify", "user-library-read", "user-follow-modify", "user-follow-read", "streaming", "app-remote-control"}; + private static final HttpUrl BASE_API_URL = HttpUrl.get("https://api.spotify.com"); + private static final HttpString HEADER_X_SCOPE = HttpString.tryFromString("X-Spotify-Scope"); + + public WebApiHandler(@NotNull SessionWrapper wrapper) { + super(wrapper); + } + + @Override + protected void handleRequest(@NotNull HttpServerExchange exchange, @NotNull Session session) throws Exception { + exchange.startBlocking(); + if (exchange.isInIoThread()) { + exchange.dispatch(this); + return; + } + + String body = FileUtils.readFile(exchange.getInputStream()); + HeaderValues contentType = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE); + + String[] scopes = API_TOKENS_ALL; + if (exchange.getRequestHeaders().contains(HEADER_X_SCOPE)) + scopes = exchange.getRequestHeaders().get(HEADER_X_SCOPE).toArray(new String[0]); + + TokenProvider.StoredToken token = session.tokens().getToken(scopes); + + HttpUrl.Builder url = BASE_API_URL.newBuilder() + .addPathSegments(exchange.getRelativePath().substring(1)) + .query(exchange.getQueryString()); + + Request.Builder req = new Request.Builder() + .url(url.build()) + .addHeader("Authorization", "Bearer " + token.accessToken); + + String method = exchange.getRequestMethod().toString(); + if (!body.isEmpty() && contentType != null) + req.method(method, RequestBody.create(body, MediaType.get(contentType.getFirst()))); + else + req.method(method, null); + + try (Response resp = session.client().newCall(req.build()).execute()) { + exchange.setStatusCode(resp.code()); + + String respContentType = resp.header("Content-Type"); + if (respContentType != null) exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, respContentType); + + ResponseBody respBody = resp.body(); + if (respBody != null) exchange.getOutputStream().write(respBody.bytes()); + } + } +} From 71a9d8ac09ff06ef569e2ba3079dd350d2856f73 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 16 Oct 2020 15:16:26 +0200 Subject: [PATCH 09/18] Handle null `player_options_override` (#254) --- .../java/xyz/gianlu/librespot/common/ProtoUtils.java | 10 ++++++---- .../librespot/player/state/DeviceStateHandler.java | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/xyz/gianlu/librespot/common/ProtoUtils.java b/lib/src/main/java/xyz/gianlu/librespot/common/ProtoUtils.java index a77d1f1a..653e36a1 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/common/ProtoUtils.java +++ b/lib/src/main/java/xyz/gianlu/librespot/common/ProtoUtils.java @@ -181,12 +181,14 @@ public static Player.PlayOrigin convertPlayOrigin(@Nullable PlayOrigin po) { @NotNull - public static ContextPlayerOptions jsonToPlayerOptions(@NotNull JsonObject obj, @Nullable ContextPlayerOptions old) { + public static ContextPlayerOptions jsonToPlayerOptions(@Nullable JsonObject obj, @Nullable ContextPlayerOptions old) { ContextPlayerOptions.Builder builder = old == null ? ContextPlayerOptions.newBuilder() : old.toBuilder(); - Optional.ofNullable(obj.get("repeating_context")).ifPresent(elm -> builder.setRepeatingContext(elm.getAsBoolean())); - Optional.ofNullable(obj.get("repeating_track")).ifPresent(elm -> builder.setRepeatingTrack(elm.getAsBoolean())); - Optional.ofNullable(obj.get("shuffling_context")).ifPresent(elm -> builder.setShufflingContext(elm.getAsBoolean())); + if (obj != null) { + Optional.ofNullable(obj.get("repeating_context")).ifPresent(elm -> builder.setRepeatingContext(elm.getAsBoolean())); + Optional.ofNullable(obj.get("repeating_track")).ifPresent(elm -> builder.setRepeatingTrack(elm.getAsBoolean())); + Optional.ofNullable(obj.get("shuffling_context")).ifPresent(elm -> builder.setShufflingContext(elm.getAsBoolean())); + } return builder.build(); } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/state/DeviceStateHandler.java b/player/src/main/java/xyz/gianlu/librespot/player/state/DeviceStateHandler.java index b6b193d7..4f5c8211 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/state/DeviceStateHandler.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/state/DeviceStateHandler.java @@ -323,7 +323,7 @@ public static JsonObject getContext(@NotNull JsonObject obj) { return obj.getAsJsonObject("context"); } - @NotNull + @Nullable public static JsonObject getPlayerOptionsOverride(@NotNull JsonObject obj) { return obj.getAsJsonObject("options").getAsJsonObject("player_options_override"); } From 948f72cc84e4ea0834ac615eb64006314d640f69 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sat, 17 Oct 2020 10:40:46 +0200 Subject: [PATCH 10/18] Do not pause if not specified (#254) --- player/src/main/java/xyz/gianlu/librespot/player/Player.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/Player.java b/player/src/main/java/xyz/gianlu/librespot/player/Player.java index bbe0a642..f9d46b26 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/Player.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/Player.java @@ -180,7 +180,6 @@ public void volumeChanged() { public void notActive() { } }); - return future; } @@ -340,7 +339,7 @@ private void handlePlay(@NotNull JsonObject obj) { events.contextChanged(); Boolean paused = PlayCommandHelper.isInitiallyPaused(obj); - if (paused == null) paused = true; + if (paused == null) paused = false; loadSession(sessionId, !paused, PlayCommandHelper.willSkipToSomething(obj)); } catch (IOException | MercuryClient.MercuryException ex) { LOGGER.fatal("Failed loading context!", ex); From f752b8f8d85cf83502e994b811f33f29b183f0a2 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sun, 18 Oct 2020 21:23:53 +0200 Subject: [PATCH 11/18] Fixed tests path --- .../test/{ => java}/xyz/gianlu/librespot/SpotifyUrisTest.java | 0 .../test/{ => java}/xyz/gianlu/librespot/cache/CacheTest.java | 0 .../xyz/gianlu/librespot/common/AsyncProcessorTest.java | 0 .../{ => java}/xyz/gianlu/librespot/common/FisherYatesTest.java | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename lib/src/main/test/{ => java}/xyz/gianlu/librespot/SpotifyUrisTest.java (100%) rename lib/src/main/test/{ => java}/xyz/gianlu/librespot/cache/CacheTest.java (100%) rename lib/src/main/test/{ => java}/xyz/gianlu/librespot/common/AsyncProcessorTest.java (100%) rename lib/src/main/test/{ => java}/xyz/gianlu/librespot/common/FisherYatesTest.java (100%) diff --git a/lib/src/main/test/xyz/gianlu/librespot/SpotifyUrisTest.java b/lib/src/main/test/java/xyz/gianlu/librespot/SpotifyUrisTest.java similarity index 100% rename from lib/src/main/test/xyz/gianlu/librespot/SpotifyUrisTest.java rename to lib/src/main/test/java/xyz/gianlu/librespot/SpotifyUrisTest.java diff --git a/lib/src/main/test/xyz/gianlu/librespot/cache/CacheTest.java b/lib/src/main/test/java/xyz/gianlu/librespot/cache/CacheTest.java similarity index 100% rename from lib/src/main/test/xyz/gianlu/librespot/cache/CacheTest.java rename to lib/src/main/test/java/xyz/gianlu/librespot/cache/CacheTest.java diff --git a/lib/src/main/test/xyz/gianlu/librespot/common/AsyncProcessorTest.java b/lib/src/main/test/java/xyz/gianlu/librespot/common/AsyncProcessorTest.java similarity index 100% rename from lib/src/main/test/xyz/gianlu/librespot/common/AsyncProcessorTest.java rename to lib/src/main/test/java/xyz/gianlu/librespot/common/AsyncProcessorTest.java diff --git a/lib/src/main/test/xyz/gianlu/librespot/common/FisherYatesTest.java b/lib/src/main/test/java/xyz/gianlu/librespot/common/FisherYatesTest.java similarity index 100% rename from lib/src/main/test/xyz/gianlu/librespot/common/FisherYatesTest.java rename to lib/src/main/test/java/xyz/gianlu/librespot/common/FisherYatesTest.java From 9ab9f43a91ebbce0e9a3a3c6f3c55a714c756525 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sun, 18 Oct 2020 21:48:24 +0200 Subject: [PATCH 12/18] Added hash check for first chunk of cached data --- .../librespot/audio/cdn/CdnManager.java | 5 +- .../audio/storage/AudioFileStreaming.java | 2 +- .../gianlu/librespot/cache/CacheManager.java | 68 +++++++++++++++++-- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java index 4b3ce875..51ddf514 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java @@ -206,7 +206,6 @@ private Streamer(@NotNull StreamId streamId, @NotNull SuperAudioFormat format, @ boolean fromCache; byte[] firstChunk; byte[] sizeHeader; - if (cacheHandler != null && (sizeHeader = cacheHandler.getHeader(AudioFileFetch.HEADER_SIZE)) != null) { size = ByteBuffer.wrap(sizeHeader).getInt() * 4; chunks = (size + CHUNK_SIZE - 1) / CHUNK_SIZE; @@ -214,7 +213,7 @@ private Streamer(@NotNull StreamId streamId, @NotNull SuperAudioFormat format, @ try { firstChunk = cacheHandler.readChunk(0); fromCache = true; - } catch (IOException ex) { + } catch (IOException | CacheManager.BadChunkHashException ex) { LOGGER.error("Failed getting first chunk from cache.", ex); InternalResponse resp = request(0, CHUNK_SIZE - 1); @@ -294,7 +293,7 @@ private void requestChunk(int index) { cacheHandler.readChunk(index, this); return; } - } catch (IOException ex) { + } catch (IOException | CacheManager.BadChunkHashException ex) { LOGGER.fatal("Failed requesting chunk from cache, index: {}", index, ex); } } diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java index 81205ac9..cbdee021 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java @@ -83,7 +83,7 @@ private boolean tryCacheChunk(int index) { if (!cacheHandler.hasChunk(index)) return false; cacheHandler.readChunk(index, this); return true; - } catch (IOException ex) { + } catch (IOException | CacheManager.BadChunkHashException ex) { LOGGER.fatal("Failed requesting chunk from cache, index: {}", index, ex); return false; } diff --git a/lib/src/main/java/xyz/gianlu/librespot/cache/CacheManager.java b/lib/src/main/java/xyz/gianlu/librespot/cache/CacheManager.java index 776426bd..fdf93e27 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/cache/CacheManager.java +++ b/lib/src/main/java/xyz/gianlu/librespot/cache/CacheManager.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; import xyz.gianlu.librespot.audio.GeneralWritableStream; import xyz.gianlu.librespot.audio.StreamId; +import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.core.Session; import java.io.Closeable; @@ -13,10 +14,9 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -29,7 +29,14 @@ public class CacheManager implements Closeable { private static final long CLEAN_UP_THRESHOLD = TimeUnit.DAYS.toMillis(7); private static final Logger LOGGER = LogManager.getLogger(CacheManager.class); + /** + * The header indicating when the file was last read or written to. + */ private static final int HEADER_TIMESTAMP = 254; + /** + * The header indicating the hash of the first chunk of the file. + */ + private static final int HEADER_HASH = 253; private final File parent; private final CacheJournal journal; private final Map fileHandlers = new ConcurrentHashMap<>(); @@ -127,6 +134,13 @@ public Handler getHandler(@NotNull StreamId streamId) throws IOException { return getHandler(streamId.isEpisode() ? streamId.getEpisodeGid() : streamId.getFileId()); } + public static class BadChunkHashException extends Exception { + BadChunkHashException(@NotNull String streamId, byte[] expected, byte[] actual) { + super(String.format("Failed verifying chunk hash for %s, expected: %s, actual: %s", + streamId, Utils.bytesToHex(expected), Utils.bytesToHex(actual))); + } + } + public class Handler implements Closeable { private final String streamId; private final RandomAccessFile io; @@ -173,21 +187,35 @@ public byte[] getHeader(byte id) throws IOException { return header == null ? null : header.value; } + /** + * Checks if the chunk is present in the cache, WITHOUT checking the hash. + * + * @param index The index of the chunk + * @return Whether the chunk is available + */ public boolean hasChunk(int index) throws IOException { updateTimestamp(); synchronized (io) { - if (io.length() < (index + 1) * CHUNK_SIZE) return false; + if (io.length() < (index + 1) * CHUNK_SIZE) + return false; } return journal.hasChunk(streamId, index); } - public void readChunk(int index, @NotNull GeneralWritableStream stream) throws IOException { + public void readChunk(int index, @NotNull GeneralWritableStream stream) throws IOException, BadChunkHashException { stream.writeChunk(readChunk(index), index, true); } - public byte[] readChunk(int index) throws IOException { + /** + * Reads the given chunk. + * + * @param index The index of the chunk + * @return The buffer containing the content of the chunk + * @throws BadChunkHashException If {@code index == 0} and the hash doesn't match + */ + public byte[] readChunk(int index) throws IOException, BadChunkHashException { updateTimestamp(); synchronized (io) { @@ -198,6 +226,22 @@ public byte[] readChunk(int index) throws IOException { if (read != buffer.length) throw new IOException(String.format("Couldn't read full chunk, read: %d, needed: %d", read, buffer.length)); + if (index == 0) { + JournalHeader header = journal.getHeader(streamId, HEADER_HASH); + if (header != null) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hash = digest.digest(buffer); + if (!Arrays.equals(header.value, hash)) { + journal.setChunk(streamId, index, false); + throw new BadChunkHashException(streamId, header.value, hash); + } + } catch (NoSuchAlgorithmException ex) { + LOGGER.error("Failed initializing MD5 digest.", ex); + } + } + } + return buffer; } } @@ -210,6 +254,16 @@ public void writeChunk(byte[] buffer, int index) throws IOException { try { journal.setChunk(streamId, index, true); + + if (index == 0) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hash = digest.digest(buffer); + journal.setHeader(streamId, HEADER_HASH, hash); + } catch (NoSuchAlgorithmException ex) { + LOGGER.error("Failed initializing MD5 digest.", ex); + } + } } finally { updateTimestamp(); } From 8369eb88c184bc4cd0fc65ae888205b04bee1313 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:16:26 +0100 Subject: [PATCH 13/18] Bump commons-net from 3.6 to 3.7.2 (#256) Bumps commons-net from 3.6 to 3.7.2. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- lib/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pom.xml b/lib/pom.xml index fca5278a..1f276f36 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -97,7 +97,7 @@ commons-net commons-net - 3.6 + 3.7.2 \ No newline at end of file From 5362828b345f365b3a8691c7d45265eaa9b04750 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:16:37 +0100 Subject: [PATCH 14/18] Bump annotations from 19.0.0 to 20.1.0 (#257) Bumps [annotations](https://github.com/JetBrains/java-annotations) from 19.0.0 to 20.1.0. - [Release notes](https://github.com/JetBrains/java-annotations/releases) - [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md) - [Commits](https://github.com/JetBrains/java-annotations/compare/19.0.0...20.1.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8451549..2e294386 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ org.jetbrains annotations - 19.0.0 + 20.1.0 From 1a19c48309885454379095ed9cf4f82684c1a631 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:19:33 +0100 Subject: [PATCH 15/18] Bump protobuf-java from 3.12.2 to 3.14.0 (#258) Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.12.2 to 3.14.0. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.12.2...v3.14.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2e294386..66fc3b4e 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.8 1.8 2.8.6 - 3.12.2 + 3.14.0 From d2e1c2473d6ac669a18a309fba333365c52dea6f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:21:42 +0100 Subject: [PATCH 16/18] Bump junit-jupiter from 5.6.2 to 5.7.0 (#259) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.6.2 to 5.7.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.6.2...r5.7.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 66fc3b4e..7f9d535e 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ org.junit.jupiter junit-jupiter - 5.6.2 + 5.7.0 test From ad1259fadd4e947313390b216edb2755c2938d1a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:24:40 +0100 Subject: [PATCH 17/18] Bump maven-javadoc-plugin from 3.1.1 to 3.2.0 (#260) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.1.1...maven-javadoc-plugin-3.2.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f9d535e..7f387530 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.2.0 attach-javadocs From 9098bbd7d489418239b9e788983630678834c8b0 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Tue, 17 Nov 2020 10:26:16 +0100 Subject: [PATCH 18/18] Release 1.5.2 --- CHANGELOG.md | 12 ++++++++++++ api/pom.xml | 2 +- lib/pom.xml | 2 +- player/pom.xml | 2 +- pom.xml | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06080acf..bb1d4ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.2] - 17-11-2020 +### Added +- Added `Player#waitReady()` method (d3149d3843e066986524e14369c5871c22629810) +- Added pass through endpoints for official Spotify API (#255) +- Store and check hash of first chunk of cache data (9ab9f43a91ebbce0e9a3a3c6f3c55a714c756525) + +### Fixed +- Fixed `UnsupportedOperationException` when starting playback (#251) +- Close cache files correctly (e953129ed5f0dc4e9931660bd216267557d6010a, #253) +- Fixed starting playback from API (#254) + + ## [1.5.1] - 31-07-2020 ### Fixed - Fixed issue with Zeroconf (#246) diff --git a/api/pom.xml b/api/pom.xml index 5bfe2abe..ba96dcd8 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.2-SNAPSHOT + 1.5.2 ../ diff --git a/lib/pom.xml b/lib/pom.xml index 1f276f36..9e864173 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.2-SNAPSHOT + 1.5.2 ../ diff --git a/player/pom.xml b/player/pom.xml index 87042b9a..89152175 100644 --- a/player/pom.xml +++ b/player/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java - 1.5.2-SNAPSHOT + 1.5.2 ../ diff --git a/pom.xml b/pom.xml index 7f387530..0991cf96 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ xyz.gianlu.librespot librespot-java pom - 1.5.2-SNAPSHOT + 1.5.2 librespot-java Java port of librespot, the Open Source Spotify client library