mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-25 21:22:16 +03:00
Merge pull request #49 from ultrasonic/add-get-album-list
Add get album list
This commit is contained in:
commit
0a22f7bcc7
@ -0,0 +1,109 @@
|
|||||||
|
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.AlbumListType
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for [SubsonicAPIDefinition] for getAlbumList call.
|
||||||
|
*/
|
||||||
|
class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
|
||||||
|
@Test
|
||||||
|
fun `Should handle error response`() {
|
||||||
|
val response = checkErrorCallParsed(mockWebServerRule) {
|
||||||
|
client.api.getAlbumList(BY_GENRE).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
response.albumList `should equal` emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should handle ok response`() {
|
||||||
|
mockWebServerRule.enqueueResponse("get_album_list_ok.json")
|
||||||
|
|
||||||
|
val response = client.api.getAlbumList(BY_GENRE).execute()
|
||||||
|
|
||||||
|
assertResponseSuccessful(response)
|
||||||
|
with(response.body().albumList) {
|
||||||
|
size `should equal to` 2
|
||||||
|
this[1] `should equal` MusicDirectoryChild(id = 9997, parent = 9996, isDir = true,
|
||||||
|
title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful",
|
||||||
|
artist = "Nightwish", year = 2015, genre = "Symphonic Metal",
|
||||||
|
coverArt = "9997", playCount = 11,
|
||||||
|
created = parseDate("2017-09-02T16:22:49.000Z"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass type in request params`() {
|
||||||
|
val listType = AlbumListType.HIGHEST
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "type=${listType.typeName}") {
|
||||||
|
client.api.getAlbumList(type = listType).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass size in request params`() {
|
||||||
|
val size = 45
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "size=$size") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, size = size).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass offset in request params`() {
|
||||||
|
val offset = 3
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "offset=$offset") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, offset = offset).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass from year in request params`() {
|
||||||
|
val fromYear = 2001
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "fromYear=$fromYear") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, fromYear = fromYear).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass to year in request params`() {
|
||||||
|
val toYear = 2017
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "toYear=$toYear") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, toYear = toYear).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass genre in request params`() {
|
||||||
|
val genre = "Rock"
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "genre=$genre") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, genre = genre).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should pass music folder id in request params`() {
|
||||||
|
val folderId = 545L
|
||||||
|
|
||||||
|
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
|
||||||
|
expectedParam = "musicFolderId=$folderId") {
|
||||||
|
client.api.getAlbumList(type = BY_GENRE, musicFolderId = folderId).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"subsonic-response" : {
|
||||||
|
"status" : "ok",
|
||||||
|
"version" : "1.15.0",
|
||||||
|
"albumList" : {
|
||||||
|
"album" : [ {
|
||||||
|
"id" : "10020",
|
||||||
|
"parent" : "490",
|
||||||
|
"isDir" : true,
|
||||||
|
"title" : "Fury",
|
||||||
|
"album" : "Fury",
|
||||||
|
"artist" : "Sick Puppies",
|
||||||
|
"year" : 2016,
|
||||||
|
"genre" : "Alternative Rock",
|
||||||
|
"coverArt" : "10020",
|
||||||
|
"playCount" : 13,
|
||||||
|
"created" : "2017-09-02T17:34:51.000Z"
|
||||||
|
}, {
|
||||||
|
"id" : "9997",
|
||||||
|
"parent" : "9996",
|
||||||
|
"isDir" : true,
|
||||||
|
"title" : "Endless Forms Most Beautiful",
|
||||||
|
"album" : "Endless Forms Most Beautiful",
|
||||||
|
"artist" : "Nightwish",
|
||||||
|
"year" : 2015,
|
||||||
|
"genre" : "Symphonic Metal",
|
||||||
|
"coverArt" : "9997",
|
||||||
|
"playCount" : 11,
|
||||||
|
"created" : "2017-09-02T16:22:49.000Z"
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ class SubsonicAPIClient(baseUrl: String,
|
|||||||
|
|
||||||
private val jacksonMapper = ObjectMapper()
|
private val jacksonMapper = ObjectMapper()
|
||||||
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
.registerModule(KotlinModule())
|
.registerModule(KotlinModule())
|
||||||
|
|
||||||
private val retrofit = Retrofit.Builder()
|
private val retrofit = Retrofit.Builder()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
|
||||||
|
import org.moire.ultrasonic.api.subsonic.response.GetAlbumListResponse
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse
|
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse
|
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse
|
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse
|
||||||
@ -123,4 +125,13 @@ interface SubsonicAPIDefinition {
|
|||||||
fun scrobble(@Query("id") id: String,
|
fun scrobble(@Query("id") id: String,
|
||||||
@Query("time") time: Long? = null,
|
@Query("time") time: Long? = null,
|
||||||
@Query("submission") submission: Boolean? = null): Call<SubsonicResponse>
|
@Query("submission") submission: Boolean? = null): Call<SubsonicResponse>
|
||||||
|
|
||||||
|
@GET("getAlbumList.view")
|
||||||
|
fun getAlbumList(@Query("type") type: AlbumListType,
|
||||||
|
@Query("size") size: Int? = null,
|
||||||
|
@Query("offset") offset: Int? = null,
|
||||||
|
@Query("fromYear") fromYear: Int? = null,
|
||||||
|
@Query("toYear") toYear: Int? = null,
|
||||||
|
@Query("genre") genre: String? = null,
|
||||||
|
@Query("musicFolderId") musicFolderId: Long? = null): Call<GetAlbumListResponse>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package org.moire.ultrasonic.api.subsonic.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of album list used in [org.moire.ultrasonic.api.subsonic.SubsonicAPIDefinition.getAlbumList]
|
||||||
|
* calls.
|
||||||
|
*
|
||||||
|
* @author Yahor Berdnikau
|
||||||
|
*/
|
||||||
|
enum class AlbumListType(val typeName: String) {
|
||||||
|
RANDOM("random"),
|
||||||
|
NEWEST("newest"),
|
||||||
|
HIGHEST("highest"),
|
||||||
|
FREQUENT("frequent"),
|
||||||
|
RECENT("recent"),
|
||||||
|
SORTED_BY_NAME("alphabeticalByName"),
|
||||||
|
SORTED_BY_ARTIST("alphabeticalByArtist"),
|
||||||
|
STARRED("starred"),
|
||||||
|
BY_YEAR("byYear"),
|
||||||
|
BY_GENRE("byGenre");
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return typeName
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun fromName(typeName: String): AlbumListType = when (typeName) {
|
||||||
|
in RANDOM.typeName -> RANDOM
|
||||||
|
in NEWEST.typeName -> NEWEST
|
||||||
|
in HIGHEST.typeName -> HIGHEST
|
||||||
|
in FREQUENT.typeName -> FREQUENT
|
||||||
|
in RECENT.typeName -> RECENT
|
||||||
|
in SORTED_BY_NAME.typeName -> SORTED_BY_NAME
|
||||||
|
in SORTED_BY_ARTIST.typeName -> SORTED_BY_ARTIST
|
||||||
|
in STARRED.typeName -> STARRED
|
||||||
|
in BY_YEAR.typeName -> BY_YEAR
|
||||||
|
in BY_GENRE.typeName -> BY_GENRE
|
||||||
|
else -> throw IllegalArgumentException("Unknown type: $typeName")
|
||||||
|
}
|
||||||
|
|
||||||
|
private operator fun String.contains(other: String) = this.equals(other, true)
|
||||||
|
}
|
||||||
|
}
|
@ -32,4 +32,6 @@ data class MusicDirectoryChild(val id: Long = -1L,
|
|||||||
val channelId: Long = -1,
|
val channelId: Long = -1,
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val status: String = "",
|
val status: String = "",
|
||||||
val publishDate: Calendar? = null)
|
val publishDate: Calendar? = null,
|
||||||
|
val userRating: Int? = null,
|
||||||
|
val averageRating: Float? = null)
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
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 GetAlbumListResponse(status: Status,
|
||||||
|
version: SubsonicAPIVersions,
|
||||||
|
error: SubsonicError?)
|
||||||
|
: SubsonicResponse(status, version, error) {
|
||||||
|
@JsonProperty("albumList") private val albumWrapper = AlbumWrapper()
|
||||||
|
|
||||||
|
val albumList: List<MusicDirectoryChild>
|
||||||
|
get() = albumWrapper.albumList
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AlbumWrapper(
|
||||||
|
@JsonProperty("album") val albumList: List<MusicDirectoryChild> = emptyList())
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.moire.ultrasonic.api.subsonic.models
|
||||||
|
|
||||||
|
import org.amshove.kluent.`should equal to`
|
||||||
|
import org.amshove.kluent.`should equal`
|
||||||
|
import org.amshove.kluent.`should throw`
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for [AlbumListType] class.
|
||||||
|
*/
|
||||||
|
class AlbumListTypeTest {
|
||||||
|
@Test
|
||||||
|
fun `Should create type from string ignoring case`() {
|
||||||
|
val type = AlbumListType.SORTED_BY_NAME.typeName.toLowerCase()
|
||||||
|
|
||||||
|
val albumListType = AlbumListType.fromName(type)
|
||||||
|
|
||||||
|
albumListType `should equal` AlbumListType.SORTED_BY_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should throw IllegalArgumentException for unknown type`() {
|
||||||
|
val failCall = {
|
||||||
|
AlbumListType.fromName("some-not-existing-type")
|
||||||
|
}
|
||||||
|
|
||||||
|
failCall `should throw` IllegalArgumentException::class
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should convert type string to corresponding AlbumListType`() {
|
||||||
|
AlbumListType.values().forEach {
|
||||||
|
AlbumListType.fromName(it.typeName) `should equal` it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should return type name for toString call`() {
|
||||||
|
AlbumListType.STARRED.typeName `should equal to` AlbumListType.STARRED.toString()
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,9 @@ import org.apache.http.protocol.ExecutionContext;
|
|||||||
import org.apache.http.protocol.HttpContext;
|
import org.apache.http.protocol.HttpContext;
|
||||||
import org.moire.ultrasonic.R;
|
import org.moire.ultrasonic.R;
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.AlbumListType;
|
||||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild;
|
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild;
|
||||||
|
import org.moire.ultrasonic.api.subsonic.response.GetAlbumListResponse;
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse;
|
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse;
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse;
|
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse;
|
||||||
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse;
|
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse;
|
||||||
@ -654,19 +656,26 @@ public class RESTMusicService implements MusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception
|
public MusicDirectory getAlbumList(String type,
|
||||||
{
|
int size,
|
||||||
checkServerVersion(context, "1.2", "Album list not supported.");
|
int offset,
|
||||||
|
Context context,
|
||||||
|
ProgressListener progressListener) throws Exception {
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException("Type is null!");
|
||||||
|
}
|
||||||
|
|
||||||
Reader reader = getReader(context, progressListener, "getAlbumList", null, asList("type", "size", "offset"), Arrays.<Object>asList(type, size, offset));
|
updateProgressListener(progressListener, R.string.parser_reading);
|
||||||
try
|
Response<GetAlbumListResponse> response = subsonicAPIClient.getApi()
|
||||||
{
|
.getAlbumList(AlbumListType.fromName(type), size, offset, null,
|
||||||
return new AlbumListParser(context).parse(reader, progressListener, false);
|
null, null, null).execute();
|
||||||
}
|
checkResponseSuccessful(response);
|
||||||
finally
|
|
||||||
{
|
List<MusicDirectory.Entry> childList = APIMusicDirectoryConverter
|
||||||
Util.close(reader);
|
.toDomainEntityList(response.body().getAlbumList());
|
||||||
}
|
MusicDirectory result = new MusicDirectory();
|
||||||
|
result.addAll(childList);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,6 +48,8 @@ fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun List<MusicDirectoryChild>.toDomainEntityList() = this.map { it.toDomainEntity() }
|
||||||
|
|
||||||
fun APIMusicDirectory.toDomainEntity(): MusicDirectory = MusicDirectory().apply {
|
fun APIMusicDirectory.toDomainEntity(): MusicDirectory = MusicDirectory().apply {
|
||||||
name = this@toDomainEntity.name
|
name = this@toDomainEntity.name
|
||||||
addAll(this@toDomainEntity.childList.map { it.toDomainEntity() })
|
addAll(this@toDomainEntity.childList.map { it.toDomainEntity() })
|
||||||
|
@ -71,7 +71,7 @@ class APIMusicDirectoryConverterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Should convert MusicDirectoryChild podact entity`() {
|
fun `Should convert MusicDirectoryChild podcast entity`() {
|
||||||
val entity = MusicDirectoryChild(id = 584, streamId = 394,
|
val entity = MusicDirectoryChild(id = 584, streamId = 394,
|
||||||
artist = "some-artist", publishDate = Calendar.getInstance())
|
artist = "some-artist", publishDate = Calendar.getInstance())
|
||||||
|
|
||||||
@ -82,4 +82,16 @@ class APIMusicDirectoryConverterTest {
|
|||||||
artist `should equal to` dateFormat.format(entity.publishDate?.time)
|
artist `should equal to` dateFormat.format(entity.publishDate?.time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Should convert list of MusicDirectoryChild to domain entity list`() {
|
||||||
|
val entitiesList = listOf(MusicDirectoryChild(id = 45), MusicDirectoryChild(id = 34))
|
||||||
|
|
||||||
|
val domainList = entitiesList.toDomainEntityList()
|
||||||
|
|
||||||
|
domainList.size `should equal to` entitiesList.size
|
||||||
|
domainList.forEachIndexed { index, entry ->
|
||||||
|
entry `should equal` entitiesList[index].toDomainEntity()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user