1
0
mirror of https://github.com/ultrasonic/ultrasonic synced 2025-03-06 12:27:41 +01:00

Merge pull request #34 from ultrasonic/add-old-search

Add old search
This commit is contained in:
Yahor Berdnikau 2017-08-24 22:14:15 +02:00 committed by GitHub
commit 1d35d0c23c
10 changed files with 244 additions and 110 deletions

View File

@ -49,7 +49,7 @@ complexity:
ComplexMethod: ComplexMethod:
threshold: 10 threshold: 10
TooManyFunctions: TooManyFunctions:
threshold: 10 threshold: 20
ComplexCondition: ComplexCondition:
threshold: 3 threshold: 3
LabeledExpression: LabeledExpression:

View File

@ -0,0 +1,125 @@
package org.moire.ultrasonic.api.subsonic
import org.amshove.kluent.`should contain`
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
import java.util.Calendar
/**
* Integration test for [SubsonicAPIClient] for search call.
*/
class SubsonicApiSearchTest : SubsonicAPIClientTest() {
@Test
fun `Should parse error response`() {
checkErrorCallParsed(mockWebServerRule, {
client.api.search().execute()
})
}
@Test
fun `Should parse ok response`() {
enqueueOkResponse()
val response = client.api.search().execute()
assertResponseSuccessful(response)
with(response.body().searchResult) {
offset `should equal to` 10
totalHits `should equal to` 53
matchList.size `should equal to` 1
matchList[0] `should equal` MusicDirectoryChild(id = 5831L, parent = 5766L,
isDir = false, title = "You'll Be Under My Wheels",
album = "Need for Speed Most Wanted", artist = "The Prodigy",
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
size = 5607024, contentType = "audio/mpeg", suffix = "mp3", duration = 233,
bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = 568,
artistId = 505, type = "music")
}
}
@Test
fun `Should pass artist param`() {
enqueueOkResponse()
val artist = "some-artist"
client.api.search(artist = artist).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "artist=$artist"
}
@Test
fun `Should pass album param`() {
enqueueOkResponse()
val album = "some-album"
client.api.search(album = album).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "album=$album"
}
@Test
fun `Should pass title param`() {
enqueueOkResponse()
val title = "some-title"
client.api.search(title = title).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "title=$title"
}
@Test
fun `Should contain any param`() {
enqueueOkResponse()
val any = "AnyString"
client.api.search(any = any).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "any=$any"
}
@Test
fun `Should contain count param`() {
enqueueOkResponse()
val count = 11
client.api.search(count = count).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "count=$count"
}
@Test
fun `Should contain offset param`() {
enqueueOkResponse()
val offset = 54
client.api.search(offset = offset).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "offset=$offset"
}
@Test
fun `Should contain newerThan param`() {
enqueueOkResponse()
val newerThan = Calendar.getInstance()
client.api.search(newerThan = newerThan.time.time).execute()
val request = mockWebServerRule.mockWebServer.takeRequest()
request.requestLine `should contain` "newerThan=${newerThan.time.time}"
}
private fun enqueueOkResponse() {
mockWebServerRule.enqueueResponse("search_ok.json")
}
}

View File

@ -0,0 +1,35 @@
{
"subsonic-response" : {
"status" : "ok",
"version" : "1.15.0",
"searchResult" : {
"offset" : 10,
"totalHits" : 53,
"match" : [ {
"id" : "5831",
"parent" : "5766",
"isDir" : false,
"title" : "You'll Be Under My Wheels",
"album" : "Need for Speed Most Wanted",
"artist" : "The Prodigy",
"track" : 17,
"year" : 2005,
"genre" : "Rap",
"coverArt" : "5766",
"size" : 5607024,
"contentType" : "audio/mpeg",
"suffix" : "mp3",
"duration" : 233,
"bitRate" : 192,
"path" : "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels.mp3",
"isVideo" : false,
"playCount" : 0,
"discNumber" : 1,
"created" : "2016-10-23T20:09:02.000Z",
"albumId" : "568",
"artistId" : "505",
"type" : "music"
} ]
}
}
}

View File

