diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt new file mode 100644 index 00000000..45c22b89 --- /dev/null +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetSongsByGenreTest.kt @@ -0,0 +1,82 @@ +package org.moire.ultrasonic.api.subsonic + +import org.amshove.kluent.`should equal to` +import org.amshove.kluent.`should equal` +import org.junit.Test +import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild + +/** + * Integration test for [SubsonicAPIDefinition.getSongsByGenre] call. + */ +class SubsonicApiGetSongsByGenreTest : SubsonicAPIClientTest() { + @Test + fun `Should handle error response`() { + val response = checkErrorCallParsed(mockWebServerRule) { + client.api.getSongsByGenre("Metal").execute() + } + + response.songsList `should equal` emptyList() + } + + @Test + fun `Should handle ok response`() { + mockWebServerRule.enqueueResponse("get_songs_by_genre_ok.json") + + val response = client.api.getSongsByGenre("Trance").execute() + + assertResponseSuccessful(response) + response.body().songsList.size `should equal to` 2 + with(response.body().songsList) { + this[0] `should equal` MusicDirectoryChild(id = 575, parent = 576, isDir = false, + title = "Time Machine (Vadim Zhukov Remix)", album = "668", + artist = "Tasadi", year = 2008, genre = "Trance", size = 22467672, + contentType = "audio/mpeg", suffix = "mp3", duration = 561, bitRate = 320, + path = "Tasadi/668/00 Time Machine (Vadim Zhukov Remix).mp3", + isVideo = false, playCount = 0, created = parseDate("2016-10-23T21:58:29.000Z"), + albumId = 0, artistId = 0, type = "music") + this[1] `should equal` MusicDirectoryChild(id = 621, parent = 622, isDir = false, + title = "My Heart (Vadim Zhukov Remix)", album = "668", + artist = "DJ Polyakov PPK Feat Kate Cameron", year = 2009, genre = "Trance", + size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670, + bitRate = 320, + path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov Remix).mp3", + isVideo = false, playCount = 2, created = parseDate("2016-10-23T21:58:29.000Z"), + albumId = 5, artistId = 4, type = "music") + } + } + + @Test + fun `Should pass genre in request param`() { + val genre = "Rock" + mockWebServerRule.assertRequestParam(expectedParam = "genre=$genre") { + client.api.getSongsByGenre(genre = genre).execute() + } + } + + @Test + fun `Should pass count in request param`() { + val count = 494 + + mockWebServerRule.assertRequestParam(expectedParam = "count=$count") { + client.api.getSongsByGenre("Trance", count = count).execute() + } + } + + @Test + fun `Should pass offset in request param`() { + val offset = 31 + + mockWebServerRule.assertRequestParam(expectedParam = "offset=$offset") { + client.api.getSongsByGenre("Trance", offset = offset).execute() + } + } + + @Test + fun `Should pass music folder id in request param`() { + val musicFolderId = 1010L + + mockWebServerRule.assertRequestParam(expectedParam = "musicFolderId=$musicFolderId") { + client.api.getSongsByGenre("Trance", musicFolderId = musicFolderId).execute() + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/get_songs_by_genre_ok.json b/subsonic-api/src/integrationTest/resources/get_songs_by_genre_ok.json new file mode 100644 index 00000000..2a2a8a32 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/get_songs_by_genre_ok.json @@ -0,0 +1,51 @@ +{ + "subsonic-response" : { + "status" : "ok", + "version" : "1.15.0", + "songsByGenre" : { + "song" : [ { + "id" : "575", + "parent" : "576", + "isDir" : false, + "title" : "Time Machine (Vadim Zhukov Remix)", + "album" : "668", + "artist" : "Tasadi", + "year" : 2008, + "genre" : "Trance", + "size" : 22467672, + "contentType" : "audio/mpeg", + "suffix" : "mp3", + "duration" : 561, + "bitRate" : 320, + "path" : "Tasadi/668/00 Time Machine (Vadim Zhukov Remix).mp3", + "isVideo" : false, + "playCount" : 0, + "created" : "2016-10-23T21:58:29.000Z", + "albumId" : "0", + "artistId" : "0", + "type" : "music" + }, { + "id" : "621", + "parent" : "622", + "isDir" : false, + "title" : "My Heart (Vadim Zhukov Remix)", + "album" : "668", + "artist" : "DJ Polyakov PPK Feat Kate Cameron", + "year" : 2009, + "genre" : "Trance", + "size" : 26805932, + "contentType" : "audio/mpeg", + "suffix" : "mp3", + "duration" : 670, + "bitRate" : 320, + "path" : "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov Remix).mp3", + "isVideo" : false, + "playCount" : 2, + "created" : "2016-10-23T21:58:29.000Z", + "albumId" : "5", + "artistId" : "4", + "type" : "music" + } ] + } + } +} \ No newline at end of file diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index cd407947..9a4bc92e 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -19,6 +19,9 @@ import java.math.BigInteger import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.security.SecureRandom +import java.util.concurrent.TimeUnit.MILLISECONDS + +private const val READ_TIMEOUT = 60_000L /** * Subsonic API client that provides api access. @@ -38,6 +41,7 @@ class SubsonicAPIClient(baseUrl: String, } private val okHttpClient = OkHttpClient.Builder() + .readTimeout(READ_TIMEOUT, MILLISECONDS) .addInterceptor { chain -> // Adds default request params val originalRequest = chain.request() diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt index fde41c0a..ca63cb3f 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt @@ -16,6 +16,7 @@ import org.moire.ultrasonic.api.subsonic.response.GetPlaylistResponse import org.moire.ultrasonic.api.subsonic.response.GetPlaylistsResponse import org.moire.ultrasonic.api.subsonic.response.GetPodcastsResponse import org.moire.ultrasonic.api.subsonic.response.GetRandomSongsResponse +import org.moire.ultrasonic.api.subsonic.response.GetSongsByGenreResponse import org.moire.ultrasonic.api.subsonic.response.GetStarredResponse import org.moire.ultrasonic.api.subsonic.response.GetStarredTwoResponse import org.moire.ultrasonic.api.subsonic.response.JukeboxResponse @@ -201,4 +202,11 @@ interface SubsonicAPIDefinition { @GET("getGenres.view") fun getGenres(): Call + + @GET("getSongsByGenre.view") + fun getSongsByGenre( + @Query("genre") genre: String, + @Query("count") count: Int = 10, + @Query("offset") offset: Int = 0, + @Query("musicFolderId") musicFolderId: Long? = null): Call } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetSongsByGenreResponse.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetSongsByGenreResponse.kt new file mode 100644 index 00000000..e5247cfa --- /dev/null +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/GetSongsByGenreResponse.kt @@ -0,0 +1,18 @@ +package org.moire.ultrasonic.api.subsonic.response + +import com.fasterxml.jackson.annotation.JsonProperty +import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions +import org.moire.ultrasonic.api.subsonic.SubsonicError +import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild + +class GetSongsByGenreResponse( + status: Status, + version: SubsonicAPIVersions, + error: SubsonicError?) : SubsonicResponse(status, version, error) { + @JsonProperty("songsByGenre") private val songsByGenreList = SongsByGenreWrapper() + + val songsList get() = songsByGenreList.songsList +} + +internal class SongsByGenreWrapper( + @JsonProperty("song") val songsList: List = emptyList()) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index fecd9875..33147a0c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -71,6 +71,7 @@ import org.moire.ultrasonic.api.subsonic.response.GetPlaylistResponse; import org.moire.ultrasonic.api.subsonic.response.GetPlaylistsResponse; import org.moire.ultrasonic.api.subsonic.response.GetPodcastsResponse; import org.moire.ultrasonic.api.subsonic.response.GetRandomSongsResponse; +import org.moire.ultrasonic.api.subsonic.response.GetSongsByGenreResponse; import org.moire.ultrasonic.api.subsonic.response.GetStarredResponse; import org.moire.ultrasonic.api.subsonic.response.GetStarredTwoResponse; import org.moire.ultrasonic.api.subsonic.response.JukeboxResponse; @@ -113,7 +114,6 @@ import org.moire.ultrasonic.service.parser.BookmarkParser; import org.moire.ultrasonic.service.parser.ChatMessageParser; import org.moire.ultrasonic.service.parser.ErrorParser; import org.moire.ultrasonic.service.parser.MusicDirectoryParser; -import org.moire.ultrasonic.service.parser.RandomSongsParser; import org.moire.ultrasonic.service.parser.SubsonicRESTException; import org.moire.ultrasonic.service.parser.UserInfoParser; import org.moire.ultrasonic.service.ssl.SSLSocketFactory; @@ -1207,35 +1207,26 @@ public class RESTMusicService implements MusicService return ApiGenreConverter.toDomainEntityList(response.body().getGenresList()); } - @Override - public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception - { - checkServerVersion(context, "1.9", "Genres not supported."); + @Override + public MusicDirectory getSongsByGenre(String genre, + int count, + int offset, + Context context, + ProgressListener progressListener) throws Exception { + if (genre == null) { + throw new IllegalArgumentException("Genre is null"); + } - HttpParams params = new BasicHttpParams(); - HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); + updateProgressListener(progressListener, R.string.parser_reading); + Response response = subsonicAPIClient.getApi() + .getSongsByGenre(genre, count, offset, null) + .execute(); + checkResponseSuccessful(response); - List parameterNames = new ArrayList(); - List parameterValues = new ArrayList(); - - parameterNames.add("genre"); - parameterValues.add(genre); - parameterNames.add("count"); - parameterValues.add(count); - parameterNames.add("offset"); - parameterValues.add(offset); - - Reader reader = getReader(context, progressListener, "getSongsByGenre", params, parameterNames, parameterValues); - - try - { - return new RandomSongsParser(context).parse(reader, progressListener); - } - finally - { - Util.close(reader); - } - } + MusicDirectory result = new MusicDirectory(); + result.addAll(APIMusicDirectoryConverter.toDomainEntityList(response.body().getSongsList())); + return result; + } @Override public UserInfo getUser(String username, Context context, ProgressListener progressListener) throws Exception diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/RandomSongsParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/RandomSongsParser.java deleted file mode 100644 index ddfb4955..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/RandomSongsParser.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.service.parser; - -import android.content.Context; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.util.ProgressListener; - -import org.xmlpull.v1.XmlPullParser; - -import java.io.Reader; - -/** - * @author Sindre Mehus - */ -public class RandomSongsParser extends MusicDirectoryEntryParser -{ - - public RandomSongsParser(Context context) - { - super(context); - } - - public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception - { - updateProgress(progressListener, R.string.parser_reading); - init(reader); - - MusicDirectory dir = new MusicDirectory(); - int eventType; - do - { - eventType = nextParseEvent(); - if (eventType == XmlPullParser.START_TAG) - { - String name = getElementName(); - if ("song".equals(name)) - { - dir.addChild(parseEntry("", false, 0)); - } - else if ("error".equals(name)) - { - handleError(); - } - } - } while (eventType != XmlPullParser.END_DOCUMENT); - - validate(); - updateProgress(progressListener, R.string.parser_reading_done); - - return dir; - } - -} \ No newline at end of file