mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-18 04:30:48 +01:00
commit
4645546d86
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" : [ {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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?)
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user