1
0
mirror of https://github.com/ultrasonic/ultrasonic synced 2025-02-18 04:30:48 +01:00

Merge pull request #23 from ultrasonic/use-get-indexes

Use get indexes
This commit is contained in:
Yahor Berdnikau 2017-08-12 21:33:05 +02:00 committed by GitHub
commit 4645546d86
14 changed files with 184 additions and 99 deletions

View File

@ -14,10 +14,10 @@ ext.versions = [
androidSupport : "22.2.1",
kotlin : "1.1.2-5",
kotlin : "1.1.3-2",
retrofit : "2.1.0",
jackson : "2.8.7",
jackson : "2.9.0",
okhttp : "3.6.0",
junit : "4.12",
@ -41,6 +41,7 @@ ext.androidSupport = [
ext.other = [
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin",
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit",
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
@ -51,7 +52,6 @@ ext.other = [
ext.testing = [
junit : "junit:junit:$versions.junit",
kotlinJunit : "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin",
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
mockitoKotlin : "com.nhaarman:mockito-kotlin:$versions.mockitoKotlin",
kluent : "org.amshove.kluent:kluent:$versions.kluent",
mockWebServer : "com.squareup.okhttp3:mockwebserver:$versions.okhttp",

View File

@ -13,12 +13,14 @@ dependencies {
compile other.kotlinStdlib
compile other.retrofit
compile other.jacksonConverter
compile other.jacksonKotlin
compile(other.jacksonKotlin) {
exclude module: 'kotlin-reflect'
}
compile other.kotlinReflect // for jackson kotlin, but to use the same version
compile other.okhttpLogging
testCompile testing.junit
testCompile testing.kotlinJunit
testCompile testing.kotlinReflect
testCompile testing.mockitoKotlin
testCompile testing.kluent
testCompile testing.mockWebServer

View File

@ -30,7 +30,7 @@ import java.util.TimeZone
/**
* Integration test for [SubsonicAPIClient] class.
*/
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
class SubsonicAPIClientTest {
companion object {
const val USERNAME = "some-user"
@ -154,17 +154,19 @@ class SubsonicAPIClientTest {
@Test
fun `Should parse get indexes ok response`() {
// check for shortcut parsing
enqueueResponse("get_indexes_ok.json")
val response = client.api.getIndexes(null, null).execute()
assertResponseSuccessful(response)
response.body().indexes `should not be` null
with(response.body().indexes!!) {
with(response.body().indexes) {
lastModified `should equal` 1491069027523
ignoredArticles `should equal` "The El La Los Las Le Les"
shortcuts `should be` null
shortcutList `should equal` listOf(
Artist(889L, "podcasts", null),
Artist(890L, "audiobooks", null)
)
indexList `should equal` mutableListOf(
Index("A", listOf(
Artist(50L, "Ace Of Base", parseDate("2017-04-02T20:16:29.815Z")),
@ -220,7 +222,13 @@ class SubsonicAPIClientTest {
fun `Should parse get indexes error response`() {
val response = checkErrorCallParsed { client.api.getIndexes(null, null).execute() }
response.indexes `should be` null
response.indexes `should not be` null
with(response.indexes) {
lastModified `should equal to` 0
ignoredArticles `should equal to` ""
indexList.size `should equal to` 0
shortcutList.size `should equal to` 0
}
}
@Test

View File

@ -5,6 +5,14 @@
"indexes" : {
"lastModified" : 1491069027523,
"ignoredArticles" : "The El La Los Las Le Les",
"shortcut" : [ {
"id" : "889",
"name" : "podcasts"
},
{
"id" : "890",
"name" : "audiobooks"
} ],
"index" : [ {
"name" : "A",
"artist" : [ {

View File

@ -98,7 +98,7 @@ class SubsonicAPIClient(baseUrl: String,
private fun OkHttpClient.Builder.addLogging() {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
this.addInterceptor(loggingInterceptor)
}

View File

@ -2,6 +2,6 @@ package org.moire.ultrasonic.api.subsonic.models
import java.util.Calendar
data class Artist(val id: Long,
val name: String,
data class Artist(val id: Long = -1,
val name: String = "",
val starred: Calendar?)

View File

@ -2,6 +2,6 @@ package org.moire.ultrasonic.api.subsonic.models
import com.fasterxml.jackson.annotation.JsonProperty
data class Index(val name: String,
data class Index(val name: String = "",
@JsonProperty("artist")
val artists: List<Artist>)
val artists: List<Artist> = emptyList())

View File

@ -2,8 +2,9 @@ package org.moire.ultrasonic.api.subsonic.models
import com.fasterxml.jackson.annotation.JsonProperty
data class Indexes(val lastModified: Long,
val ignoredArticles: String?,
data class Indexes(val lastModified: Long = 0,
val ignoredArticles: String = "",
@JsonProperty("index")
val indexList: List<Index>,
val shortcuts: List<Index>?)
val indexList: List<Index> = emptyList(),
@JsonProperty("shortcut")
val shortcutList: List<Artist> = emptyList())

View File

@ -7,5 +7,5 @@ import org.moire.ultrasonic.api.subsonic.models.Indexes
class GetIndexesResponse(status: Status,
version: SubsonicAPIVersions,
error: SubsonicError?,
val indexes: Indexes?) :
val indexes: Indexes = Indexes()) :
SubsonicResponse(status, version, error)

View File

@ -53,17 +53,9 @@ dependencies {
compile other.kotlinStdlib
testCompile testing.junit
testCompile(testing.kotlinJunit) {
exclude module: "kotlin-stdlib"
}
testCompile(testing.mockitoKotlin) {
exclude module: "kotlin-stdlib"
exclude module: "kotlin-reflect"
}
testCompile(testing.kluent) {
exclude module: "kotlin-stdlib"
exclude module: "kotlin-reflect"
}
testCompile testing.kotlinJunit
testCompile testing.mockitoKotlin
testCompile testing.kluent
}
// Excluding all non-kotlin classes

View File

@ -102,4 +102,31 @@ public class Artist implements Serializable
{
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Artist artist = (Artist) o;
if (closeness != artist.closeness) return false;
if (id != null ? !id.equals(artist.id) : artist.id != null) return false;
if (name != null ? !name.equals(artist.name) : artist.name != null) return false;
if (index != null ? !index.equals(artist.index) : artist.index != null) return false;
if (coverArt != null ? !coverArt.equals(artist.coverArt) : artist.coverArt != null)
return false;
return albumCount != null ? albumCount.equals(artist.albumCount) : artist.albumCount == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (index != null ? index.hashCode() : 0);
result = 31 * result + (coverArt != null ? coverArt.hashCode() : 0);
result = 31 * result + (albumCount != null ? albumCount.hashCode() : 0);
result = 31 * result + closeness;
return result;
}
}

View File

@ -56,6 +56,7 @@ import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
import org.moire.ultrasonic.api.subsonic.response.GetIndexesResponse;
import org.moire.ultrasonic.api.subsonic.response.LicenseResponse;
import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse;
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse;
@ -117,6 +118,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import retrofit2.Response;
@ -227,66 +229,45 @@ public class RESTMusicService implements MusicService
Response<MusicFoldersResponse> response = subsonicAPIClient.getApi().getMusicFolders().execute();
checkResponseSuccessful(response);
List<MusicFolder> musicFolders = APIConverter
.convertMusicFolderList(response.body().getMusicFolders());
List<MusicFolder> musicFolders = APIConverter.toDomainEntityList(response.body().getMusicFolders());
writeCachedMusicFolders(context, musicFolders);
return musicFolders;
}
@Override
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception
{
Indexes cachedIndexes = readCachedIndexes(context, musicFolderId);
if (cachedIndexes != null && !refresh)
{
return cachedIndexes;
}
@Override
public Indexes getIndexes(String musicFolderId,
boolean refresh,
Context context,
ProgressListener progressListener) throws Exception {
Indexes cachedIndexes = readCachedIndexes(context, musicFolderId);
if (cachedIndexes != null && !refresh) {
return cachedIndexes;
}
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
updateProgressListener(progressListener, R.string.parser_reading);
Response<GetIndexesResponse> response = subsonicAPIClient.getApi()
.getIndexes(musicFolderId == null ? null : Long.valueOf(musicFolderId), null).execute();
checkResponseSuccessful(response);
if (musicFolderId != null)
{
parameterNames.add("musicFolderId");
parameterValues.add(musicFolderId);
}
Indexes indexes = APIConverter.toDomainEntity(response.body().getIndexes());
writeCachedIndexes(context, indexes, musicFolderId);
return indexes;
}
Reader reader = getReader(context, progressListener, "getIndexes", null, parameterNames, parameterValues);
private static Indexes readCachedIndexes(Context context, String musicFolderId) {
String filename = getCachedIndexesFilename(context, musicFolderId);
return FileUtil.deserialize(context, filename);
}
try
{
Indexes indexes = new IndexesParser(context).parse(reader, progressListener);
if (indexes != null)
{
writeCachedIndexes(context, indexes, musicFolderId);
return indexes;
}
private static void writeCachedIndexes(Context context, Indexes indexes, String musicFolderId) {
String filename = getCachedIndexesFilename(context, musicFolderId);
FileUtil.serialize(context, indexes, filename);
}
return cachedIndexes != null ? cachedIndexes : new Indexes(0, null, new ArrayList<org.moire.ultrasonic.domain.Artist>(), new ArrayList<org.moire.ultrasonic.domain.Artist>());
}
finally
{
Util.close(reader);
}
}
private static Indexes readCachedIndexes(Context context, String musicFolderId)
{
String filename = getCachedIndexesFilename(context, musicFolderId);
return FileUtil.deserialize(context, filename);
}
private static void writeCachedIndexes(Context context, Indexes indexes, String musicFolderId)
{
String filename = getCachedIndexesFilename(context, musicFolderId);
FileUtil.serialize(context, indexes, filename);
}
private static String getCachedIndexesFilename(Context context, String musicFolderId)
{
String s = Util.getRestUrl(context, null) + musicFolderId;
return String.format("indexes-%d.ser", Math.abs(s.hashCode()));
}
private static String getCachedIndexesFilename(Context context, String musicFolderId) {
String s = Util.getRestUrl(context, null) + musicFolderId;
return String.format(Locale.US, "indexes-%d.ser", Math.abs(s.hashCode()));
}
@Override
public Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) throws Exception

View File

@ -1,15 +1,28 @@
// Converts entities from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] to app entities.
// Converts entities from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient] to app domain entities.
@file:JvmName("APIConverter")
package org.moire.ultrasonic.data
import org.moire.ultrasonic.api.subsonic.models.Index
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Indexes
import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.api.subsonic.models.Artist as APIArtist
import org.moire.ultrasonic.api.subsonic.models.Indexes as APIIndexes
import org.moire.ultrasonic.api.subsonic.models.MusicFolder as APIMusicFolder
typealias APIMusicFolder = org.moire.ultrasonic.api.subsonic.models.MusicFolder
fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id.toString(), this.name)
fun convertMusicFolder(entity: APIMusicFolder): MusicFolder {
return MusicFolder(entity.id.toString(), entity.name)
}
fun convertMusicFolderList(entitiesList: List<APIMusicFolder>): List<MusicFolder> {
return entitiesList.map { convertMusicFolder(it) }
fun List<APIMusicFolder>.toDomainEntityList(): List<MusicFolder>
= this.map { it.toDomainEntity() }
fun APIIndexes.toDomainEntity(): Indexes = Indexes(this.lastModified, this.ignoredArticles,
this.shortcutList.map { it.toDomainEntity() }, this.indexList.foldIndexToArtistList())
private fun List<Index>.foldIndexToArtistList(): List<Artist> = this.fold(listOf(), {
acc, index -> acc + index.artists.map { it.toDomainEntity() }
})
fun APIArtist.toDomainEntity(): Artist = Artist().apply {
id = this@toDomainEntity.id.toString()
name = this@toDomainEntity.name
}

View File

@ -3,8 +3,13 @@
package org.moire.ultrasonic.data
import org.amshove.kluent.`should equal to`
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.models.Artist
import org.moire.ultrasonic.api.subsonic.models.Index
import org.moire.ultrasonic.api.subsonic.models.Indexes
import org.moire.ultrasonic.api.subsonic.models.MusicFolder
import java.util.Calendar
/**
* Unit test for functions in SubsonicAPIConverter file.
@ -16,10 +21,10 @@ class APIConverterTest {
fun `Should convert MusicFolder entity`() {
val entity = createMusicFolder(10, "some-name")
val convertedEntity = convertMusicFolder(entity)
val convertedEntity = entity.toDomainEntity()
convertedEntity.name `should equal to` "some-name"
convertedEntity.id `should equal to` 10.toString()
convertedEntity.name `should equal to` entity.name
convertedEntity.id `should equal to` entity.id.toString()
}
@Test
@ -29,15 +34,63 @@ class APIConverterTest {
createMusicFolder(4, "some-name-4")
)
val convertedList = convertMusicFolderList(entityList)
val convertedList = entityList.toDomainEntityList()
convertedList.size `should equal to` 2
convertedList[0].id `should equal to` 3.toString()
convertedList[0].name `should equal to` "some-name-3"
convertedList[1].id `should equal to` 4.toString()
convertedList[1].name `should equal to` "some-name-4"
with(convertedList) {
size `should equal to` entityList.size
this[0].id `should equal to` entityList[0].id.toString()
this[0].name `should equal to` entityList[0].name
this[1].id `should equal to` entityList[1].id.toString()
this[1].name `should equal to` entityList[1].name
}
}
@Test
fun `Should convert artist entity`() {
val entity = createArtist(10, "artist-name", Calendar.getInstance())
val convertedEntity = entity.toDomainEntity()
with(convertedEntity) {
id `should equal to` entity.id.toString()
name `should equal to` entity.name
}
}
@Test
fun `Should convert Indexes entity`() {
val artistsA = listOf(createArtist(4, "AC/DC"), createArtist(45, "ABBA"))
val artistsT = listOf(createArtist(10, "Taproot"), createArtist(12, "Teebee"))
val entity = createIndexes(154, "Le Tre Ze", listOf(
createIndex("A", artistsA),
createIndex("T", artistsT)
), artistsA)
val convertedEntity = entity.toDomainEntity()
val expectedArtists = (artistsA + artistsT).map { it.toDomainEntity() }.toMutableList()
with(convertedEntity) {
lastModified `should equal to` entity.lastModified
ignoredArticles `should equal to` entity.ignoredArticles
artists.size `should equal to` expectedArtists.size
artists `should equal` expectedArtists
shortcuts `should equal` artistsA.map { it.toDomainEntity() }.toMutableList()
}
}
private fun createMusicFolder(id: Long = 0, name: String = ""): MusicFolder =
MusicFolder(id, name)
private fun createArtist(id: Long = -1, name: String = "", starred: Calendar? = null): Artist
= Artist(id, name, starred)
private fun createIndex(name: String = "", artistList: List<Artist> = emptyList()): Index
= Index(name, artistList)
private fun createIndexes(
lastModified: Long = 0,
ignoredArticles: String,
indexList: List<Index> = emptyList(),
shortcuts: List<Artist> = emptyList()): Indexes
= Indexes(lastModified, ignoredArticles, indexList, shortcuts)
}