@ -7,6 +7,7 @@ import org.moire.ultrasonic.api.subsonic.response.GetIndexesResponse
import org.moire.ultrasonic.api.subsonic.response.GetMusicDirectoryResponse import org.moire.ultrasonic.api.subsonic.response.GetMusicDirectoryResponse
import org.moire.ultrasonic.api.subsonic.response.LicenseResponse import org.moire.ultrasonic.api.subsonic.response.LicenseResponse
import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse
import org.moire.ultrasonic.api.subsonic.response.SearchResponse
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
@ -17,6 +18,7 @@ import retrofit2.http.Query
* *
* For methods description see [http://www.subsonic.org/pages/api.jsp]. * For methods description see [http://www.subsonic.org/pages/api.jsp].
*/ */
@Suppress("TooManyFunctions", "LongParameterList")
interface SubsonicAPIDefinition { interface SubsonicAPIDefinition {
@GET("ping.view") @GET("ping.view")
fun ping(): Call<SubsonicResponse> fun ping(): Call<SubsonicResponse>
@ -52,4 +54,13 @@ interface SubsonicAPIDefinition {
@GET("getAlbum.view") @GET("getAlbum.view")
fun getAlbum(@Query("id") id: Long): Call<GetAlbumResponse> fun getAlbum(@Query("id") id: Long): Call<GetAlbumResponse>
@GET("search.view")
fun search(@Query("artist") artist: String? = null,
@Query("album") album: String? = null,
@Query("title") title: String? = null,
@Query("any") any: String? = null,
@Query("count") count: Int? = null,
@Query("offset") offset: Int? = null,
@Query("newerThan") newerThan: Long? = null): Call<SearchResponse>
} }

View File

@ -0,0 +1,7 @@
package org.moire.ultrasonic.api.subsonic.models
import com.fasterxml.jackson.annotation.JsonProperty
data class SearchResult(val offset: Int = 0,
val totalHits: Int = 0,
@JsonProperty("match") val matchList: List<MusicDirectoryChild> = emptyList())

View File

@ -0,0 +1,11 @@
package org.moire.ultrasonic.api.subsonic.response
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.SubsonicError
import org.moire.ultrasonic.api.subsonic.models.SearchResult
class SearchResponse(status: Status,
version: SubsonicAPIVersions,
error: SubsonicError?,
val searchResult: SearchResult = SearchResult())
: SubsonicResponse(status, version, error)

View File

@ -63,6 +63,7 @@ import org.moire.ultrasonic.api.subsonic.response.GetIndexesResponse;
import org.moire.ultrasonic.api.subsonic.response.GetMusicDirectoryResponse; import org.moire.ultrasonic.api.subsonic.response.GetMusicDirectoryResponse;
import org.moire.ultrasonic.api.subsonic.response.LicenseResponse; import org.moire.ultrasonic.api.subsonic.response.LicenseResponse;
import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse; import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse;
import org.moire.ultrasonic.api.subsonic.response.SearchResponse;
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse; import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse;
import org.moire.ultrasonic.data.APIConverter; import org.moire.ultrasonic.data.APIConverter;
import org.moire.ultrasonic.domain.Bookmark; import org.moire.ultrasonic.domain.Bookmark;
@ -94,7 +95,6 @@ import org.moire.ultrasonic.service.parser.PodcastEpisodeParser;
import org.moire.ultrasonic.service.parser.PodcastsChannelsParser; import org.moire.ultrasonic.service.parser.PodcastsChannelsParser;
import org.moire.ultrasonic.service.parser.RandomSongsParser; import org.moire.ultrasonic.service.parser.RandomSongsParser;
import org.moire.ultrasonic.service.parser.SearchResult2Parser; import org.moire.ultrasonic.service.parser.SearchResult2Parser;
import org.moire.ultrasonic.service.parser.SearchResultParser;
import org.moire.ultrasonic.service.parser.ShareParser; import org.moire.ultrasonic.service.parser.ShareParser;
import org.moire.ultrasonic.service.parser.UserInfoParser; import org.moire.ultrasonic.service.parser.UserInfoParser;
import org.moire.ultrasonic.service.ssl.SSLSocketFactory; import org.moire.ultrasonic.service.ssl.SSLSocketFactory;
@ -407,39 +407,36 @@ public class RESTMusicService implements MusicService
return APIConverter.toMusicDirectoryDomainEntity(response.body().getAlbum()); return APIConverter.toMusicDirectoryDomainEntity(response.body().getAlbum());
} }
@Override @Override
public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception public SearchResult search(SearchCriteria criteria,
{ Context context,
try ProgressListener progressListener) throws Exception {
{ try {
return !Util.isOffline(context) && Util.getShouldUseId3Tags(context) ? search3(criteria, context, progressListener) : search2(criteria, context, progressListener); return !Util.isOffline(context) &&
} Util.getShouldUseId3Tags(context) ?
catch (ServerTooOldException x) search3(criteria, context, progressListener) :
{ search2(criteria, context, progressListener);
// Ensure backward compatibility with REST 1.3. } catch (ServerTooOldException x) {
return searchOld(criteria, context, progressListener); // Ensure backward compatibility with REST 1.3.
} return searchOld(criteria, context, progressListener);
} }
}
/** /**
* Search using the "search" REST method. * Search using the "search" REST method.
*/ */
private SearchResult searchOld(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception private SearchResult searchOld(SearchCriteria criteria,
{ Context context,
List<String> parameterNames = asList("any", "songCount"); ProgressListener progressListener) throws Exception {
List<Object> parameterValues = Arrays.<Object>asList(criteria.getQuery(), criteria.getSongCount()); updateProgressListener(progressListener, R.string.parser_reading);
Reader reader = getReader(context, progressListener, "search", null, parameterNames, parameterValues); Response<SearchResponse> response = subsonicAPIClient.getApi().search(null, null, null, criteria.getQuery(),
try criteria.getSongCount(), null, null).execute();
{ checkResponseSuccessful(response);
return new SearchResultParser(context).parse(reader, progressListener);
}
finally
{
Util.close(reader);
}
}
/** return APIConverter.toDomainEntity(response.body().getSearchResult());
}
/**
* Search using the "search2" REST method, available in 1.4.0 and later. * Search using the "search2" REST method, available in 1.4.0 and later.
*/ */
private SearchResult search2(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception private SearchResult search2(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception

View File

@ -1,77 +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 <http://www.gnu.org/licenses/>.
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.domain.SearchResult;
import org.moire.ultrasonic.domain.Artist;
import org.moire.ultrasonic.util.ProgressListener;
import org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
/**
* @author Sindre Mehus
*/
public class SearchResultParser extends MusicDirectoryEntryParser
{
public SearchResultParser(Context context)
{
super(context);
}
public SearchResult parse(Reader reader, ProgressListener progressListener) throws Exception
{
updateProgress(progressListener, R.string.parser_reading);
init(reader);
List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
int eventType;
do
{
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG)
{
String name = getElementName();
if ("match".equals(name))
{
songs.add(parseEntry("", false, 0));
}
else if ("error".equals(name))
{
handleError();
}
}
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
updateProgress(progressListener, R.string.parser_reading_done);
return new SearchResult(Collections.<Artist>emptyList(), Collections.<MusicDirectory.Entry>emptyList(), songs);
}
}

View File

@ -9,10 +9,12 @@ import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Indexes import org.moire.ultrasonic.domain.Indexes
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.api.subsonic.models.Artist as APIArtist 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.Indexes as APIIndexes
import org.moire.ultrasonic.api.subsonic.models.MusicDirectory as APIMusicDirectory import org.moire.ultrasonic.api.subsonic.models.MusicDirectory as APIMusicDirectory
import org.moire.ultrasonic.api.subsonic.models.MusicFolder as APIMusicFolder import org.moire.ultrasonic.api.subsonic.models.MusicFolder as APIMusicFolder
import org.moire.ultrasonic.api.subsonic.models.SearchResult as APISearchResult
fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id.toString(), this.name) fun APIMusicFolder.toDomainEntity(): MusicFolder = MusicFolder(this.id.toString(), this.name)
@ -85,3 +87,6 @@ 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() })
} }
fun APISearchResult.toDomainEntity(): SearchResult = SearchResult(emptyList(), emptyList(),
this.matchList.map { it.toDomainEntity() })

