From d9a7fa241398b508f68e26cd4ca9dd1324259efa Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 12:19:34 +0200 Subject: [PATCH 01/16] Cleaner separation of API result handling. * CallResponseChecker functionality are now Kotlin Extensions * Removed unfitting shortcuts from SubsonicAPIClient * Increase timeout to get a video url * Fix a bug, that the Rest API version was checked twice on each call --- .../api/subsonic/GetStreamUrlTest.kt | 8 +- .../api/subsonic/SubsonicApiGetAvatarTest.kt | 8 +- .../subsonic/SubsonicApiGetCoverArtTest.kt | 6 +- .../api/subsonic/SubsonicApiStreamTest.kt | 8 +- .../ultrasonic/api/subsonic/Extensions.kt | 97 ++++++ .../api/subsonic/SubsonicAPIClient.kt | 113 ++---- .../api/subsonic}/SubsonicRESTException.kt | 4 +- .../VersionAwareJacksonConverterFactory.kt | 3 +- .../service/JukeboxMediaPlayer.java | 1 + .../ultrasonic/data/ActiveServerProvider.kt | 11 +- .../moire/ultrasonic/di/MusicServiceModule.kt | 4 +- .../ultrasonic/fragment/EditServerFragment.kt | 14 +- .../imageloader/AvatarRequestHandler.kt | 3 +- .../imageloader/CoverArtRequestHandler.kt | 7 +- .../ultrasonic/imageloader/ImageLoader.kt | 12 +- .../service/ApiCallResponseChecker.kt | 66 ---- .../service/CommunicationErrorHandler.kt | 1 + .../ultrasonic/service/RESTMusicService.kt | 322 +++++++++--------- .../ultrasonic/subsonic/RestErrorMapper.kt | 4 +- .../imageloader/AvatarRequestHandlerTest.kt | 3 +- .../imageloader/CoverArtRequestHandlerTest.kt | 9 +- 21 files changed, 340 insertions(+), 364 deletions(-) create mode 100644 core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt rename {ultrasonic/src/main/kotlin/org/moire/ultrasonic/service => core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic}/SubsonicRESTException.kt (67%) delete mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/ApiCallResponseChecker.kt diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt index 259f714b..7cdf4a69 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt @@ -10,7 +10,7 @@ import org.moire.ultrasonic.api.subsonic.interceptors.toHexBytes import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule /** - * Integration test for [SubsonicAPIClient.getStreamUrl] method. + * Integration test for [getStreamUrl] method. */ class GetStreamUrlTest { @JvmField @Rule val mockWebServerRule = MockWebServerRule() @@ -30,7 +30,7 @@ class GetStreamUrlTest { ) client = SubsonicAPIClient(config) val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString() - expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" + + expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&format=raw&u=$USERNAME" + "&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}" } @@ -38,7 +38,7 @@ class GetStreamUrlTest { fun `Should return valid stream url`() { mockWebServerRule.enqueueResponse("ping_ok.json") - val streamUrl = client.getStreamUrl(id) + val streamUrl = client.api.getStreamUrl(id) streamUrl `should be equal to` expectedUrl } @@ -47,7 +47,7 @@ class GetStreamUrlTest { fun `Should still return stream url if connection failed`() { mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(500)) - val streamUrl = client.getStreamUrl(id) + val streamUrl = client.api.getStreamUrl(id) streamUrl `should be equal to` expectedUrl } diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt index 3bb637ac..d6a11332 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt @@ -7,14 +7,14 @@ import org.amshove.kluent.`should not be` import org.junit.Test /** - * Integration test for [SubsonicAPIClient.getAvatar] call. + * Integration test for [SubsonicAPIDefinition.getAvatar] call. */ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() { @Test fun `Should handle api error response`() { mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json") - val response = client.getAvatar("some") + val response = client.api.getAvatar("some-id").execute().toStreamResponse() with(response) { stream `should be` null @@ -28,7 +28,7 @@ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() { val httpErrorCode = 500 mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode)) - val response = client.getAvatar("some") + val response = client.api.getAvatar("some-id").execute().toStreamResponse() with(response) { stream `should be equal to` null @@ -44,7 +44,7 @@ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() { .setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")) ) - val response = client.stream("some") + val response = client.api.stream("some-id").execute().toStreamResponse() with(response) { responseHttpCode `should be equal to` 200 diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt index f760f98a..2c047ee8 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt @@ -14,7 +14,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() { fun `Should handle api error response`() { mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json") - val response = client.getCoverArt("some-id") + val response = client.api.getCoverArt("some-id").execute().toStreamResponse() with(response) { stream `should be` null @@ -28,7 +28,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() { val httpErrorCode = 404 mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode)) - val response = client.getCoverArt("some-id") + val response = client.api.getCoverArt("some-id").execute().toStreamResponse() with(response) { stream `should be` null @@ -44,7 +44,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() { .setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")) ) - val response = client.getCoverArt("some-id") + val response = client.api.getCoverArt("some-id").execute().toStreamResponse() with(response) { responseHttpCode `should be equal to` 200 diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt index b0bd27db..ad16462b 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt @@ -14,7 +14,7 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() { fun `Should handle api error response`() { mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json") - val response = client.stream("some-id") + val response = client.api.stream("some-id").execute().toStreamResponse() with(response) { stream `should be` null @@ -28,7 +28,7 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() { val httpErrorCode = 404 mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode)) - val response = client.stream("some-id") + val response = client.api.stream("some-id").execute().toStreamResponse() with(response) { stream `should be` null @@ -38,13 +38,13 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() { } @Test - fun `Should return successfull call stream`() { + fun `Should return successful call stream`() { mockWebServerRule.mockWebServer.enqueue( MockResponse() .setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")) ) - val response = client.stream("some-id") + val response = client.api.stream("some-id").execute().toStreamResponse() with(response) { responseHttpCode `should be equal to` 200 diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt new file mode 100644 index 00000000..08c6c3f4 --- /dev/null +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt @@ -0,0 +1,97 @@ +package org.moire.ultrasonic.api.subsonic + +import com.fasterxml.jackson.module.kotlin.readValue +import java.io.IOException +import okhttp3.ResponseBody +import org.moire.ultrasonic.api.subsonic.response.StreamResponse +import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse +import retrofit2.Response + +/** + * Converts a Response to a StreamResponse + */ +fun Response.toStreamResponse(): StreamResponse { + val response = this + return if (response.isSuccessful) { + val responseBody = response.body() + val contentType = responseBody?.contentType() + if ( + contentType != null && + contentType.type().equals("application", true) && + contentType.subtype().equals("json", true) + ) { + val error = SubsonicAPIClient.jacksonMapper.readValue( + responseBody.byteStream() + ) + StreamResponse(apiError = error.error, responseHttpCode = response.code()) + } else { + StreamResponse( + stream = responseBody?.byteStream(), + responseHttpCode = response.code() + ) + } + } else { + StreamResponse(responseHttpCode = response.code()) + } +} + +/** + * This call wraps Subsonic API calls so their results can be checked for errors, API version, etc + * It creates Exceptions from the results returned by the Subsonic API + */ +@Suppress("ThrowsCount") +fun Response.throwOnFailure(): Response { + val response = this + + if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) { + return this + } + if (!response.isSuccessful) { + throw IOException("Server error, code: " + response.code()) + } else if ( + response.body()!!.status === SubsonicResponse.Status.ERROR && + response.body()!!.error != null + ) { + throw SubsonicRESTException(response.body()!!.error!!) + } else { + throw IOException("Failed to perform request: " + response.code()) + } +} + +fun Response.falseOnFailure(): Boolean { + return (this.isSuccessful && this.body()!!.status === SubsonicResponse.Status.OK) +} + +/** + * This call wraps Subsonic API calls so their results can be checked for errors, API version, etc + * It creates Exceptions from a StreamResponse + */ +fun StreamResponse.throwOnFailure(): StreamResponse { + val response = this + if (response.hasError() || response.stream == null) { + if (response.apiError != null) { + throw SubsonicRESTException(response.apiError) + } else { + throw IOException( + "Failed to make endpoint request, code: " + response.responseHttpCode + ) + } + } + return this +} + +/** + * Gets a stream url. + * + * Calling this method do actual connection to the backend, though not downloading all content. + * + * Consider do not use this method, but [SubsonicAPIDefinition.stream] call. + */ +fun SubsonicAPIDefinition.getStreamUrl(id: String): String { + val response = this.stream(id, format = "raw").execute() + val url = response.raw().request().url().toString() + if (response.isSuccessful) { + response.body()?.close() + } + return url +} diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index 6484208e..c111716b 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -3,23 +3,18 @@ package org.moire.ultrasonic.api.subsonic import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.fasterxml.jackson.module.kotlin.readValue import java.security.SecureRandom import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit.MILLISECONDS import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager import okhttp3.OkHttpClient -import okhttp3.ResponseBody import okhttp3.logging.HttpLoggingInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.PasswordHexInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.PasswordMD5Interceptor import org.moire.ultrasonic.api.subsonic.interceptors.ProxyPasswordInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.RangeHeaderInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.VersionInterceptor -import org.moire.ultrasonic.api.subsonic.response.StreamResponse -import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse -import retrofit2.Response import retrofit2.Retrofit private const val READ_TIMEOUT = 60_000L @@ -48,8 +43,11 @@ class SubsonicAPIClient( config.enableLdapUserSupport ) + var onProtocolChange: (SubsonicAPIVersions) -> Unit = {} + /** - * Get currently used protocol version. + * The currently used protocol version. + * The setter also updates the interceptors and callback (if registered) */ var protocolVersion = config.minimalProtocolVersion private set(value) { @@ -57,6 +55,7 @@ class SubsonicAPIClient( proxyPasswordInterceptor.apiVersion = field wrappedApi.currentApiVersion = field versionInterceptor.protocolVersion = field + onProtocolChange(field) } private val okHttpClient = baseOkClient.newBuilder() @@ -78,18 +77,19 @@ class SubsonicAPIClient( .apply { if (config.debug) addLogging() } .build() - private val jacksonMapper = ObjectMapper() - .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) - .registerModule(KotlinModule()) - + // Create the Retrofit instance, and register a special converter factory + // It will update our protocol version to the correct version, once we made a successful call private val retrofit = Retrofit.Builder() .baseUrl("${config.baseUrl}/rest/") .client(okHttpClient) .addConverterFactory( VersionAwareJacksonConverterFactory.create( - { protocolVersion = it }, + { + // Only trigger update on change + if (protocolVersion != it) { + protocolVersion = it + } + }, jacksonMapper ) ) @@ -102,85 +102,6 @@ class SubsonicAPIClient( val api: SubsonicAPIDefinition get() = wrappedApi - /** - * TODO: Remove this in favour of handling the stream response inside RESTService - * Convenient method to get cover art from api using item [id] and optional maximum [size]. - * - * It detects the response `Content-Type` and tries to parse subsonic error if there is one. - * - * Prefer this method over [SubsonicAPIDefinition.getCoverArt] as this handles error cases. - */ - fun getCoverArt(id: String, size: Long? = null): StreamResponse = handleStreamResponse { - api.getCoverArt(id, size).execute() - } - - /** - * TODO: Remove this in favour of handling the stream response inside RESTService - * Convenient method to get media stream from api using item [id] and optional [maxBitrate]. - * - * Optionally also you can provide [offset] that stream should start from. - * - * It detects the response `Content-Type` and tries to parse subsonic error if there is one. - * - * Prefer this method over [SubsonicAPIDefinition.stream] as this handles error cases. - */ - fun stream(id: String, maxBitrate: Int? = null, offset: Long? = null): StreamResponse = - handleStreamResponse { - api.stream(id, maxBitrate, offset = offset).execute() - } - - /** - * TODO: Remove this in favour of handling the stream response inside RESTService - * Convenient method to get user avatar using [username]. - * - * It detects the response `Content-Type` and tries to parse subsonic error if there is one. - * - * Prefer this method over [SubsonicAPIDefinition.getAvatar] as this handles error cases. - */ - fun getAvatar(username: String): StreamResponse = handleStreamResponse { - api.getAvatar(username).execute() - } - - // TODO: Move this to response checker - private inline fun handleStreamResponse(apiCall: () -> Response): StreamResponse { - val response = apiCall() - return if (response.isSuccessful) { - val responseBody = response.body() - val contentType = responseBody?.contentType() - if ( - contentType != null && - contentType.type().equals("application", true) && - contentType.subtype().equals("json", true) - ) { - val error = jacksonMapper.readValue(responseBody.byteStream()) - StreamResponse(apiError = error.error, responseHttpCode = response.code()) - } else { - StreamResponse( - stream = responseBody?.byteStream(), - responseHttpCode = response.code() - ) - } - } else { - StreamResponse(responseHttpCode = response.code()) - } - } - - /** - * Get stream url. - * - * Calling this method do actual connection to the backend, though not downloading all content. - * - * Consider do not use this method, but [stream] call. - */ - fun getStreamUrl(id: String): String { - val request = api.stream(id).execute() - val url = request.raw().request().url().toString() - if (request.isSuccessful) { - request.body()?.close() - } - return url - } - private fun OkHttpClient.Builder.addLogging() { val loggingInterceptor = HttpLoggingInterceptor(okLogger) loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY @@ -202,4 +123,12 @@ class SubsonicAPIClient( hostnameVerifier { _, _ -> true } } + + companion object { + val jacksonMapper: ObjectMapper = ObjectMapper() + .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .registerModule(KotlinModule()) + } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicRESTException.kt similarity index 67% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt rename to core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicRESTException.kt index 4d315d0a..0ce8861c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/SubsonicRESTException.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicRESTException.kt @@ -1,6 +1,4 @@ -package org.moire.ultrasonic.service - -import org.moire.ultrasonic.api.subsonic.SubsonicError +package org.moire.ultrasonic.api.subsonic /** * Exception returned by API with given `code`. diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt index 66ba8d8b..7863d7a1 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt @@ -63,7 +63,6 @@ class VersionAwareJacksonConverterFactory( } } - @Suppress("SwallowedException") class VersionAwareResponseBodyConverter ( private val notifier: (SubsonicAPIVersions) -> Unit = {}, private val adapter: ObjectReader @@ -77,7 +76,7 @@ class VersionAwareJacksonConverterFactory( if (response is SubsonicResponse) { try { notifier(response.version) - } catch (e: IllegalArgumentException) { + } catch (ignored: IllegalArgumentException) { // no-op } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java index f7c24cd8..3c14ad65 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java @@ -30,6 +30,7 @@ import android.widget.Toast; import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.R; import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; +import org.moire.ultrasonic.api.subsonic.SubsonicRESTException; import org.moire.ultrasonic.app.UApp; import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.JukeboxStatus; diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt index a8e9b819..d9c11c86 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -37,12 +37,15 @@ class ActiveServerProvider( cachedServer = repository.findById(serverId) } Timber.d( - "getActiveServer retrieved from DataBase, id: $serverId; " + - "cachedServer: $cachedServer" + "getActiveServer retrieved from DataBase, id: %s cachedServer: %s", + serverId, cachedServer ) } - if (cachedServer != null) return cachedServer!! + if (cachedServer != null) { + return cachedServer!! + } + setActiveServerId(0) } @@ -105,7 +108,7 @@ class ActiveServerProvider( * @param method: The Rest resource to use * @return The Rest Url of the method on the server */ - fun getRestUrl(method: String?): String? { + fun getRestUrl(method: String?): String { val builder = StringBuilder(8192) val activeServer = getActiveServer() val serverUrl: String = activeServer.url diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index c2c0b6d2..fb26fa50 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -14,7 +14,6 @@ import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.imageloader.ImageLoader import org.moire.ultrasonic.log.TimberOkHttpLogger -import org.moire.ultrasonic.service.ApiCallResponseChecker import org.moire.ultrasonic.service.CachedMusicService import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.OfflineMusicService @@ -68,10 +67,9 @@ val musicServiceModule = module { single { TimberOkHttpLogger() } single { SubsonicAPIClient(get(), get()) } - single { ApiCallResponseChecker(get(), get()) } single(named(ONLINE_MUSIC_SERVICE)) { - CachedMusicService(RESTMusicService(get(), get(), get(), get())) + CachedMusicService(RESTMusicService(get(), get(), get())) } single(named(OFFLINE_MUSIC_SERVICE)) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt index 39dc892d..077a7587 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt @@ -22,12 +22,13 @@ import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration +import org.moire.ultrasonic.api.subsonic.SubsonicRESTException +import org.moire.ultrasonic.api.subsonic.falseOnFailure import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse +import org.moire.ultrasonic.api.subsonic.throwOnFailure import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ServerSetting -import org.moire.ultrasonic.service.ApiCallResponseChecker import org.moire.ultrasonic.service.MusicServiceFactory -import org.moire.ultrasonic.service.SubsonicRESTException import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.ErrorDialog import org.moire.ultrasonic.util.ModalBackgroundTask @@ -360,7 +361,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { // Execute a ping to check the authentication, now using the correct API version. pingResponse = subsonicApiClient.api.ping().execute() - ApiCallResponseChecker.checkResponseSuccessful(pingResponse) + pingResponse.throwOnFailure() currentServerSetting!!.chatSupport = isServerFunctionAvailable { subsonicApiClient.api.getChatMessages().execute() @@ -387,7 +388,8 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { updateProgress(getProgress()) val licenseResponse = subsonicApiClient.api.getLicense().execute() - ApiCallResponseChecker.checkResponseSuccessful(licenseResponse) + licenseResponse.throwOnFailure() + if (!licenseResponse.body()!!.license.valid) { return getProgress() + "\n" + resources.getString(R.string.settings_testing_unlicensed) @@ -438,9 +440,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { private fun isServerFunctionAvailable(function: () -> Response): Boolean { return try { - val response = function() - ApiCallResponseChecker.checkResponseSuccessful(response) - true + function().falseOnFailure() } catch (_: IOException) { false } catch (_: SubsonicRESTException) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt index e009f58c..0c32e2d3 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt @@ -6,6 +6,7 @@ import com.squareup.picasso.RequestHandler import java.io.IOException import okio.Okio import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient +import org.moire.ultrasonic.api.subsonic.toStreamResponse /** * Loads avatars from subsonic api. @@ -23,7 +24,7 @@ class AvatarRequestHandler( val username = request.uri.getQueryParameter(QUERY_USERNAME) ?: throw IllegalArgumentException("Nullable username") - val response = apiClient.getAvatar(username) + val response = apiClient.api.getAvatar(username).execute().toStreamResponse() if (response.hasError() || response.stream == null) { throw IOException("${response.apiError}") } else { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt index a6aeb048..fbfad62f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt @@ -7,13 +7,14 @@ import com.squareup.picasso.RequestHandler import java.io.IOException import okio.Okio import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient +import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE import org.moire.ultrasonic.util.FileUtil.SUFFIX_SMALL /** * Loads cover arts from subsonic api. */ -class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : RequestHandler() { +class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHandler() { override fun canHandleRequest(data: Request): Boolean { return with(data.uri) { scheme == SCHEME && @@ -38,7 +39,9 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request } // Try to fetch the image from the API - val response = apiClient.getCoverArt(id, size) + val response = client.api.getCoverArt(id, size).execute().toStreamResponse() + + // Handle the response if (!response.hasError() && response.stream != null) { return Result(Okio.source(response.stream!!), NETWORK) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt index 52f7e5ef..9d40ad28 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -13,8 +13,9 @@ import java.io.OutputStream import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient +import org.moire.ultrasonic.api.subsonic.throwOnFailure +import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.moire.ultrasonic.domain.MusicDirectory -import org.moire.ultrasonic.service.RESTMusicService import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber @@ -24,9 +25,12 @@ import timber.log.Timber */ class ImageLoader( context: Context, - private val apiClient: SubsonicAPIClient, + apiClient: SubsonicAPIClient, private val config: ImageLoaderConfig ) { + // Shortcut + @Suppress("VariableNaming", "PropertyName") + val API = apiClient.api private val picasso = Picasso.Builder(context) .addRequestHandler(CoverArtRequestHandler(apiClient)) @@ -143,8 +147,8 @@ class ImageLoader( // Query the API Timber.d("Loading cover art for: %s", entry) - val response = apiClient.getCoverArt(id!!, size.toLong()) - RESTMusicService.checkStreamResponseError(response) + val response = API.getCoverArt(id!!, size.toLong()).execute().toStreamResponse() + response.throwOnFailure() // Check for failure if (response.stream == null) return diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/ApiCallResponseChecker.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/ApiCallResponseChecker.kt deleted file mode 100644 index 51bd4b0a..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/ApiCallResponseChecker.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.moire.ultrasonic.service - -import java.io.IOException -import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient -import org.moire.ultrasonic.api.subsonic.SubsonicAPIDefinition -import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse -import org.moire.ultrasonic.data.ActiveServerProvider -import retrofit2.Response -import timber.log.Timber - -/** - * This call wraps Subsonic API calls so their results can be checked for errors, API version, etc - */ -class ApiCallResponseChecker( - private val subsonicAPIClient: SubsonicAPIClient, - private val activeServerProvider: ActiveServerProvider -) { - /** - * Executes a Subsonic API call with response check - */ - @Throws(SubsonicRESTException::class, IOException::class) - fun > callWithResponseCheck( - call: (SubsonicAPIDefinition) -> T - ): T { - // Check for API version when first contacting the server - if (activeServerProvider.getActiveServer().minimumApiVersion == null) { - try { - val response = subsonicAPIClient.api.ping().execute() - if (response.body() != null) { - val restApiVersion = response.body()!!.version.restApiVersion - Timber.i("Server minimum API version set to %s", restApiVersion) - activeServerProvider.setMinimumApiVersion(restApiVersion) - } - } catch (ignored: Exception) { - // This Ping is only used to get the API Version, if it fails, that's no problem. - } - } - - // This call will be now executed with the correct API Version, so it shouldn't fail - val result = call.invoke(subsonicAPIClient.api) - checkResponseSuccessful(result) - return result - } - - /** - * Creates Exceptions from the results returned by the Subsonic API - */ - companion object { - @Throws(SubsonicRESTException::class, IOException::class) - fun checkResponseSuccessful(response: Response) { - if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) { - return - } - if (!response.isSuccessful) { - throw IOException("Server error, code: " + response.code()) - } else if ( - response.body()!!.status === SubsonicResponse.Status.ERROR && - response.body()!!.error != null - ) { - throw SubsonicRESTException(response.body()!!.error!!) - } else { - throw IOException("Failed to perform request: " + response.code()) - } - } - } -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt index 588ac266..1b876fb5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt @@ -28,6 +28,7 @@ import java.security.cert.CertificateException import javax.net.ssl.SSLException import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException +import org.moire.ultrasonic.api.subsonic.SubsonicRESTException import org.moire.ultrasonic.subsonic.getLocalizedErrorMessage import org.moire.ultrasonic.util.Util import timber.log.Timber diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index d3e3e708..ab7b0717 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -13,11 +13,14 @@ import java.io.IOException import java.io.InputStream import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient +import org.moire.ultrasonic.api.subsonic.getStreamUrl import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName import org.moire.ultrasonic.api.subsonic.models.JukeboxAction -import org.moire.ultrasonic.api.subsonic.response.StreamResponse +import org.moire.ultrasonic.api.subsonic.throwOnFailure +import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.cache.serializers.getIndexesSerializer import org.moire.ultrasonic.cache.serializers.getMusicFolderListSerializer @@ -50,20 +53,24 @@ import timber.log.Timber */ @Suppress("LargeClass") open class RESTMusicService( - private val subsonicAPIClient: SubsonicAPIClient, + subsonicAPIClient: SubsonicAPIClient, private val fileStorage: PermanentFileStorage, - private val activeServerProvider: ActiveServerProvider, - private val responseChecker: ApiCallResponseChecker + private val activeServerProvider: ActiveServerProvider ) : MusicService { + // Shortcut to the API + @Suppress("VariableNaming", "PropertyName") + val API = subsonicAPIClient.api + @Throws(Exception::class) override fun ping() { - responseChecker.callWithResponseCheck { api -> api.ping().execute() } + API.ping().execute().throwOnFailure() } @Throws(Exception::class) override fun isLicenseValid(): Boolean { - val response = responseChecker.callWithResponseCheck { api -> api.getLicense().execute() } + val response = API.getLicense().execute() + response.throwOnFailure() return response.body()!!.license.valid } @@ -78,9 +85,8 @@ open class RESTMusicService( if (cachedMusicFolders != null && !refresh) return cachedMusicFolders - val response = responseChecker.callWithResponseCheck { api -> - api.getMusicFolders().execute() - } + val response = API.getMusicFolders().execute() + response.throwOnFailure() val musicFolders = response.body()!!.musicFolders.toDomainEntityList() fileStorage.store(MUSIC_FOLDER_STORAGE_NAME, musicFolders, getMusicFolderListSerializer()) @@ -98,9 +104,8 @@ open class RESTMusicService( val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer()) if (cachedIndexes != null && !refresh) return cachedIndexes - val response = responseChecker.callWithResponseCheck { api -> - api.getIndexes(musicFolderId, null).execute() - } + val response = API.getIndexes(musicFolderId, null).execute() + response.throwOnFailure() val indexes = response.body()!!.indexes.toDomainEntity() fileStorage.store(indexName, indexes, getIndexesSerializer()) @@ -114,9 +119,8 @@ open class RESTMusicService( val cachedArtists = fileStorage.load(ARTISTS_STORAGE_NAME, getIndexesSerializer()) if (cachedArtists != null && !refresh) return cachedArtists - val response = responseChecker.callWithResponseCheck { api -> - api.getArtists(null).execute() - } + val response = API.getArtists(null).execute() + response.throwOnFailure() val indexes = response.body()!!.indexes.toDomainEntity() fileStorage.store(ARTISTS_STORAGE_NAME, indexes, getIndexesSerializer()) @@ -129,7 +133,7 @@ open class RESTMusicService( albumId: String?, artistId: String? ) { - responseChecker.callWithResponseCheck { api -> api.star(id, albumId, artistId).execute() } + API.star(id, albumId, artistId).execute().throwOnFailure() } @Throws(Exception::class) @@ -138,7 +142,7 @@ open class RESTMusicService( albumId: String?, artistId: String? ) { - responseChecker.callWithResponseCheck { api -> api.unstar(id, albumId, artistId).execute() } + API.unstar(id, albumId, artistId).execute().throwOnFailure() } @Throws(Exception::class) @@ -146,7 +150,7 @@ open class RESTMusicService( id: String, rating: Int ) { - responseChecker.callWithResponseCheck { api -> api.setRating(id, rating).execute() } + API.setRating(id, rating).execute().throwOnFailure() } @Throws(Exception::class) @@ -155,9 +159,8 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getMusicDirectory(id).execute() - } + val response = API.getMusicDirectory(id).execute() + response.throwOnFailure() return response.body()!!.musicDirectory.toDomainEntity() } @@ -168,7 +171,8 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> api.getArtist(id).execute() } + val response = API.getArtist(id).execute() + response.throwOnFailure() return response.body()!!.artist.toMusicDirectoryDomainEntity() } @@ -179,7 +183,8 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> api.getAlbum(id).execute() } + val response = API.getAlbum(id).execute() + response.throwOnFailure() return response.body()!!.album.toMusicDirectoryDomainEntity() } @@ -207,10 +212,10 @@ open class RESTMusicService( private fun searchOld( criteria: SearchCriteria ): SearchResult { - val response = responseChecker.callWithResponseCheck { api -> - api.search(null, null, null, criteria.query, criteria.songCount, null, null) + val response = + API.search(null, null, null, criteria.query, criteria.songCount, null, null) .execute() - } + response.throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -223,12 +228,12 @@ open class RESTMusicService( criteria: SearchCriteria ): SearchResult { requireNotNull(criteria.query) { "Query param is null" } - val response = responseChecker.callWithResponseCheck { api -> - api.search2( - criteria.query, criteria.artistCount, null, criteria.albumCount, null, - criteria.songCount, null - ).execute() - } + val response = API.search2( + criteria.query, criteria.artistCount, null, criteria.albumCount, null, + criteria.songCount, null + ).execute() + + response.throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -238,12 +243,12 @@ open class RESTMusicService( criteria: SearchCriteria ): SearchResult { requireNotNull(criteria.query) { "Query param is null" } - val response = responseChecker.callWithResponseCheck { api -> - api.search3( - criteria.query, criteria.artistCount, null, criteria.albumCount, null, - criteria.songCount, null - ).execute() - } + val response = API.search3( + criteria.query, criteria.artistCount, null, criteria.albumCount, null, + criteria.songCount, null + ).execute() + + response.throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -253,9 +258,8 @@ open class RESTMusicService( id: String, name: String ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getPlaylist(id).execute() - } + val response = API.getPlaylist(id).execute() + response.throwOnFailure() val playlist = response.body()!!.playlist.toMusicDirectoryDomainEntity() savePlaylist(name, playlist) @@ -300,9 +304,8 @@ open class RESTMusicService( override fun getPlaylists( refresh: Boolean ): List { - val response = responseChecker.callWithResponseCheck { api -> - api.getPlaylists(null).execute() - } + val response = API.getPlaylists(null).execute() + response.throwOnFailure() return response.body()!!.playlists.toDomainEntitiesList() } @@ -318,16 +321,15 @@ open class RESTMusicService( for ((id1) in entries) { pSongIds.add(id1) } - responseChecker.callWithResponseCheck { api -> - api.createPlaylist(id, name, pSongIds.toList()).execute() - } + + API.createPlaylist(id, name, pSongIds.toList()).execute().throwOnFailure() } @Throws(Exception::class) override fun deletePlaylist( id: String ) { - responseChecker.callWithResponseCheck { api -> api.deletePlaylist(id).execute() } + API.deletePlaylist(id).execute().throwOnFailure() } @Throws(Exception::class) @@ -337,19 +339,16 @@ open class RESTMusicService( comment: String?, pub: Boolean ) { - responseChecker.callWithResponseCheck { api -> - api.updatePlaylist(id, name, comment, pub, null, null) - .execute() - } + API.updatePlaylist(id, name, comment, pub, null, null) + .execute().throwOnFailure() } @Throws(Exception::class) override fun getPodcastsChannels( refresh: Boolean ): List { - val response = responseChecker.callWithResponseCheck { api -> - api.getPodcasts(false, null).execute() - } + val response = API.getPodcasts(false, null).execute() + response.throwOnFailure() return response.body()!!.podcastChannels.toDomainEntitiesList() } @@ -358,9 +357,8 @@ open class RESTMusicService( override fun getPodcastEpisodes( podcastChannelId: String? ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getPodcasts(true, podcastChannelId).execute() - } + val response = API.getPodcasts(true, podcastChannelId).execute() + response.throwOnFailure() val podcastEntries = response.body()!!.podcastChannels[0].episodeList val musicDirectory = MusicDirectory() @@ -384,9 +382,8 @@ open class RESTMusicService( artist: String, title: String ): Lyrics { - val response = responseChecker.callWithResponseCheck { api -> - api.getLyrics(artist, title).execute() - } + val response = API.getLyrics(artist, title).execute() + response.throwOnFailure() return response.body()!!.lyrics.toDomainEntity() } @@ -396,9 +393,7 @@ open class RESTMusicService( id: String, submission: Boolean ) { - responseChecker.callWithResponseCheck { api -> - api.scrobble(id, null, submission).execute() - } + API.scrobble(id, null, submission).execute().throwOnFailure() } @Throws(Exception::class) @@ -408,10 +403,17 @@ open class RESTMusicService( offset: Int, musicFolderId: String? ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getAlbumList(fromName(type), size, offset, null, null, null, musicFolderId) - .execute() - } + val response = API.getAlbumList( + fromName(type), + size, + offset, + null, + null, + null, + musicFolderId + ).execute() + + response.throwOnFailure() val childList = response.body()!!.albumList.toDomainEntityList() val result = MusicDirectory() @@ -427,17 +429,17 @@ open class RESTMusicService( offset: Int, musicFolderId: String? ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getAlbumList2( - fromName(type), - size, - offset, - null, - null, - null, - musicFolderId - ).execute() - } + val response = API.getAlbumList2( + fromName(type), + size, + offset, + null, + null, + null, + musicFolderId + ).execute() + + response.throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.albumList.toDomainEntityList()) @@ -449,15 +451,15 @@ open class RESTMusicService( override fun getRandomSongs( size: Int ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getRandomSongs( - size, - null, - null, - null, - null - ).execute() - } + val response = API.getRandomSongs( + size, + null, + null, + null, + null + ).execute() + + response.throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.songsList.toDomainEntityList()) @@ -467,18 +469,18 @@ open class RESTMusicService( @Throws(Exception::class) override fun getStarred(): SearchResult { - val response = responseChecker.callWithResponseCheck { api -> - api.getStarred(null).execute() - } + val response = API.getStarred(null).execute() + + response.throwOnFailure() return response.body()!!.starred.toDomainEntity() } @Throws(Exception::class) override fun getStarred2(): SearchResult { - val response = responseChecker.callWithResponseCheck { api -> - api.getStarred2(null).execute() - } + val response = API.getStarred2(null).execute() + + response.throwOnFailure() return response.body()!!.starred2.toDomainEntity() } @@ -491,8 +493,10 @@ open class RESTMusicService( ): Pair { val songOffset = if (offset < 0) 0 else offset - val response = subsonicAPIClient.stream(song.id, maxBitrate, songOffset) - checkStreamResponseError(response) + val response = API.stream(song.id, maxBitrate, offset = songOffset) + .execute().toStreamResponse() + + response.throwOnFailure() if (response.stream == null) { throw IOException("Null stream response") @@ -518,13 +522,18 @@ open class RESTMusicService( Thread( { - expectedResult[0] = subsonicAPIClient.getStreamUrl(id) + "&format=raw" + expectedResult[0] = API.getStreamUrl(id) latch.countDown() }, "Get-Video-Url" ).start() - latch.await(5, TimeUnit.SECONDS) + // Getting the stream can take a long time on some servers + latch.await(1, TimeUnit.MINUTES) + + if (expectedResult[0] == null) { + throw TimeoutException("Server didn't respond in time") + } return expectedResult[0]!! } @@ -533,10 +542,10 @@ open class RESTMusicService( override fun updateJukeboxPlaylist( ids: List? ): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.SET, null, null, ids, null) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.SET, null, null, ids, null) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -546,40 +555,40 @@ open class RESTMusicService( index: Int, offsetSeconds: Int ): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.SKIP, index, offsetSeconds, null, null) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.SKIP, index, offsetSeconds, null, null) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @Throws(Exception::class) override fun stopJukebox(): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.STOP, null, null, null, null) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.STOP, null, null, null, null) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @Throws(Exception::class) override fun startJukebox(): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.START, null, null, null, null) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.START, null, null, null, null) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @Throws(Exception::class) override fun getJukeboxStatus(): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.STATUS, null, null, null, null) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.STATUS, null, null, null, null) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -588,10 +597,10 @@ open class RESTMusicService( override fun setJukeboxGain( gain: Float ): JukeboxStatus { - val response = responseChecker.callWithResponseCheck { api -> - api.jukeboxControl(JukeboxAction.SET_GAIN, null, null, null, gain) - .execute() - } + val response = API.jukeboxControl(JukeboxAction.SET_GAIN, null, null, null, gain) + .execute() + + response.throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -600,7 +609,8 @@ open class RESTMusicService( override fun getShares( refresh: Boolean ): List { - val response = responseChecker.callWithResponseCheck { api -> api.getShares().execute() } + val response = API.getShares().execute() + response.throwOnFailure() return response.body()!!.shares.toDomainEntitiesList() } @@ -609,7 +619,8 @@ open class RESTMusicService( override fun getGenres( refresh: Boolean ): List? { - val response = responseChecker.callWithResponseCheck { api -> api.getGenres().execute() } + val response = API.getGenres().execute() + response.throwOnFailure() return response.body()!!.genresList.toDomainEntityList() } @@ -620,9 +631,8 @@ open class RESTMusicService( count: Int, offset: Int ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> - api.getSongsByGenre(genre, count, offset, null).execute() - } + val response = API.getSongsByGenre(genre, count, offset, null).execute() + response.throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.songsList.toDomainEntityList()) @@ -634,9 +644,9 @@ open class RESTMusicService( override fun getUser( username: String ): UserInfo { - val response = responseChecker.callWithResponseCheck { api -> - api.getUser(username).execute() - } + val response = API.getUser(username).execute() + + response.throwOnFailure() return response.body()!!.user.toDomainEntity() } @@ -645,9 +655,9 @@ open class RESTMusicService( override fun getChatMessages( since: Long? ): List { - val response = responseChecker.callWithResponseCheck { api -> - api.getChatMessages(since).execute() - } + val response = API.getChatMessages(since).execute() + + response.throwOnFailure() return response.body()!!.chatMessages.toDomainEntitiesList() } @@ -656,12 +666,13 @@ open class RESTMusicService( override fun addChatMessage( message: String ) { - responseChecker.callWithResponseCheck { api -> api.addChatMessage(message).execute() } + API.addChatMessage(message).execute().throwOnFailure() } @Throws(Exception::class) override fun getBookmarks(): List { - val response = responseChecker.callWithResponseCheck { api -> api.getBookmarks().execute() } + val response = API.getBookmarks().execute() + response.throwOnFailure() return response.body()!!.bookmarkList.toDomainEntitiesList() } @@ -671,23 +682,22 @@ open class RESTMusicService( id: String, position: Int ) { - responseChecker.callWithResponseCheck { api -> - api.createBookmark(id, position.toLong(), null).execute() - } + API.createBookmark(id, position.toLong(), null).execute().throwOnFailure() } @Throws(Exception::class) override fun deleteBookmark( id: String ) { - responseChecker.callWithResponseCheck { api -> api.deleteBookmark(id).execute() } + API.deleteBookmark(id).execute().throwOnFailure() } @Throws(Exception::class) override fun getVideos( refresh: Boolean ): MusicDirectory { - val response = responseChecker.callWithResponseCheck { api -> api.getVideos().execute() } + val response = API.getVideos().execute() + response.throwOnFailure() val musicDirectory = MusicDirectory() musicDirectory.addAll(response.body()!!.videosList.toDomainEntityList()) @@ -701,9 +711,8 @@ open class RESTMusicService( description: String?, expires: Long? ): List { - val response = responseChecker.callWithResponseCheck { api -> - api.createShare(ids, description, expires).execute() - } + val response = API.createShare(ids, description, expires).execute() + response.throwOnFailure() return response.body()!!.shares.toDomainEntitiesList() } @@ -712,7 +721,7 @@ open class RESTMusicService( override fun deleteShare( id: String ) { - responseChecker.callWithResponseCheck { api -> api.deleteShare(id).execute() } + API.deleteShare(id).execute().throwOnFailure() } @Throws(Exception::class) @@ -726,8 +735,15 @@ open class RESTMusicService( expiresValue = null } - responseChecker.callWithResponseCheck { api -> - api.updateShare(id, description, expiresValue).execute() + API.updateShare(id, description, expiresValue).execute().throwOnFailure() + } + + init { + // The client will notice if the minimum supported API version has changed + // By registering a callback we ensure this info is saved in the database as well + subsonicAPIClient.onProtocolChange = { + Timber.i("Server minimum API version set to %s", it) + activeServerProvider.setMinimumApiVersion(it.toString()) } } @@ -735,19 +751,5 @@ open class RESTMusicService( private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder" private const val INDEXES_STORAGE_NAME = "indexes" private const val ARTISTS_STORAGE_NAME = "artists" - - // TODO: Move to response checker - @Throws(SubsonicRESTException::class, IOException::class) - fun checkStreamResponseError(response: StreamResponse) { - if (response.hasError() || response.stream == null) { - if (response.apiError != null) { - throw SubsonicRESTException(response.apiError!!) - } else { - throw IOException( - "Failed to make endpoint request, code: " + response.responseHttpCode - ) - } - } - } } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt index 1ab7f043..2f2e2304 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/RestErrorMapper.kt @@ -12,7 +12,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.TokenAuthNotSupportedForL import org.moire.ultrasonic.api.subsonic.SubsonicError.TrialPeriodIsOver import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOperation import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword -import org.moire.ultrasonic.service.SubsonicRESTException +import org.moire.ultrasonic.api.subsonic.SubsonicRESTException /** * Extension for [SubsonicRESTException] that returns localized error string, that can used to @@ -21,7 +21,7 @@ import org.moire.ultrasonic.service.SubsonicRESTException fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String = when (error) { is Generic -> { - val message = error.message + val message = (error as Generic).message val errorMessage = if (message == "") { context.getString(R.string.api_subsonic_generic_no_message) } else { diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt index 0b27cbcf..5ad84cff 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt @@ -14,6 +14,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.response.StreamResponse +import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -59,7 +60,7 @@ class AvatarRequestHandlerTest { apiError = null, responseHttpCode = 200 ) - whenever(mockApiClient.getAvatar(any())) + whenever(mockApiClient.api.getAvatar(any()).execute().toStreamResponse()) .thenReturn(streamResponse) val response = handler.load( diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt index 17dda53e..442eba3c 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt @@ -16,6 +16,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.response.StreamResponse +import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) @@ -56,7 +57,9 @@ class CoverArtRequestHandlerTest { fun `Should throw IOException when request to api failed`() { val streamResponse = StreamResponse(null, null, 500) - whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse) + whenever( + mockApiClient.api.getCoverArt(any(), anyOrNull()).execute().toStreamResponse() + ).thenReturn(streamResponse) val fail = { handler.load(createLoadCoverArtRequest("some").buildRequest(), 0) @@ -73,7 +76,9 @@ class CoverArtRequestHandlerTest { responseHttpCode = 200 ) - whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse) + whenever( + mockApiClient.api.getCoverArt(any(), anyOrNull()).execute().toStreamResponse() + ).thenReturn(streamResponse) val response = handler.load( createLoadCoverArtRequest("some").buildRequest(), 0 From a60a843edf507b06f5dfa865ea4558332ab7113d Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 17:00:25 +0200 Subject: [PATCH 02/16] Fix tests --- .../moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt | 10 ++++++++++ .../ultrasonic/imageloader/AvatarRequestHandler.kt | 7 ++++--- .../ultrasonic/imageloader/CoverArtRequestHandler.kt | 3 ++- .../ultrasonic/imageloader/AvatarRequestHandlerTest.kt | 9 ++++++--- .../imageloader/CoverArtRequestHandlerTest.kt | 9 ++++----- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index c111716b..e51085a9 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -9,12 +9,15 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager import okhttp3.OkHttpClient +import okhttp3.ResponseBody import okhttp3.logging.HttpLoggingInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.PasswordHexInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.PasswordMD5Interceptor import org.moire.ultrasonic.api.subsonic.interceptors.ProxyPasswordInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.RangeHeaderInterceptor import org.moire.ultrasonic.api.subsonic.interceptors.VersionInterceptor +import org.moire.ultrasonic.api.subsonic.response.StreamResponse +import retrofit2.Response import retrofit2.Retrofit private const val READ_TIMEOUT = 60_000L @@ -124,6 +127,13 @@ class SubsonicAPIClient( hostnameVerifier { _, _ -> true } } + /** + * This function is necessary because Mockito has problems with stubbing chained calls + */ + fun toStreamResponse(call: Response): StreamResponse { + return call.toStreamResponse() + } + companion object { val jacksonMapper: ObjectMapper = ObjectMapper() .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt index 0c32e2d3..b63549fb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt @@ -6,13 +6,12 @@ import com.squareup.picasso.RequestHandler import java.io.IOException import okio.Okio import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient -import org.moire.ultrasonic.api.subsonic.toStreamResponse /** * Loads avatars from subsonic api. */ class AvatarRequestHandler( - private val apiClient: SubsonicAPIClient + private val client: SubsonicAPIClient ) : RequestHandler() { override fun canHandleRequest(data: Request): Boolean { return with(data.uri) { @@ -24,7 +23,9 @@ class AvatarRequestHandler( val username = request.uri.getQueryParameter(QUERY_USERNAME) ?: throw IllegalArgumentException("Nullable username") - val response = apiClient.api.getAvatar(username).execute().toStreamResponse() + // Inverted call order, because Mockito has problems with chained calls. + val response = client.toStreamResponse(client.api.getAvatar(username).execute()) + if (response.hasError() || response.stream == null) { throw IOException("${response.apiError}") } else { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt index fbfad62f..3cdc2039 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt @@ -39,7 +39,8 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan } // Try to fetch the image from the API - val response = client.api.getCoverArt(id, size).execute().toStreamResponse() + // Inverted call order, because Mockito has problems with chained calls. + val response = client.toStreamResponse(client.api.getCoverArt(id, size).execute()) // Handle the response if (!response.hasError() && response.stream != null) { diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt index 5ad84cff..ddf283bb 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt @@ -9,6 +9,7 @@ import org.amshove.kluent.`should throw` import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -21,7 +22,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.NONE) class AvatarRequestHandlerTest { - private val mockApiClient: SubsonicAPIClient = mock() + private val mockApiClient: SubsonicAPIClient = mock(defaultAnswer = Answers.RETURNS_DEEP_STUBS) private val handler = AvatarRequestHandler(mockApiClient) @Test @@ -60,8 +61,10 @@ class AvatarRequestHandlerTest { apiError = null, responseHttpCode = 200 ) - whenever(mockApiClient.api.getAvatar(any()).execute().toStreamResponse()) - .thenReturn(streamResponse) + + whenever( + mockApiClient.toStreamResponse(any()) + ).thenReturn(streamResponse) val response = handler.load( createLoadAvatarRequest("some-username").buildRequest(), 0 diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt index 442eba3c..fdef0b0c 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt @@ -10,18 +10,17 @@ import org.amshove.kluent.`should throw` import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.kotlin.any -import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.response.StreamResponse -import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class CoverArtRequestHandlerTest { - private val mockApiClient: SubsonicAPIClient = mock() + private val mockApiClient: SubsonicAPIClient = mock(defaultAnswer = Answers.RETURNS_DEEP_STUBS) private val handler = CoverArtRequestHandler(mockApiClient) @Test @@ -58,7 +57,7 @@ class CoverArtRequestHandlerTest { val streamResponse = StreamResponse(null, null, 500) whenever( - mockApiClient.api.getCoverArt(any(), anyOrNull()).execute().toStreamResponse() + mockApiClient.toStreamResponse(any()) ).thenReturn(streamResponse) val fail = { @@ -77,7 +76,7 @@ class CoverArtRequestHandlerTest { ) whenever( - mockApiClient.api.getCoverArt(any(), anyOrNull()).execute().toStreamResponse() + mockApiClient.toStreamResponse(any()) ).thenReturn(streamResponse) val response = handler.load( From 620239f85956a281b67e54ddb3d6ddcb55ff9353 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 17:36:11 +0200 Subject: [PATCH 03/16] Improve the extension function by making it call-chainable --- .../ultrasonic/api/subsonic/Extensions.kt | 4 +- .../ultrasonic/service/RESTMusicService.kt | 117 +++++------------- 2 files changed, 36 insertions(+), 85 deletions(-) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt index 08c6c3f4..61b310bc 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt @@ -40,11 +40,11 @@ fun Response.toStreamResponse(): StreamResponse { * It creates Exceptions from the results returned by the Subsonic API */ @Suppress("ThrowsCount") -fun Response.throwOnFailure(): Response { +fun Response.throwOnFailure(): Response { val response = this if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) { - return this + return this as Response } if (!response.isSuccessful) { throw IOException("Server error, code: " + response.code()) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index ab7b0717..684dc84b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -69,8 +69,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun isLicenseValid(): Boolean { - val response = API.getLicense().execute() - response.throwOnFailure() + val response = API.getLicense().execute().throwOnFailure() return response.body()!!.license.valid } @@ -85,8 +84,7 @@ open class RESTMusicService( if (cachedMusicFolders != null && !refresh) return cachedMusicFolders - val response = API.getMusicFolders().execute() - response.throwOnFailure() + val response = API.getMusicFolders().execute().throwOnFailure() val musicFolders = response.body()!!.musicFolders.toDomainEntityList() fileStorage.store(MUSIC_FOLDER_STORAGE_NAME, musicFolders, getMusicFolderListSerializer()) @@ -104,8 +102,7 @@ open class RESTMusicService( val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer()) if (cachedIndexes != null && !refresh) return cachedIndexes - val response = API.getIndexes(musicFolderId, null).execute() - response.throwOnFailure() + val response = API.getIndexes(musicFolderId, null).execute().throwOnFailure() val indexes = response.body()!!.indexes.toDomainEntity() fileStorage.store(indexName, indexes, getIndexesSerializer()) @@ -119,8 +116,7 @@ open class RESTMusicService( val cachedArtists = fileStorage.load(ARTISTS_STORAGE_NAME, getIndexesSerializer()) if (cachedArtists != null && !refresh) return cachedArtists - val response = API.getArtists(null).execute() - response.throwOnFailure() + val response = API.getArtists(null).execute().throwOnFailure() val indexes = response.body()!!.indexes.toDomainEntity() fileStorage.store(ARTISTS_STORAGE_NAME, indexes, getIndexesSerializer()) @@ -159,8 +155,7 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = API.getMusicDirectory(id).execute() - response.throwOnFailure() + val response = API.getMusicDirectory(id).execute().throwOnFailure() return response.body()!!.musicDirectory.toDomainEntity() } @@ -171,8 +166,7 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = API.getArtist(id).execute() - response.throwOnFailure() + val response = API.getArtist(id).execute().throwOnFailure() return response.body()!!.artist.toMusicDirectoryDomainEntity() } @@ -183,8 +177,7 @@ open class RESTMusicService( name: String?, refresh: Boolean ): MusicDirectory { - val response = API.getAlbum(id).execute() - response.throwOnFailure() + val response = API.getAlbum(id).execute().throwOnFailure() return response.body()!!.album.toMusicDirectoryDomainEntity() } @@ -214,8 +207,7 @@ open class RESTMusicService( ): SearchResult { val response = API.search(null, null, null, criteria.query, criteria.songCount, null, null) - .execute() - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -231,9 +223,7 @@ open class RESTMusicService( val response = API.search2( criteria.query, criteria.artistCount, null, criteria.albumCount, null, criteria.songCount, null - ).execute() - - response.throwOnFailure() + ).execute().throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -246,9 +236,7 @@ open class RESTMusicService( val response = API.search3( criteria.query, criteria.artistCount, null, criteria.albumCount, null, criteria.songCount, null - ).execute() - - response.throwOnFailure() + ).execute().throwOnFailure() return response.body()!!.searchResult.toDomainEntity() } @@ -258,8 +246,7 @@ open class RESTMusicService( id: String, name: String ): MusicDirectory { - val response = API.getPlaylist(id).execute() - response.throwOnFailure() + val response = API.getPlaylist(id).execute().throwOnFailure() val playlist = response.body()!!.playlist.toMusicDirectoryDomainEntity() savePlaylist(name, playlist) @@ -304,8 +291,7 @@ open class RESTMusicService( override fun getPlaylists( refresh: Boolean ): List { - val response = API.getPlaylists(null).execute() - response.throwOnFailure() + val response = API.getPlaylists(null).execute().throwOnFailure() return response.body()!!.playlists.toDomainEntitiesList() } @@ -347,8 +333,7 @@ open class RESTMusicService( override fun getPodcastsChannels( refresh: Boolean ): List { - val response = API.getPodcasts(false, null).execute() - response.throwOnFailure() + val response = API.getPodcasts(false, null).execute().throwOnFailure() return response.body()!!.podcastChannels.toDomainEntitiesList() } @@ -357,8 +342,7 @@ open class RESTMusicService( override fun getPodcastEpisodes( podcastChannelId: String? ): MusicDirectory { - val response = API.getPodcasts(true, podcastChannelId).execute() - response.throwOnFailure() + val response = API.getPodcasts(true, podcastChannelId).execute().throwOnFailure() val podcastEntries = response.body()!!.podcastChannels[0].episodeList val musicDirectory = MusicDirectory() @@ -382,8 +366,7 @@ open class RESTMusicService( artist: String, title: String ): Lyrics { - val response = API.getLyrics(artist, title).execute() - response.throwOnFailure() + val response = API.getLyrics(artist, title).execute().throwOnFailure() return response.body()!!.lyrics.toDomainEntity() } @@ -411,9 +394,7 @@ open class RESTMusicService( null, null, musicFolderId - ).execute() - - response.throwOnFailure() + ).execute().throwOnFailure() val childList = response.body()!!.albumList.toDomainEntityList() val result = MusicDirectory() @@ -437,9 +418,7 @@ open class RESTMusicService( null, null, musicFolderId - ).execute() - - response.throwOnFailure() + ).execute().throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.albumList.toDomainEntityList()) @@ -457,9 +436,7 @@ open class RESTMusicService( null, null, null - ).execute() - - response.throwOnFailure() + ).execute().throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.songsList.toDomainEntityList()) @@ -469,18 +446,14 @@ open class RESTMusicService( @Throws(Exception::class) override fun getStarred(): SearchResult { - val response = API.getStarred(null).execute() - - response.throwOnFailure() + val response = API.getStarred(null).execute().throwOnFailure() return response.body()!!.starred.toDomainEntity() } @Throws(Exception::class) override fun getStarred2(): SearchResult { - val response = API.getStarred2(null).execute() - - response.throwOnFailure() + val response = API.getStarred2(null).execute().throwOnFailure() return response.body()!!.starred2.toDomainEntity() } @@ -543,9 +516,7 @@ open class RESTMusicService( ids: List? ): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.SET, null, null, ids, null) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -556,9 +527,7 @@ open class RESTMusicService( offsetSeconds: Int ): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.SKIP, index, offsetSeconds, null, null) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -566,9 +535,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun stopJukebox(): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.STOP, null, null, null, null) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -576,9 +543,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun startJukebox(): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.START, null, null, null, null) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -586,9 +551,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun getJukeboxStatus(): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.STATUS, null, null, null, null) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -598,9 +561,7 @@ open class RESTMusicService( gain: Float ): JukeboxStatus { val response = API.jukeboxControl(JukeboxAction.SET_GAIN, null, null, null, gain) - .execute() - - response.throwOnFailure() + .execute().throwOnFailure() return response.body()!!.jukebox.toDomainEntity() } @@ -609,8 +570,7 @@ open class RESTMusicService( override fun getShares( refresh: Boolean ): List { - val response = API.getShares().execute() - response.throwOnFailure() + val response = API.getShares().execute().throwOnFailure() return response.body()!!.shares.toDomainEntitiesList() } @@ -619,8 +579,7 @@ open class RESTMusicService( override fun getGenres( refresh: Boolean ): List? { - val response = API.getGenres().execute() - response.throwOnFailure() + val response = API.getGenres().execute().throwOnFailure() return response.body()!!.genresList.toDomainEntityList() } @@ -631,8 +590,7 @@ open class RESTMusicService( count: Int, offset: Int ): MusicDirectory { - val response = API.getSongsByGenre(genre, count, offset, null).execute() - response.throwOnFailure() + val response = API.getSongsByGenre(genre, count, offset, null).execute().throwOnFailure() val result = MusicDirectory() result.addAll(response.body()!!.songsList.toDomainEntityList()) @@ -644,9 +602,7 @@ open class RESTMusicService( override fun getUser( username: String ): UserInfo { - val response = API.getUser(username).execute() - - response.throwOnFailure() + val response = API.getUser(username).execute().throwOnFailure() return response.body()!!.user.toDomainEntity() } @@ -655,9 +611,7 @@ open class RESTMusicService( override fun getChatMessages( since: Long? ): List { - val response = API.getChatMessages(since).execute() - - response.throwOnFailure() + val response = API.getChatMessages(since).execute().throwOnFailure() return response.body()!!.chatMessages.toDomainEntitiesList() } @@ -671,8 +625,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun getBookmarks(): List { - val response = API.getBookmarks().execute() - response.throwOnFailure() + val response = API.getBookmarks().execute().throwOnFailure() return response.body()!!.bookmarkList.toDomainEntitiesList() } @@ -696,8 +649,7 @@ open class RESTMusicService( override fun getVideos( refresh: Boolean ): MusicDirectory { - val response = API.getVideos().execute() - response.throwOnFailure() + val response = API.getVideos().execute().throwOnFailure() val musicDirectory = MusicDirectory() musicDirectory.addAll(response.body()!!.videosList.toDomainEntityList()) @@ -711,8 +663,7 @@ open class RESTMusicService( description: String?, expires: Long? ): List { - val response = API.createShare(ids, description, expires).execute() - response.throwOnFailure() + val response = API.createShare(ids, description, expires).execute().throwOnFailure() return response.body()!!.shares.toDomainEntitiesList() } From 6ab0ff973aafc847489212e28de1115724c5796e Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 19:33:17 +0200 Subject: [PATCH 04/16] Fix version checking for freshly added servers --- .../api/subsonic/ApiVersionCheckWrapper.kt | 10 ++++++++-- .../api/subsonic/SubsonicAPIClient.kt | 8 +++++--- .../subsonic/SubsonicClientConfiguration.kt | 3 ++- .../moire/ultrasonic/di/MusicServiceModule.kt | 18 ++++++++++-------- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt index 20923b0b..a30623aa 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt @@ -45,7 +45,8 @@ import retrofit2.Call @Suppress("TooManyFunctions") internal class ApiVersionCheckWrapper( val api: SubsonicAPIDefinition, - var currentApiVersion: SubsonicAPIVersions + var currentApiVersion: SubsonicAPIVersions?, + var isRealProtocolVersion: Boolean = false ) : SubsonicAPIDefinition by api { override fun getArtists(musicFolderId: String?): Call { checkVersion(V1_8_0) @@ -325,10 +326,15 @@ internal class ApiVersionCheckWrapper( } private fun checkVersion(expectedVersion: SubsonicAPIVersions) { - if (currentApiVersion < expectedVersion) throw ApiNotSupportedException(currentApiVersion) + // If it is true, it is probably the first call with this server + if (!isRealProtocolVersion) return + if (currentApiVersion!! < expectedVersion) + throw ApiNotSupportedException(currentApiVersion!!) } private fun checkParamVersion(param: Any?, expectedVersion: SubsonicAPIVersions) { + // If it is true, it is probably the first call with this server + if (!isRealProtocolVersion) return if (param != null) { checkVersion(expectedVersion) } diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index e51085a9..c50cc353 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -57,6 +57,7 @@ class SubsonicAPIClient( field = value proxyPasswordInterceptor.apiVersion = field wrappedApi.currentApiVersion = field + wrappedApi.isRealProtocolVersion = true versionInterceptor.protocolVersion = field onProtocolChange(field) } @@ -88,8 +89,8 @@ class SubsonicAPIClient( .addConverterFactory( VersionAwareJacksonConverterFactory.create( { - // Only trigger update on change - if (protocolVersion != it) { + // Only trigger update on change, or if still using the default + if (protocolVersion != it || !config.isRealProtocolVersion) { protocolVersion = it } }, @@ -100,7 +101,8 @@ class SubsonicAPIClient( private val wrappedApi = ApiVersionCheckWrapper( retrofit.create(SubsonicAPIDefinition::class.java), - config.minimalProtocolVersion + config.minimalProtocolVersion, + config.isRealProtocolVersion ) val api: SubsonicAPIDefinition get() = wrappedApi diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt index 732efe7a..ec0655ec 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicClientConfiguration.kt @@ -11,5 +11,6 @@ data class SubsonicClientConfiguration( val clientID: String, val allowSelfSignedCertificate: Boolean = false, val enableLdapUserSupport: Boolean = false, - val debug: Boolean = false + val debug: Boolean = false, + val isRealProtocolVersion: Boolean = false ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index fb26fa50..c6d18ee5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -49,19 +49,21 @@ val musicServiceModule = module { } single { + val server = get().getActiveServer() + return@single SubsonicClientConfiguration( - baseUrl = get().getActiveServer().url, - username = get().getActiveServer().userName, - password = get().getActiveServer().password, + baseUrl = server.url, + username = server.userName, + password = server.password, minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion( - get().getActiveServer().minimumApiVersion + server.minimumApiVersion ?: Constants.REST_PROTOCOL_VERSION ), clientID = Constants.REST_CLIENT_ID, - allowSelfSignedCertificate = get() - .getActiveServer().allowSelfSignedCertificate, - enableLdapUserSupport = get().getActiveServer().ldapSupport, - debug = BuildConfig.DEBUG + allowSelfSignedCertificate = server.allowSelfSignedCertificate, + enableLdapUserSupport = server.ldapSupport, + debug = BuildConfig.DEBUG, + isRealProtocolVersion = server.minimumApiVersion != null ) } From 619f44179040fcd71a48b3db470729e4750ac92a Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 20:23:22 +0200 Subject: [PATCH 05/16] Fix tests --- .../moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt index fbf8deb7..bbb448bf 100644 --- a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt +++ b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt @@ -14,7 +14,7 @@ import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE */ class ApiVersionCheckWrapperTest { private val apiMock = mock() - private val wrapper = ApiVersionCheckWrapper(apiMock, V1_1_0) + private val wrapper = ApiVersionCheckWrapper(apiMock, V1_1_0, isRealProtocolVersion = true) @Test fun `Should just call real api for ping`() { From 24ae0d9e8171df5c33d615a07bcb427f44919f55 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 9 Jun 2021 20:24:22 +0200 Subject: [PATCH 06/16] Catch all exceptions in GenericListModel This is how it used to be, before i changed it because detekt was complaining. --- .../org/moire/ultrasonic/fragment/GenericListModel.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt index bf377eed..468802a2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt @@ -10,8 +10,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import java.net.ConnectException -import java.net.UnknownHostException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -89,10 +87,8 @@ open class GenericListModel(application: Application) : try { load(isOffline, useId3Tags, musicService, refresh, bundle) - } catch (exception: ConnectException) { - handleException(exception, swipe.context) - } catch (exception: UnknownHostException) { - handleException(exception, swipe.context) + } catch (all: Exception) { + handleException(all, swipe.context) } } From 5e4c6cc627b1bc9c71aee28a948c8099c5dd1835 Mon Sep 17 00:00:00 2001 From: tzugen Date: Fri, 11 Jun 2021 10:24:21 +0200 Subject: [PATCH 07/16] I made currentApiVersion nullable here two commits ago while testing, but it doesn't need to be. Also update some comments --- .../moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt | 6 +++--- .../kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt index a30623aa..d2da7070 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt @@ -45,7 +45,7 @@ import retrofit2.Call @Suppress("TooManyFunctions") internal class ApiVersionCheckWrapper( val api: SubsonicAPIDefinition, - var currentApiVersion: SubsonicAPIVersions?, + var currentApiVersion: SubsonicAPIVersions, var isRealProtocolVersion: Boolean = false ) : SubsonicAPIDefinition by api { override fun getArtists(musicFolderId: String?): Call { @@ -328,8 +328,8 @@ internal class ApiVersionCheckWrapper( private fun checkVersion(expectedVersion: SubsonicAPIVersions) { // If it is true, it is probably the first call with this server if (!isRealProtocolVersion) return - if (currentApiVersion!! < expectedVersion) - throw ApiNotSupportedException(currentApiVersion!!) + if (currentApiVersion < expectedVersion) + throw ApiNotSupportedException(currentApiVersion) } private fun checkParamVersion(param: Any?, expectedVersion: SubsonicAPIVersions) { diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt index 61b310bc..666dbcf1 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt @@ -36,7 +36,7 @@ fun Response.toStreamResponse(): StreamResponse { } /** - * This call wraps Subsonic API calls so their results can be checked for errors, API version, etc + * This extension checks API call results for errors, API version, etc * It creates Exceptions from the results returned by the Subsonic API */ @Suppress("ThrowsCount") @@ -58,6 +58,10 @@ fun Response.throwOnFailure(): Response { } } +/** + * This extension checks API call results for errors, API version, etc + * @return Boolean: True if everything was ok, false if an error was found + */ fun Response.falseOnFailure(): Boolean { return (this.isSuccessful && this.body()!!.status === SubsonicResponse.Status.OK) } From a2324eabf27143e8a1f1349d5ffd5a4b746c1dbe Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 10 Jun 2021 13:42:33 +0200 Subject: [PATCH 08/16] Use ID3 tags by default for new users. This is what most people are used to from iTunes or Spotify.. --- ultrasonic/src/main/res/xml/settings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml index ce724706..bb25965a 100644 --- a/ultrasonic/src/main/res/xml/settings.xml +++ b/ultrasonic/src/main/res/xml/settings.xml @@ -70,13 +70,13 @@ a:key="playbackControlSettings" app:iconSpaceReserved="false"> Date: Thu, 10 Jun 2021 13:43:28 +0200 Subject: [PATCH 09/16] Add an option to load the demo database for new users --- .../ultrasonic/activity/NavigationActivity.kt | 38 ++++++---- .../di/AppPermanentStorageModule.kt | 4 +- .../fragment/ServerSettingsModel.kt | 69 ++++++++++++++----- ultrasonic/src/main/res/values-cs/strings.xml | 1 - ultrasonic/src/main/res/values-de/strings.xml | 1 - ultrasonic/src/main/res/values-es/strings.xml | 1 - ultrasonic/src/main/res/values-fr/strings.xml | 1 - ultrasonic/src/main/res/values-hu/strings.xml | 1 - ultrasonic/src/main/res/values-nl/strings.xml | 1 - ultrasonic/src/main/res/values-pl/strings.xml | 1 - .../src/main/res/values-pt-rBR/strings.xml | 1 - ultrasonic/src/main/res/values-pt/strings.xml | 1 - ultrasonic/src/main/res/values/strings.xml | 10 +-- 13 files changed, 86 insertions(+), 44 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index 9f029926..dde04b63 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -29,7 +29,6 @@ import androidx.preference.PreferenceManager import com.google.android.material.navigation.NavigationView import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.java.KoinJavaComponent.inject import org.moire.ultrasonic.R import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline @@ -151,7 +150,12 @@ class NavigationActivity : AppCompatActivity() { showWelcomeScreen = showWelcomeScreen and !areServersMigrated loadSettings() - showInfoDialog(showWelcomeScreen) + + // This is a first run with only the demo entry inside the database + // We set the active server to the demo one and show the welcome dialog + if (showWelcomeScreen) { + showWelcomeDialog() + } nowPlayingEventListener = object : NowPlayingEventListener { override fun onDismissNowPlaying() { @@ -313,19 +317,27 @@ class NavigationActivity : AppCompatActivity() { finish() } - private fun showInfoDialog(show: Boolean) { + private fun showWelcomeDialog() { if (!infoDialogDisplayed) { infoDialogDisplayed = true - if (show) { - AlertDialog.Builder(this) - .setIcon(android.R.drawable.ic_dialog_info) - .setTitle(R.string.main_welcome_title) - .setMessage(R.string.main_welcome_text) - .setPositiveButton(R.string.common_ok) { dialog, _ -> - dialog.dismiss() - findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment) - }.show() - } + + AlertDialog.Builder(this) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.main_welcome_title) + .setMessage(R.string.main_welcome_text_demo) + .setNegativeButton(R.string.main_welcome_cancel) { dialog, _ -> + // Got to the settings screen + dialog.dismiss() + findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment) + } + .setPositiveButton(R.string.common_ok) { dialog, _ -> + // Add the demo server + val activeServerProvider: ActiveServerProvider by inject() + val demoIndex = serverSettingsModel.addDemoServer() + activeServerProvider.setActiveServerByIndex(demoIndex) + findNavController(R.id.nav_host_fragment).navigate(R.id.mainFragment) + dialog.dismiss() + }.show() } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt index 1b430ae1..0758ade8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt @@ -12,6 +12,7 @@ import org.moire.ultrasonic.fragment.ServerSettingsModel import org.moire.ultrasonic.util.Util const val SP_NAME = "Default_SP" +const val DB_FILENAME = "ultrasonic-database" /** * This Koin module contains registration of classes related to permanent storage @@ -23,11 +24,10 @@ val appPermanentStorage = module { Room.databaseBuilder( androidContext(), AppDatabase::class.java, - "ultrasonic-database" + DB_FILENAME ) .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) - .fallbackToDestructiveMigrationOnDowngrade() .build() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt index af33133c..ad8499bf 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt @@ -11,6 +11,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import org.moire.ultrasonic.R +import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.data.ServerSettingDao @@ -25,20 +27,6 @@ class ServerSettingsModel( application: Application ) : AndroidViewModel(application) { - companion object { - private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated" - // These constants were removed from Constants.java as they are deprecated and only used here - private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled" - private const val PREFERENCES_KEY_SERVER_NAME = "serverName" - private const val PREFERENCES_KEY_SERVER_URL = "serverUrl" - private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers" - private const val PREFERENCES_KEY_USERNAME = "username" - private const val PREFERENCES_KEY_PASSWORD = "password" - private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate" - private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport" - private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId" - } - private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) /** @@ -67,8 +55,8 @@ class ServerSettingsModel( repository.insert(newServerSetting) index++ Timber.i( - "Imported server from Preferences to Database:" + - " ${newServerSetting.name}" + "Imported server from Preferences to Database: %s", + newServerSetting.name ) } } @@ -187,6 +175,23 @@ class ServerSettingsModel( } } + /** + * Inserts a new Setting into the database + * @return The id of the demo server + */ + fun addDemoServer(): Int { + val demo = DEMO_SERVER_CONFIG.copy() + + runBlocking { + demo.index = (repository.count() ?: 0) + 1 + demo.id = (repository.getMaxId() ?: 0) + 1 + repository.insert(demo) + Timber.d("Added demo server") + } + + return demo.id + } + /** * Reads up a Server Setting stored in the obsolete Preferences */ @@ -262,4 +267,36 @@ class ServerSettingsModel( editor.putBoolean(PREFERENCES_KEY_SERVER_MIGRATED + preferenceId, true) editor.apply() } + + companion object { + private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated" + // These constants were removed from Constants.java as they are deprecated and only used here + private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled" + private const val PREFERENCES_KEY_SERVER_NAME = "serverName" + private const val PREFERENCES_KEY_SERVER_URL = "serverUrl" + private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers" + private const val PREFERENCES_KEY_USERNAME = "username" + private const val PREFERENCES_KEY_PASSWORD = "password" + private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate" + private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport" + private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId" + + private val DEMO_SERVER_CONFIG = ServerSetting( + id = 0, + index = 0, + name = UApp.applicationContext().getString(R.string.server_menu_demo), + url = "https://demo.ampache.dev", + userName = "ultrasonic_demo", + password = "W7DumQ3ZUR89Se3", + jukeboxByDefault = false, + allowSelfSignedCertificate = false, + ldapSupport = false, + musicFolderId = null, + minimumApiVersion = "1.13.0", + chatSupport = true, + bookmarkSupport = true, + shareSupport = true, + podcastSupport = true + ) + } } diff --git a/ultrasonic/src/main/res/values-cs/strings.xml b/ultrasonic/src/main/res/values-cs/strings.xml index 55ba92ac..cf782340 100644 --- a/ultrasonic/src/main/res/values-cs/strings.xml +++ b/ultrasonic/src/main/res/values-cs/strings.xml @@ -101,7 +101,6 @@ Označené hvězdičkou Skladby Videa - Vítejte v aplikaci Ultrasonic! Aplikace není ještě nakonfigurována. Poté co nastavíte svůj osobní server (dostupný na subsonic.org), vyberte Správa serverů v Nastavení a připojte aplikaci. Vítejte! O aplikaci Další diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index aab075fe..cd69a241 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -100,7 +100,6 @@ Mit Stern Titel Filme - Willkommen bei Ultrasonic! Die App ist zurzeit unkonfiguriert. Nachdem du deinen Server konfiguriert hast (siehe subsonic.org), bitte Server hinzufügen in den Einstellungen klicken um die Verbindun herzustellen. Willkommen Über Allgemein diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 35192166..14d90c1c 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -111,7 +111,6 @@ Me gusta Canciones Vídeos - Te damos la bienvenida a Ultrasonic! La aplicación no está configurada actualmente. Después de que hayas configurado tu servidor personal (disponible desde subsonic.org), por favor haz click en Administrar servidores en Configuración para conectarte con él. ¡Saludos! Acerca de Común diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 5bc3b7b7..94a63970 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -101,7 +101,6 @@ Favoris Titres Vidéos - Bienvenue dans Ultrasonic ! L\'application n\'est pas configurée. Après avoir configuré votre serveur personnel (disponible à partir de subsonic.org), veuillez accéder aux Paramètres et modifier la configuration pour vous y connecter. Bienvenue ! À propos Général diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 5476f2c3..41490785 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -111,7 +111,6 @@ Csillaggal megjelölt Dalok Videók - Üdvözli az Ultrasonic! Az alkalmazás még nincs beállítva. Miután konfigurálta saját kiszolgálóját (elérhető: subsonic.org), húzza balról jobbra az oldalsávot, lépjen be a Beállítások menüpontba, és adja meg csatlakozási adatokat! Üdvözlet! Névjegy Általános diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml index 9cd9fd00..8d5ee1a3 100644 --- a/ultrasonic/src/main/res/values-nl/strings.xml +++ b/ultrasonic/src/main/res/values-nl/strings.xml @@ -111,7 +111,6 @@ Favorieten Nummers Video\'s - Welkom bij Ultrasonic! De app is nog niet ingesteld. Nadat je je persoonlijke server hebt opgezet (beschikbaar op subsonic.org), kun je naar de Instellingen gaan en drukken op Server toevoegen. Welkom! Over Algemeen diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml index 4f7b15d1..1eee490b 100644 --- a/ultrasonic/src/main/res/values-pl/strings.xml +++ b/ultrasonic/src/main/res/values-pl/strings.xml @@ -100,7 +100,6 @@ Ulubione Utwory Klipy wideo - Witaj w Ultrasonic! Obecnie aplikacja nie jest skonfigurowana. Jeśli masz uruchomiony własny serwer (dostępny na subsonic.org), proszę wybrać Dodaj serwer w Ustawieniach aby się z nim połączyć. Witaj! O aplikacji Wspólne diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 903d7b6a..e4a8bdbf 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -101,7 +101,6 @@ Favoritas Músicas Vídeos - Bem-vindo ao Ultrasonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em subsonic.org), clique em Adicionar Servidor em Configurações para a conexão. Bem-vindo! Sobre Comum diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 8fb5bb12..f4919701 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -100,7 +100,6 @@ Favoritas Músicas Vídeos - Bem-vindo ao Ultrasonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em subsonic.org), clique em Adicionar Servidor em Configurações para a conexão. Bem-vindo! Sobre Comum diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 62556828..1e82cee4 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - + Loading… A network error occurred. Please check the server address or try again later. @@ -111,8 +111,9 @@ Starred Songs Videos - Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from subsonic.org), please click Manage Servers in Settings to connect to it. - Welcome! + To use Ultrasonic with your own music you will need your own server: \n\n➤ In case you want to try out the app first, it can add a demo server now. \n\n➤ Otherwise you can configure your server it in the settings. + Welcome to Ultrasonic! + Take me to the settings About Common Deleted playlist %s @@ -348,7 +349,7 @@ 0.00 GB 0 KB 0.00 MB - -:-- + -:-- 0:00 MX Player is not installed. Get it for free on Play Store, or change video settings. Get MX Player @@ -449,6 +450,7 @@ Authentication Advanced settings One or more features were disabled because the server doesn\'t support them.\nYou can run this test again anytime. + Demo Server 1 song From db31fefe8390af6fa799c959b1d31413a40a98ab Mon Sep 17 00:00:00 2001 From: tzugen Date: Fri, 11 Jun 2021 10:42:40 +0200 Subject: [PATCH 10/16] Remove flash support --- detekt-baseline.xml | 1 - .../ultrasonic/fragment/SettingsFragment.java | 3 - .../org/moire/ultrasonic/util/Constants.java | 1 - .../java/org/moire/ultrasonic/util/Util.java | 55 +++---- .../ultrasonic/util/VideoPlayerType.java | 139 ------------------ .../ultrasonic/service/CachedMusicService.kt | 4 +- .../moire/ultrasonic/service/MusicService.kt | 2 +- .../ultrasonic/service/OfflineMusicService.kt | 2 +- .../ultrasonic/service/RESTMusicService.kt | 3 +- .../moire/ultrasonic/subsonic/VideoPlayer.kt | 16 +- .../org/moire/ultrasonic/view/SongView.kt | 4 +- ultrasonic/src/main/res/values-cs/strings.xml | 5 +- ultrasonic/src/main/res/values-de/strings.xml | 5 +- ultrasonic/src/main/res/values-es/strings.xml | 5 +- ultrasonic/src/main/res/values-fr/strings.xml | 5 +- ultrasonic/src/main/res/values-hu/strings.xml | 5 +- ultrasonic/src/main/res/values-it/strings.xml | 4 +- ultrasonic/src/main/res/values-nl/strings.xml | 5 +- ultrasonic/src/main/res/values-pl/strings.xml | 5 +- .../src/main/res/values-pt-rBR/strings.xml | 5 +- ultrasonic/src/main/res/values-pt/strings.xml | 5 +- ultrasonic/src/main/res/values-ru/strings.xml | 5 +- .../src/main/res/values-zh-rCN/strings.xml | 4 +- ultrasonic/src/main/res/values/arrays.xml | 10 -- ultrasonic/src/main/res/values/strings.xml | 5 +- ultrasonic/src/main/res/xml/settings.xml | 11 -- 26 files changed, 50 insertions(+), 264 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java diff --git a/detekt-baseline.xml b/detekt-baseline.xml index e1cb4ed7..856dc92d 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -5,7 +5,6 @@ ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt" ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED ) - ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File) ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index 66da521d..5a6cfdd8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -55,7 +55,6 @@ public class SettingsFragment extends PreferenceFragmentCompat private Preference addServerPreference; private ListPreference theme; - private ListPreference videoPlayer; private ListPreference maxBitrateWifi; private ListPreference maxBitrateMobile; private ListPreference cacheSize; @@ -110,7 +109,6 @@ public class SettingsFragment extends PreferenceFragmentCompat addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVERS_EDIT); theme = findPreference(Constants.PREFERENCES_KEY_THEME); - videoPlayer = findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER); maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); @@ -411,7 +409,6 @@ public class SettingsFragment extends PreferenceFragmentCompat private void update() { theme.setSummary(theme.getEntry()); - videoPlayer.setSummary(videoPlayer.getEntry()); maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); maxBitrateMobile.setSummary(maxBitrateMobile.getEntry()); cacheSize.setSummary(cacheSize.getEntry()); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index 14806a76..880564d8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -117,7 +117,6 @@ public final class Constants public static final String PREFERENCES_KEY_CLEAR_PLAYLIST = "clearPlaylist"; public static final String PREFERENCES_KEY_CLEAR_BOOKMARK = "clearBookmark"; public static final String PREFERENCES_KEY_DISC_SORT = "discAndTrackSort"; - public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer"; public static final String PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS = "sendBluetoothNotifications"; public static final String PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART = "sendBluetoothAlbumArt"; public static final String PREFERENCES_KEY_VIEW_REFRESH = "viewRefresh"; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index 82366ed6..e93d7cc4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -21,8 +21,9 @@ package org.moire.ultrasonic.util; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; -import android.content.*; -import android.content.pm.ApplicationInfo; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; @@ -39,7 +40,6 @@ import android.os.Build; import android.os.Environment; import android.os.Parcelable; import android.util.DisplayMetrics; -import timber.log.Timber; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -51,19 +51,32 @@ import androidx.preference.PreferenceManager; import org.moire.ultrasonic.R; import org.moire.ultrasonic.app.UApp; import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.*; +import org.moire.ultrasonic.domain.Bookmark; +import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory.Entry; +import org.moire.ultrasonic.domain.PlayerState; +import org.moire.ultrasonic.domain.RepeatMode; +import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.MediaPlayerService; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.text.DecimalFormat; -import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import timber.log.Timber; + /** * @author Sindre Mehus * @version $Id$ @@ -1148,36 +1161,6 @@ public class Util else return minutes > 0 ? String.format(Locale.getDefault(), "%d:%02d", minutes, seconds) : String.format(Locale.getDefault(), "0:%02d", seconds); } - public static VideoPlayerType getVideoPlayerType() - { - SharedPreferences preferences = getPreferences(); - return VideoPlayerType.forKey(preferences.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, VideoPlayerType.MX.getKey())); - } - - public static boolean isPackageInstalled(Context context, String packageName) - { - PackageManager pm = context.getPackageManager(); - List packages = null; - - if (pm != null) - { - packages = pm.getInstalledApplications(0); - } - - if (packages != null) - { - for (ApplicationInfo packageInfo : packages) - { - if (packageInfo.packageName.equals(packageName)) - { - return true; - } - } - } - - return false; - } - public static String getVersionName(Context context) { String versionName = null; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java deleted file mode 100644 index ff005e73..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java +++ /dev/null @@ -1,139 +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 2013 (C) Sindre Mehus - */ -package org.moire.ultrasonic.util; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.service.MusicServiceFactory; - -/** - * @author Sindre Mehus - * @version $Id: VideoPlayerType.java 3473 2013-05-23 16:42:49Z sindre_mehus $ - */ -public enum VideoPlayerType -{ - - MX("mx") - { - @Override - public void playVideo(final Context context, MusicDirectory.Entry entry) throws Exception - { - - // Check if MX Player is installed. - boolean installedAd = Util.isPackageInstalled(context, PACKAGE_NAME_MX_AD); - boolean installedPro = Util.isPackageInstalled(context, PACKAGE_NAME_MX_PRO); - - if (!installedAd && !installedPro) - { - new AlertDialog.Builder(context).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int i) - { - try - { - context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD)))); - } - catch (android.content.ActivityNotFoundException x) - { - context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD)))); - } - - dialog.dismiss(); - } - }).setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int i) - { - dialog.dismiss(); - } - }).show(); - - } - else - { - // See documentation on https://sites.google.com/site/mxvpen/api - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setPackage(installedPro ? PACKAGE_NAME_MX_PRO : PACKAGE_NAME_MX_AD); - intent.putExtra("title", entry.getTitle()); - intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), false)), "video/*"); - context.startActivity(intent); - } - } - }, - - FLASH("flash") - { - @Override - public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception - { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), true))); - context.startActivity(intent); - } - }, - - DEFAULT("default") - { - @Override - public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception - { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), false)), "video/*"); - context.startActivity(intent); - } - }; - - private final String key; - - VideoPlayerType(String key) - { - this.key = key; - } - - public String getKey() - { - return key; - } - - public static VideoPlayerType forKey(String key) - { - for (VideoPlayerType type : VideoPlayerType.values()) - { - if (type.key.equals(key)) - { - return type; - } - } - return null; - } - - public abstract void playVideo(Context context, MusicDirectory.Entry entry) throws Exception; - - private static final String PACKAGE_NAME_MX_AD = "com.mxtech.videoplayer.ad"; - private static final String PACKAGE_NAME_MX_PRO = "com.mxtech.videoplayer.pro"; - -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt index 30e5ff10..6e329d39 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -264,8 +264,8 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, } @Throws(Exception::class) - override fun getVideoUrl(id: String, useFlash: Boolean): String? { - return musicService.getVideoUrl(id, useFlash) + override fun getVideoUrl(id: String): String? { + return musicService.getVideoUrl(id) } @Throws(Exception::class) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt index b9e5f5f3..cce41209 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt @@ -123,7 +123,7 @@ interface MusicService { // TODO: Refactor and remove this call (see RestMusicService implementation) @Throws(Exception::class) - fun getVideoUrl(id: String, useFlash: Boolean): String? + fun getVideoUrl(id: String): String? @Throws(Exception::class) fun updateJukeboxPlaylist(ids: List?): JukeboxStatus diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index a4ad2ca9..f8519561 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -396,7 +396,7 @@ class OfflineMusicService : MusicService, KoinComponent { } @Throws(OfflineException::class) - override fun getVideoUrl(id: String, useFlash: Boolean): String? { + override fun getVideoUrl(id: String): String? { throw OfflineException("getVideoUrl isn't available in offline mode") } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 684dc84b..6d3a531c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -481,8 +481,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun getVideoUrl( - id: String, - useFlash: Boolean + id: String ): String { // TODO This method should not exists as video should be loaded using stream method // Previous method implementation uses assumption that video will be available diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt index fb03a627..152aa89a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt @@ -1,22 +1,30 @@ package org.moire.ultrasonic.subsonic import android.content.Context +import android.content.Intent +import android.net.Uri import org.moire.ultrasonic.R import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.util.Util /** * This utility class helps starting video playback */ -class VideoPlayer() { +class VideoPlayer { fun playVideo(context: Context, entry: MusicDirectory.Entry?) { - if (!Util.isNetworkConnected()) { + if (!Util.isNetworkConnected() || entry == null) { Util.toast(context, R.string.select_album_no_network) return } - val player = Util.getVideoPlayerType() try { - player.playVideo(context, entry) + val intent = Intent(Intent.ACTION_VIEW) + val url = MusicServiceFactory.getMusicService().getVideoUrl(entry.id) + intent.setDataAndType( + Uri.parse(url), + "video/*" + ) + context.startActivity(intent) } catch (e: Exception) { Util.toast(context, e.toString(), false) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt index 3a4638c7..54fcf3a1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt @@ -36,7 +36,6 @@ import org.moire.ultrasonic.service.DownloadFile import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.util.Util -import org.moire.ultrasonic.util.VideoPlayerType import org.moire.ultrasonic.view.EntryAdapter.SongViewHolder import timber.log.Timber @@ -111,8 +110,7 @@ class SongView(context: Context) : UpdateView(context), Checkable, KoinComponent val transcodedSuffix = song.transcodedSuffix fileFormat = if ( - TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || - song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH + TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo ) suffix else String.format("%s > %s", suffix, transcodedSuffix) val artistName = song.artist diff --git a/ultrasonic/src/main/res/values-cs/strings.xml b/ultrasonic/src/main/res/values-cs/strings.xml index 55ba92ac..12d0b6ae 100644 --- a/ultrasonic/src/main/res/values-cs/strings.xml +++ b/ultrasonic/src/main/res/values-cs/strings.xml @@ -311,7 +311,7 @@ Očekává jména hlavních adresářů obsahující jména umělců Procházet za použití ID3 tagů Používat metodu ID3 tagů místo jmen na základě adresářové struktury - Video + Video Videopřehrávač Obnovení náhledu .5 sekundy @@ -371,9 +371,6 @@ Výchozí pozdrav sdílení Mrkni na hudbu sdílenou z %s Sdílet skladby přes - MX Player - Výchozí - Flash Sdílení Všechny skladby od %s Zobrazit všechny skladby umělce diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index aab075fe..1e522845 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -308,7 +308,7 @@ Annehmen, dass der Ordner der obersten Ebene der Name des Albumkünstlers ist Durchsuchen von ID3-Tags Nutze ID3 Tag Methode anstatt Dateisystem-Methode - Film + Film Filmwiedergabe Aktualisierungsinterval .5 Sekunden @@ -368,9 +368,6 @@ Standard Begrüßung beim Teilen Hör dir mal die Musik an, die ich mit dir über %s geteilt habe. Titel teilen über - MX Player - Standard - Flash Freigabe Alle Titel von %s Alle Titel nach Künstler sortieren diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 35192166..3d3fcc1a 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -325,7 +325,7 @@ Usar el método de etiquetas ID3 en lugar del método basado en el sistema de ficheros Mostrar la imagen del artista en la lista de artistas Muestra la imagen del artista en la lista de artistas si está disponible - Vídeo + Vídeo Reproductor de vídeo Refresco de la vista .5 segundos @@ -385,9 +385,6 @@ Saludo predeterminado para los compartidos Echa un vistazo a esta música que te comparto desde %s Compartir canciones vía - MX Player - Por defecto - Flash Compartir Todas las canciones por %s Mostrar todas las canciones por artista diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 5bc3b7b7..ba9fdc91 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -313,7 +313,7 @@ Utiliser ID3 Tags à la place du système de fichier basique Afficher l’image de l’artiste dans la liste Affiche l’image de l’artiste dans la liste des artistes si celle-ci est disponible - Vidéo + Vidéo Lecteur vidéo Actualisation de la vue 0,5 secondes @@ -373,9 +373,6 @@ Texte par défaut lors d\'un partage Regardez cette musique que j\'ai partagée depuis %s Partager des titres via - MX Player - Défaut - Flash Partager Tous les titres de %s Voir tous les titres par artiste diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 5476f2c3..4493ba59 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -325,7 +325,7 @@ ID3 Tag módszer használata a fájlredszer alapú mód helyett. Előadó képének megjelenítése Az előadó listában megjeleníti a képeket, amennyiben elérhetőek - Videó + Videó Videólejátszó Nézet frissítési gyakorisága .5 másodperc @@ -385,9 +385,6 @@ Alapértelmezett megosztási üzenet Hallgasd meg ezt a zenét, megosztottam innen: %s Dalok megosztása ezzel - MX Player - Alapértelmezett - Flash Megosztás %s minden dala Az előadó összes dalának megjelenítése diff --git a/ultrasonic/src/main/res/values-it/strings.xml b/ultrasonic/src/main/res/values-it/strings.xml index f7031d63..c2820f9b 100644 --- a/ultrasonic/src/main/res/values-it/strings.xml +++ b/ultrasonic/src/main/res/values-it/strings.xml @@ -299,7 +299,7 @@ Presumi che la cartella superiore sia il nome dell\'artista dell\'album Sfoglia Utilizzando Tag ID3 Usa metodi tag ID3 invece dei metodi basati sul filesystem - Video + Video Riproduttore video .5 secondo 1 secondo @@ -336,8 +336,6 @@ Commenta \"%s\" è stato rimosso dalla playlist Condividi canzoni via - MX Player - Predefinito 1 canzone %d canzoni diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml index 9cd9fd00..337185ee 100644 --- a/ultrasonic/src/main/res/values-nl/strings.xml +++ b/ultrasonic/src/main/res/values-nl/strings.xml @@ -325,7 +325,7 @@ ID3-labels gebruiken in plaats van systeemlabels Artiestfoto tonen op artiestenlijst Toont de artiestfoto op de artiestenlijst (indien beschikbaar) - Video + Video Videospeler Verversen 0,5 seconden @@ -385,9 +385,6 @@ Standaard deelbericht Hé, luister eens naar de muziek die ik heb gedeeld via %s Nummers delen via - MX Player - Standaard - Flash Delen Alle nummers van %s Alle nummers van artiest tonen diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml index 4f7b15d1..4e84645d 100644 --- a/ultrasonic/src/main/res/values-pl/strings.xml +++ b/ultrasonic/src/main/res/values-pl/strings.xml @@ -308,7 +308,7 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników Zakłada, że folder najwyższego poziomu jest nazwą artysty albumu Przeglądaj używając tagów ID3 Używa metod z tagów ID3 zamiast metod opartych na systemie plików - Wideo + Wideo Odtwarzacz wideo Odświeżanie widoku co pół sekundy @@ -368,9 +368,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników Domyślny tekst podczas udostępniania Sprawdź muzykę, którą udostępniam na %s Udostępnij utwory za pomocą - MX Player - Domyślny - Flash Udostępnianie Wszystkie utwory %s Wyświetlaj wszystkie utwory artysty diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 903d7b6a..4127bfdd 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -313,7 +313,7 @@ Usar as etiquetas ID3 ao invés do sistema de arquivos Mostrar Foto do Artista na Lista Mostrar a imagem do artista na lista de artistas, se disponível - Vídeo + Vídeo Player de Vídeo Atualização da Tela .5 segundos @@ -373,9 +373,6 @@ Saudação Padrão do Compartilhamento Confira esta música que compartilhei do %s Compartilhar músicas via - MX Player - Padrão - Flash Compartilhar Todas as Músicas de %s Mostrar Todas as Músicas por Artista diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 8fb5bb12..9525264a 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -308,7 +308,7 @@ Assumir que a pasta mais acima é o nome do artista Navegar Usando Etiquetas ID3 Usa as etiquetas ID3 ao invés do sistema de ficheiros - Vídeo + Vídeo Player de Vídeo Atualização do Ecrã .5 segundos @@ -368,9 +368,6 @@ Saudação Padrão Confira esta música que compartilhei do %s Compartilhar músicas via - MX Player - Padrão - Flash Compartilhar Todas as Músicas de %s Todas as Músicas do Artista diff --git a/ultrasonic/src/main/res/values-ru/strings.xml b/ultrasonic/src/main/res/values-ru/strings.xml index 0897075f..a1a8edbc 100644 --- a/ultrasonic/src/main/res/values-ru/strings.xml +++ b/ultrasonic/src/main/res/values-ru/strings.xml @@ -300,7 +300,7 @@ Предположим, папка верхнего уровня - это имя исполнителя альбома Обзор с использованием тегов ID3 Используйте методы тегов ID3 ​​вместо методов на основе файловой системы - Видео + Видео Видеоплеер Посмотреть Обновить .5 секунд @@ -360,9 +360,6 @@ Поделиться приветствием по умолчанию Проверьте эту музыку, с которой я поделился %s Поделиться треками через - MX Player - По умолчанию - Flash Поделиться Все треки %s Показать все треки исполнителя diff --git a/ultrasonic/src/main/res/values-zh-rCN/strings.xml b/ultrasonic/src/main/res/values-zh-rCN/strings.xml index 60067c12..42b39e4d 100644 --- a/ultrasonic/src/main/res/values-zh-rCN/strings.xml +++ b/ultrasonic/src/main/res/values-zh-rCN/strings.xml @@ -224,7 +224,7 @@ 连接正常, 服务器未授权。 主题 允许自签名 HTTPS 证书 - 视频 + 视频 视频播放器 刷新视图 .5 秒 @@ -261,8 +261,6 @@ 评论 %s已从播放列表中移除 分享播放列表 - MX Player - 默认 分享 已禁用 删除文件 diff --git a/ultrasonic/src/main/res/values/arrays.xml b/ultrasonic/src/main/res/values/arrays.xml index d18959e9..22b3cdff 100644 --- a/ultrasonic/src/main/res/values/arrays.xml +++ b/ultrasonic/src/main/res/values/arrays.xml @@ -224,16 +224,6 @@ @string/settings.search_250 @string/settings.search_500 - - mx - default - flash - - - @string/settings.video_mx_player - @string/settings.video_default - @string/settings.video_flash - @string/settings.view_refresh_500 @string/settings.view_refresh_1000 diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 62556828..07d54971 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -327,7 +327,7 @@ Use ID3 tag methods instead of file system based methods Show artist picture in artist list Displays the artist picture in the artist list if available - Video + Video Video player View Refresh .5 seconds @@ -387,9 +387,6 @@ Default Share Greeting Check out this music I shared from %s Share songs via - MX Player - Default - Flash Share All Songs by %s Show All Songs By Artist diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml index ce724706..c1da757a 100644 --- a/ultrasonic/src/main/res/xml/settings.xml +++ b/ultrasonic/src/main/res/xml/settings.xml @@ -180,17 +180,6 @@ a:title="@string/settings.send_bluetooth_album_art" app:iconSpaceReserved="false"/> - - - From a43a525babcf4d53af1fa2d54721d767db00935c Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 12 Jun 2021 15:11:32 +0200 Subject: [PATCH 11/16] Remove two unrelated, but unused files. --- .../GetPodcastEpisodesTestReaderProvider.java | 85 ------------------- .../service/GetPodcastTestReaderProvider.java | 41 --------- 2 files changed, 126 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java deleted file mode 100644 index 538ad595..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastEpisodesTestReaderProvider.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.moire.ultrasonic.Test.service; - -import java.io.Reader; -import java.io.StringReader; - -/** - * Created by rcocula on 11/03/2016. - */ -public class GetPodcastEpisodesTestReaderProvider { - - private static String data = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; - - - public static Reader getReader() { - - return new StringReader(data); - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java deleted file mode 100644 index 91d3f62d..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/Test/service/GetPodcastTestReaderProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.moire.ultrasonic.Test.service; - -import java.io.Reader; -import java.io.StringReader; - -/** - * Created by rcocula on 11/03/2016. - */ -public class GetPodcastTestReaderProvider { - - private static String data = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; - - - public static Reader getReader() { - - return new StringReader(data); - } -} From f7dfdcadad13df0ca5c5f8348ee5c388bdd3ca41 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 12 Jun 2021 19:48:36 +0200 Subject: [PATCH 12/16] Remove unused strings --- ultrasonic/src/main/res/values-cs/strings.xml | 3 --- ultrasonic/src/main/res/values-de/strings.xml | 3 --- ultrasonic/src/main/res/values-es/strings.xml | 3 --- ultrasonic/src/main/res/values-fr/strings.xml | 3 --- ultrasonic/src/main/res/values-hu/strings.xml | 3 --- ultrasonic/src/main/res/values-it/strings.xml | 3 --- ultrasonic/src/main/res/values-nl/strings.xml | 3 --- ultrasonic/src/main/res/values-pl/strings.xml | 3 --- ultrasonic/src/main/res/values-pt-rBR/strings.xml | 3 --- ultrasonic/src/main/res/values-pt/strings.xml | 3 --- ultrasonic/src/main/res/values-ru/strings.xml | 3 --- ultrasonic/src/main/res/values-zh-rCN/strings.xml | 1 - ultrasonic/src/main/res/values/strings.xml | 7 ++----- 13 files changed, 2 insertions(+), 39 deletions(-) diff --git a/ultrasonic/src/main/res/values-cs/strings.xml b/ultrasonic/src/main/res/values-cs/strings.xml index 12d0b6ae..c01d1597 100644 --- a/ultrasonic/src/main/res/values-cs/strings.xml +++ b/ultrasonic/src/main/res/values-cs/strings.xml @@ -312,7 +312,6 @@ Procházet za použití ID3 tagů Používat metodu ID3 tagů místo jmen na základě adresářové struktury Video - Videopřehrávač Obnovení náhledu .5 sekundy 1 sekunda @@ -334,8 +333,6 @@ 0.00 MB -:-- 0:00 - MX Player není nainstalován. Nainstalujte z Obchodu Play nebo změňte nastavení videí. - Stáhnout MX Player Ťuknutím vybrat hudbu SD karta nedostupná Chybí SD karta diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index 1e522845..055683f8 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -309,7 +309,6 @@ Durchsuchen von ID3-Tags Nutze ID3 Tag Methode anstatt Dateisystem-Methode Film - Filmwiedergabe Aktualisierungsinterval .5 Sekunden 1 Sekunde @@ -331,8 +330,6 @@ 0.00 MB -:-- 0:00 - MX Player ist nicht installiert. Holen Sie ihn sich kostenlos im Play Store, oder ändern Sie die Filmeinstellungen. - MX Player holen Berühren, um Musik auszuwählen SD Karte nicht verfügbar Keine SD Karte diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 3d3fcc1a..f0c8dc81 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -326,7 +326,6 @@ Mostrar la imagen del artista en la lista de artistas Muestra la imagen del artista en la lista de artistas si está disponible Vídeo - Reproductor de vídeo Refresco de la vista .5 segundos 1 segundo @@ -348,8 +347,6 @@ 0.00 MB -:-- 0:00 - El MX Player no esta instalado. Descárgalo grátis de la Play Store, o cambia la configuración de vídeo. - Obtener MX Player Toca para seleccionar música Tarjeta SD no disponible No hay tarjeta SD diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index ba9fdc91..4c23cd30 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -314,7 +314,6 @@ Afficher l’image de l’artiste dans la liste Affiche l’image de l’artiste dans la liste des artistes si celle-ci est disponible Vidéo - Lecteur vidéo Actualisation de la vue 0,5 secondes 1 seconde @@ -336,8 +335,6 @@ 0.00 Mo —:—— 0:00 - MX Player n\'est pas installé. Récupérez le gratuitement sur Play Store, ou modifier les paramètres vidéo. - Obtenez MX Player Touchez pour sélectionner un titre Carte SD non disponible Aucune carte SD diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 4493ba59..20bdd2d8 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -326,7 +326,6 @@ Előadó képének megjelenítése Az előadó listában megjeleníti a képeket, amennyiben elérhetőek Videó - Videólejátszó Nézet frissítési gyakorisága .5 másodperc 1 másodperc @@ -348,8 +347,6 @@ 0.00 MB -:-- 0:00 - Az MX Player nincs telepítve. Töltse le díjmentesen a Play Áruházból, vagy módosítsa a videó beállításait! - MX Player letöltése Érintse meg a zene kiválasztásához Az SD kártya nem elérhető! Nincs SD kártya! diff --git a/ultrasonic/src/main/res/values-it/strings.xml b/ultrasonic/src/main/res/values-it/strings.xml index c2820f9b..81e41ca5 100644 --- a/ultrasonic/src/main/res/values-it/strings.xml +++ b/ultrasonic/src/main/res/values-it/strings.xml @@ -300,7 +300,6 @@ Sfoglia Utilizzando Tag ID3 Usa metodi tag ID3 invece dei metodi basati sul filesystem Video - Riproduttore video .5 secondo 1 secondo 1.5 secondi @@ -319,8 +318,6 @@ 0.00 MB -:-- 0:00 - MX Player non è installato. Scaricalo gratuitamente dal Play Store, o cambia le impostazioni video. - Ottieni MX Player Tocca per selezionare musica Scheda SD non disponibile Nessuna scheda SD diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml index 337185ee..ae73cbbc 100644 --- a/ultrasonic/src/main/res/values-nl/strings.xml +++ b/ultrasonic/src/main/res/values-nl/strings.xml @@ -326,7 +326,6 @@ Artiestfoto tonen op artiestenlijst Toont de artiestfoto op de artiestenlijst (indien beschikbaar) Video - Videospeler Verversen 0,5 seconden 1 seconde @@ -348,8 +347,6 @@ 0,00 MB -:-- 0:00 - MX Player is niet geïnstalleerd. Installeer deze gratis via de Play Store of wijzig de video-instellingen. - MX Player installeren Druk om muziek te selecteren SD-kaart niet beschikbaar Geen SD-kaart diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml index 4e84645d..f2019e33 100644 --- a/ultrasonic/src/main/res/values-pl/strings.xml +++ b/ultrasonic/src/main/res/values-pl/strings.xml @@ -309,7 +309,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników Przeglądaj używając tagów ID3 Używa metod z tagów ID3 zamiast metod opartych na systemie plików Wideo - Odtwarzacz wideo Odświeżanie widoku co pół sekundy co 1 sekundę @@ -331,8 +330,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników 0.00 MB -:-- 0:00 - MX Player nie jest zainstalowany, Pobierz go za darmo w Sklepie Play, lub zmień ustawiena wideo. - Pobierz MX Player Dotknij, aby wybrać muzykę Karta SD jest niedostępna Brak karty SD diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 4127bfdd..062afd45 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -314,7 +314,6 @@ Mostrar Foto do Artista na Lista Mostrar a imagem do artista na lista de artistas, se disponível Vídeo - Player de Vídeo Atualização da Tela .5 segundos 1 segundo @@ -336,8 +335,6 @@ 0.00 MB -:-- 0:00 - O MX Player não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. - Baixar MX Player Toque para selecionar a música Cartão SD indisponível Sem cartão SD diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 9525264a..0fc87d74 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -309,7 +309,6 @@ Navegar Usando Etiquetas ID3 Usa as etiquetas ID3 ao invés do sistema de ficheiros Vídeo - Player de Vídeo Atualização do Ecrã .5 segundos 1 segundo @@ -331,8 +330,6 @@ 0.00 MB —:—— 0:00 - O MX Player não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. - Descarregar MX Player Toque para selecionar a música Cartão SD indisponível Sem cartão SD diff --git a/ultrasonic/src/main/res/values-ru/strings.xml b/ultrasonic/src/main/res/values-ru/strings.xml index a1a8edbc..a5e9962c 100644 --- a/ultrasonic/src/main/res/values-ru/strings.xml +++ b/ultrasonic/src/main/res/values-ru/strings.xml @@ -301,7 +301,6 @@ Обзор с использованием тегов ID3 Используйте методы тегов ID3 ​​вместо методов на основе файловой системы Видео - Видеоплеер Посмотреть Обновить .5 секунд 1 секунда @@ -323,8 +322,6 @@ 0.00 MB -:-- 0:00 - MX Player не установлен. Получите его бесплатно в магазине Play Store или измените настройки видео. - Получить MX Player Нажмите, чтобы выбрать музыку SD-карта недоступна Нет SD-карты diff --git a/ultrasonic/src/main/res/values-zh-rCN/strings.xml b/ultrasonic/src/main/res/values-zh-rCN/strings.xml index 42b39e4d..2cc97a7a 100644 --- a/ultrasonic/src/main/res/values-zh-rCN/strings.xml +++ b/ultrasonic/src/main/res/values-zh-rCN/strings.xml @@ -225,7 +225,6 @@ 主题 允许自签名 HTTPS 证书 视频 - 视频播放器 刷新视图 .5 秒 1 秒 diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 07d54971..298d7200 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - + Loading… A network error occurred. Please check the server address or try again later. @@ -327,8 +327,7 @@ Use ID3 tag methods instead of file system based methods Show artist picture in artist list Displays the artist picture in the artist list if available - Video - Video player + Video View Refresh .5 seconds 1 second @@ -350,8 +349,6 @@ 0.00 MB -:-- 0:00 - MX Player is not installed. Get it for free on Play Store, or change video settings. - Get MX Player Touch to select music SD card unavailable No SD card From 58200660d3748475eb836b8303b6157c7508faad Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 12 Jun 2021 19:56:22 +0200 Subject: [PATCH 13/16] Also delete unused strings related to licensing stuff --- ultrasonic/src/main/res/values-cs/strings.xml | 5 ----- ultrasonic/src/main/res/values-de/strings.xml | 5 ----- ultrasonic/src/main/res/values-es/strings.xml | 5 ----- ultrasonic/src/main/res/values-fr/strings.xml | 5 ----- ultrasonic/src/main/res/values-hu/strings.xml | 5 ----- ultrasonic/src/main/res/values-it/strings.xml | 5 ----- ultrasonic/src/main/res/values-nl/strings.xml | 5 ----- ultrasonic/src/main/res/values-pl/strings.xml | 5 ----- ultrasonic/src/main/res/values-pt-rBR/strings.xml | 5 ----- ultrasonic/src/main/res/values-pt/strings.xml | 5 ----- ultrasonic/src/main/res/values-ru/strings.xml | 5 ----- ultrasonic/src/main/res/values-zh-rCN/strings.xml | 4 ---- ultrasonic/src/main/res/values/strings.xml | 5 ----- 13 files changed, 64 deletions(-) diff --git a/ultrasonic/src/main/res/values-cs/strings.xml b/ultrasonic/src/main/res/values-cs/strings.xml index c01d1597..58944209 100644 --- a/ultrasonic/src/main/res/values-cs/strings.xml +++ b/ultrasonic/src/main/res/values-cs/strings.xml @@ -130,16 +130,11 @@ Kliknout pro vyhledání Skladby Hledat - Zkušební doba vypršela - Pozdějí - Získejte neomezená stahování přispěním na Subsonic. - Hned Média nenalezena %d skladeb označeno. %d skladeb odznačeno. Varování: Připojení nedostupné. Chyba: SD karta nedostupná. - Server bez licence. Zbývá %d dní zkušební doby. Přehrát vše Všechny adresáře Vybrat adresář diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index 055683f8..08208b9f 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -129,16 +129,11 @@ Neue Suche Titel Suche - Testperiode zu Ende - Später - Unbegresnze Downloads bei Spende an Sunsonic - Jetzt Keine Medien gefunden %d Titel ausgewählt. %d Titel abgewählt. Warnung: kein Netz. Fehler: Keine SD Karte verfügbar. - Server nicht lizenziert. Noch %d Testtage Alles wiedergeben Alle Ordner Ordner wählen diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index f0c8dc81..9b494b2b 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -140,16 +140,11 @@ Haz click para buscar Canciones Buscar - El periodo de prueba ha finalizado - Mas tarde - Consigue descargas ilimitadas donando a Subsonic. - Ahora No se han encontrado medios %d pista(s) seleccionada(s). %d pista(s) deseleccionada(s). Atención: No hay red disponible. Error: No hay tarjeta SD disponible. - Servidor sin licencia. Quedan %d dia(s) de prueba. Reproducir todo Todas las carpetas Seleccionar la carpeta diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 4c23cd30..ad640ff7 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -130,16 +130,11 @@ Cliquer pour rechercher Titres Recherche - La période d\'essai est terminée - Plus tard - Obtenez des téléchargements illimités en faisant un don pour Subsonic. - Maintenant Aucun titre trouvé %d pistes sélectionnées. %d pistes non sélectionnés. Avertissement : Aucun réseau disponible. Erreur : Aucune carte SD disponible. - Serveur sans licence. %d jours d\'essai restants. Tout jouer Tous les dossiers Sélectionner le dossier diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 20bdd2d8..ffdf144a 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -140,16 +140,11 @@ Érintse meg a kereséshez Dalok Keresés - A próbaidőszak lejárt! - Később - Korlátlan letöltéshez juthat a Subsonic támogatásával. - Most Nem található média! %d dal kijelölve. %d dal visszavonva. Figyelem: Hálózat nem áll rendelkezésre! Hiba: SD kártya nem áll rendelkezésre! - A kiszolgálónak nincs licence! %d próba nap van hátra! Összes lejátszása Összes mappa Mappa kiválasztása diff --git a/ultrasonic/src/main/res/values-it/strings.xml b/ultrasonic/src/main/res/values-it/strings.xml index 81e41ca5..2a92a780 100644 --- a/ultrasonic/src/main/res/values-it/strings.xml +++ b/ultrasonic/src/main/res/values-it/strings.xml @@ -125,16 +125,11 @@ Selezione per cercare Canzoni Cerca - Periodo di prova terminato - Dopo - Ottieni download illimitato con una donazione a Subsonic. - Ora Nessun media trovato %dtracce selezionate. %d tracce non selezionate. Attenzione: nessuna rete disponibile. Errore: Nessuna memoria SD disponibile. - Nessuna licenza presente. %d giorni di prova rimanenti. Riproduci tutto Tutte le cartelle Seleziona cartella diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml index ae73cbbc..445bfaf4 100644 --- a/ultrasonic/src/main/res/values-nl/strings.xml +++ b/ultrasonic/src/main/res/values-nl/strings.xml @@ -140,16 +140,11 @@ Druk om te zoeken Nummers Zoeken - Proefperiode is afgelopen - Later - Verkrijg ongelimiteerde downloads door te doneren aan Subsonic. - Nu Geen media gevonden %d nummers geselecteerd. %d nummers gedeselecteerd. Waarschuwing: geen internetverbinding. Fout: geen SD-kaart beschikbaar. - Geen serverlicentie; nog %d dagen resterend van de proefperiode. Alles afspelen Alle mappen Map kiezen diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml index f2019e33..d127a8aa 100644 --- a/ultrasonic/src/main/res/values-pl/strings.xml +++ b/ultrasonic/src/main/res/values-pl/strings.xml @@ -128,16 +128,11 @@ Kliknij, aby wyszukać Utwory Wyszukiwanie - Okres próbny zakończył się - Później - Uzyskaj możliwość nieograniczonych pobrań przekazując darowiznę na rzecz Subsonic. - Teraz Brak mediów Zaznaczono %d utworów. Odznaczono %d utworów. Uwaga: sieć niedostępna. Błąd: Niedostępna karta SD. - Serwer bez licencji. Pozostało %d dni próbnych. Odtwórz wszystkie Wszystkie foldery Wybierz folder diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 062afd45..b9683c7a 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -130,16 +130,11 @@ Clique para pesquisar Músicas Pesquisar - O período de teste acabou - Mais tarde - Obtenha downloads ilimitados fazendo uma doação ao Subsonic. - Agora Nenhuma mídia encontrada %d faixas selecionadas. %d faixas desselecionadas. Aviso: Nenhuma rede disponível. Erro: Nenhum cartão SD disponível. - Servidor não licenciado. Restam %d dias de teste. Tocar Tudo Todas as Pastas Selecionar Pasta diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 0fc87d74..2687127d 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -128,16 +128,11 @@ Clique para pesquisar Músicas Pesquisar - O período de teste acabou - Mais tarde - Obtenha downloads ilimitados fazendo uma doação ao Subsonic. - Agora Nenhuma mídia encontrada %d faixas selecionadas. %d faixas desselecionadas. Aviso: Nenhuma rede disponível. Erro: Nenhum cartão SD disponível. - Servidor não licenciado. Restam %d dias de teste. Tocar Tudo Todas as Pastas Selecionar Pasta diff --git a/ultrasonic/src/main/res/values-ru/strings.xml b/ultrasonic/src/main/res/values-ru/strings.xml index a5e9962c..b5afcad1 100644 --- a/ultrasonic/src/main/res/values-ru/strings.xml +++ b/ultrasonic/src/main/res/values-ru/strings.xml @@ -125,16 +125,11 @@ Нажми для поиска Песни Поиск - Пробный период окончен - Позже - Получите неограниченное количество загрузок, пожертвовав Subsonic - Сейчас Медиа не найдена %d треки выбраны. %d треки не выбраны. Предупреждение: сеть недоступна. Ошибка: нет SD-карты - Сервер не лицензирован. %d пробные дни остались. Воспроизвести все Все папки Выбрать папку diff --git a/ultrasonic/src/main/res/values-zh-rCN/strings.xml b/ultrasonic/src/main/res/values-zh-rCN/strings.xml index 2cc97a7a..171e18c5 100644 --- a/ultrasonic/src/main/res/values-zh-rCN/strings.xml +++ b/ultrasonic/src/main/res/values-zh-rCN/strings.xml @@ -106,10 +106,6 @@ 点击搜索 歌曲 搜索 - 试用已结束 - 稍后 - 通过捐赠 Subsonic 得到无限制的下载。 - 现在 找不到歌曲 警告:网络不可用 错误:没有SD卡 diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 298d7200..04cd961d 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -140,16 +140,11 @@ Click to search Songs Search - Trial period is over - Later - Get unlimited downloads by donating to Subsonic. - Now No media found %d tracks selected. %d tracks unselected. Warning: No network available. Error: No SD card available. - Server not licensed. %d trial days left. Play All All Folders Select Folder From 3b5b8feb5681d2d32b8d9b697dcc92937f5ab855 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 12 Jun 2021 19:58:39 +0200 Subject: [PATCH 14/16] Update baseline files --- detekt-baseline.xml | 8 - ultrasonic/lint-baseline.xml | 642 +++++++++-------------------------- 2 files changed, 159 insertions(+), 491 deletions(-) diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 856dc92d..7235a790 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -10,8 +10,6 @@ ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons() ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) - EmptyCatchBlock:LocalMediaPlayer.kt$LocalMediaPlayer${ } - EmptyDefaultConstructor:VideoPlayer.kt$VideoPlayer$() EmptyFunctionBlock:SongView.kt$SongView${} FunctionNaming:ThemeChangedEventDistributor.kt$ThemeChangedEventDistributor$fun RaiseThemeChangedEvent() ImplicitDefaultLocale:DownloadFile.kt$DownloadFile$String.format("DownloadFile (%s)", song) @@ -28,7 +26,6 @@ ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix) - LargeClass:RESTMusicService.kt$RESTMusicService : MusicService LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) @@ -58,7 +55,6 @@ MagicNumber:MediaPlayerService.kt$MediaPlayerService$3 MagicNumber:MediaPlayerService.kt$MediaPlayerService$4 MagicNumber:RESTMusicService.kt$RESTMusicService$206 - MagicNumber:RESTMusicService.kt$RESTMusicService$5 MagicNumber:SongView.kt$SongView$3 MagicNumber:SongView.kt$SongView$4 MagicNumber:SongView.kt$SongView$60 @@ -67,14 +63,10 @@ NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String - ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap? ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() } - ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>) TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$e: Throwable TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$exception: Throwable TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 21a3f032..a0874cbb 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -19,7 +19,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -172,21 +172,10 @@ errorLine2=" ~~~~"> - - - - - - - - @@ -238,7 +216,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -249,7 +227,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -260,7 +238,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -271,7 +249,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -282,7 +260,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -293,7 +271,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -315,7 +293,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -326,18 +304,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - @@ -348,7 +315,7 @@ errorLine2=" ^"> @@ -359,7 +326,7 @@ errorLine2=" ^"> @@ -370,7 +337,7 @@ errorLine2=" ^"> @@ -381,7 +348,7 @@ errorLine2=" ^"> @@ -392,7 +359,7 @@ errorLine2=" ^"> @@ -403,7 +370,7 @@ errorLine2=" ^"> @@ -414,7 +381,7 @@ errorLine2=" ^"> @@ -738,7 +705,7 @@ column="13"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1532,7 +1208,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1543,7 +1219,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1554,55 +1230,55 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - + @@ -1613,51 +1289,51 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - + + + + @@ -1668,55 +1344,55 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - + + + @@ -1727,51 +1403,51 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1782,7 +1458,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1793,43 +1469,43 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1873,7 +1549,7 @@ errorLine2=" ^"> @@ -1884,7 +1560,7 @@ errorLine2=" ^"> @@ -1895,7 +1571,7 @@ errorLine2=" ^"> @@ -1906,7 +1582,7 @@ errorLine2=" ^"> @@ -1917,18 +1593,7 @@ errorLine2=" ^"> - - - - @@ -1939,18 +1604,7 @@ errorLine2=" ^"> - - - - @@ -1961,7 +1615,29 @@ errorLine2=" ^"> + + + + + + + + @@ -1972,7 +1648,7 @@ errorLine2=" ^"> @@ -1994,7 +1670,7 @@ errorLine2=" ^"> @@ -2113,7 +1789,7 @@ errorLine2=" ^"> @@ -2124,7 +1800,7 @@ errorLine2=" ~~~~~~~"> @@ -2135,7 +1811,7 @@ errorLine2=" ^"> @@ -2146,7 +1822,7 @@ errorLine2=" ~~~~~~~"> @@ -2157,7 +1833,7 @@ errorLine2=" ~~~~~~~"> From a02b620531cbf50c5e210af064cfee9d5fef953e Mon Sep 17 00:00:00 2001 From: Nite Date: Mon, 14 Jun 2021 15:42:24 +0200 Subject: [PATCH 15/16] Fixed typos --- .../kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt | 2 +- ultrasonic/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index dde04b63..c770efe4 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -326,7 +326,7 @@ class NavigationActivity : AppCompatActivity() { .setTitle(R.string.main_welcome_title) .setMessage(R.string.main_welcome_text_demo) .setNegativeButton(R.string.main_welcome_cancel) { dialog, _ -> - // Got to the settings screen + // Go to the settings screen dialog.dismiss() findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment) } diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 1e82cee4..3ed173d2 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -111,7 +111,7 @@ Starred Songs Videos - To use Ultrasonic with your own music you will need your own server: \n\n➤ In case you want to try out the app first, it can add a demo server now. \n\n➤ Otherwise you can configure your server it in the settings. + To use Ultrasonic with your own music you will need your own server. \n\n➤ In case you want to try out the app first, it can add a demo server now. \n\n➤ Otherwise you can configure your server in the settings. Welcome to Ultrasonic! Take me to the settings About From 57d740af1237d414f3c24a24ebb1d909a54db13f Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 14 Jun 2021 20:31:53 +0200 Subject: [PATCH 16/16] Properly generate the Video stream url, without actually making a request! --- .../api/subsonic/GetStreamUrlTest.kt | 54 ---------------- .../ultrasonic/api/subsonic/Extensions.kt | 16 ----- .../api/subsonic/SubsonicAPIClient.kt | 4 +- .../ultrasonic/service/RESTMusicService.kt | 61 +++++++++++-------- 4 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt deleted file mode 100644 index 7cdf4a69..00000000 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/GetStreamUrlTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.moire.ultrasonic.api.subsonic - -import okhttp3.mockwebserver.MockResponse -import org.amshove.kluent.`should be equal to` -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_6_0 -import org.moire.ultrasonic.api.subsonic.interceptors.toHexBytes -import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule - -/** - * Integration test for [getStreamUrl] method. - */ -class GetStreamUrlTest { - @JvmField @Rule val mockWebServerRule = MockWebServerRule() - - val id = "boom" - private lateinit var client: SubsonicAPIClient - private lateinit var expectedUrl: String - - @Before - fun setUp() { - val config = SubsonicClientConfiguration( - mockWebServerRule.mockWebServer.url("/").toString(), - USERNAME, - PASSWORD, - V1_6_0, - CLIENT_ID - ) - client = SubsonicAPIClient(config) - val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString() - expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&format=raw&u=$USERNAME" + - "&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}" - } - - @Test - fun `Should return valid stream url`() { - mockWebServerRule.enqueueResponse("ping_ok.json") - - val streamUrl = client.api.getStreamUrl(id) - - streamUrl `should be equal to` expectedUrl - } - - @Test - fun `Should still return stream url if connection failed`() { - mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(500)) - - val streamUrl = client.api.getStreamUrl(id) - - streamUrl `should be equal to` expectedUrl - } -} diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt index 666dbcf1..f9d48d23 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/Extensions.kt @@ -83,19 +83,3 @@ fun StreamResponse.throwOnFailure(): StreamResponse { } return this } - -/** - * Gets a stream url. - * - * Calling this method do actual connection to the backend, though not downloading all content. - * - * Consider do not use this method, but [SubsonicAPIDefinition.stream] call. - */ -fun SubsonicAPIDefinition.getStreamUrl(id: String): String { - val response = this.stream(id, format = "raw").execute() - val url = response.raw().request().url().toString() - if (response.isSuccessful) { - response.body()?.close() - } - return url -} diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index c50cc353..b5847c95 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -62,7 +62,7 @@ class SubsonicAPIClient( onProtocolChange(field) } - private val okHttpClient = baseOkClient.newBuilder() + val okHttpClient: OkHttpClient = baseOkClient.newBuilder() .readTimeout(READ_TIMEOUT, MILLISECONDS) .apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() } .addInterceptor { chain -> @@ -83,7 +83,7 @@ class SubsonicAPIClient( // Create the Retrofit instance, and register a special converter factory // It will update our protocol version to the correct version, once we made a successful call - private val retrofit = Retrofit.Builder() + val retrofit: Retrofit = Retrofit.Builder() .baseUrl("${config.baseUrl}/rest/") .client(okHttpClient) .addConverterFactory( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 6d3a531c..4876bd9e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -11,12 +11,11 @@ import java.io.File import java.io.FileWriter import java.io.IOException import java.io.InputStream -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient -import org.moire.ultrasonic.api.subsonic.getStreamUrl import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName import org.moire.ultrasonic.api.subsonic.models.JukeboxAction import org.moire.ultrasonic.api.subsonic.throwOnFailure @@ -53,7 +52,7 @@ import timber.log.Timber */ @Suppress("LargeClass") open class RESTMusicService( - subsonicAPIClient: SubsonicAPIClient, + val subsonicAPIClient: SubsonicAPIClient, private val fileStorage: PermanentFileStorage, private val activeServerProvider: ActiveServerProvider ) : MusicService { @@ -479,35 +478,43 @@ open class RESTMusicService( return Pair(response.stream!!, partial) } + /** + * We currently don't handle video playback in the app, but just create an Intent which video + * players can respond to. For this intent we need the full URL of the stream, including the + * authentication params. This is a bit tricky, because we want to avoid actually executing the + * call because that could take a long time. + */ @Throws(Exception::class) override fun getVideoUrl( id: String ): String { - // TODO This method should not exists as video should be loaded using stream method - // Previous method implementation uses assumption that video will be available - // by videoPlayer.view?id=&maxBitRate=500&autoplay=true, but this url is not - // official Subsonic API call. - val expectedResult = arrayOfNulls(1) - expectedResult[0] = null + // Create a new modified okhttp client to intercept the URL + val builder = subsonicAPIClient.okHttpClient.newBuilder() - val latch = CountDownLatch(1) - - Thread( - { - expectedResult[0] = API.getStreamUrl(id) - latch.countDown() - }, - "Get-Video-Url" - ).start() - - // Getting the stream can take a long time on some servers - latch.await(1, TimeUnit.MINUTES) - - if (expectedResult[0] == null) { - throw TimeoutException("Server didn't respond in time") + builder.addInterceptor { chain -> + // Returns a dummy response + Response.Builder() + .code(100) + .body(ResponseBody.create(null, "")) + .protocol(Protocol.HTTP_2) + .message("Empty response") + .request(chain.request()) + .build() } - return expectedResult[0]!! + // Create a new Okhttp client + val client = builder.build() + + // Get the request from Retrofit, but don't execute it! + val request = API.stream(id, format = "raw").request() + + // Create a new call with the request, and execute ist on our custom client + val response = client.newCall(request).execute() + + // The complete url :) + val url = response.request().url() + + return url.toString() } @Throws(Exception::class)