View File

@ -4,6 +4,7 @@ package org.moire.ultrasonic.data
import org.amshove.kluent.`should equal to` import org.amshove.kluent.`should equal to`
import org.amshove.kluent.`should equal` import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not equal`
import org.junit.Test import org.junit.Test
import org.moire.ultrasonic.api.subsonic.models.Album import org.moire.ultrasonic.api.subsonic.models.Album
import org.moire.ultrasonic.api.subsonic.models.Artist import org.moire.ultrasonic.api.subsonic.models.Artist
@ -12,6 +13,7 @@ import org.moire.ultrasonic.api.subsonic.models.Indexes
import org.moire.ultrasonic.api.subsonic.models.MusicDirectory import org.moire.ultrasonic.api.subsonic.models.MusicDirectory
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
import org.moire.ultrasonic.api.subsonic.models.MusicFolder import org.moire.ultrasonic.api.subsonic.models.MusicFolder
import org.moire.ultrasonic.api.subsonic.models.SearchResult
import java.util.Calendar import java.util.Calendar
/** /**
@ -192,6 +194,24 @@ class APIConverterTest {
} }
} }
@Test
fun `Should convert SearchResult to domain entity`() {
val entity = SearchResult(offset = 10, totalHits = 3, matchList = listOf(
MusicDirectoryChild(id = 101L)
))
val convertedEntity = entity.toDomainEntity()
with(convertedEntity) {
albums `should not equal` null
albums.size `should equal to` 0
artists `should not equal` null
artists.size `should equal to` 0
songs.size `should equal to` entity.matchList.size
songs[0] `should equal` entity.matchList[0].toDomainEntity()
}
}
private fun createMusicFolder(id: Long = 0, name: String = ""): MusicFolder = private fun createMusicFolder(id: Long = 0, name: String = ""): MusicFolder =
MusicFolder(id, name) MusicFolder(id, name)