diff --git a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicClient.kt b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicClient.kt index d0bef7a9..f4f7e196 100644 --- a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicClient.kt +++ b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicClient.kt @@ -8,6 +8,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import java.io.InputStream import java.security.MessageDigest +import java.util.concurrent.TimeUnit open class SubsonicException( val code: Int, @@ -55,11 +56,12 @@ class SubsonicClient( fun stream( method: String, params: Map = emptyMap(), + timeoutMs: Long? = null, body: (InputStream) -> T, ): T { val usedTokenAuth = useTokenAuth return try { - execute(method, params, body) + execute(method, params, timeoutMs, body) } catch (failure: SubsonicException) { if (failure.code !in TOKEN_AUTH_UNSUPPORTED_CODES || !usedTokenAuth) { Log.w("http", "$method failed: server code ${failure.code} \"${failure.message}\"") @@ -67,13 +69,14 @@ class SubsonicClient( } Log.w("http", "$method: token auth unsupported (server code ${failure.code}), retrying with plaintext") disableTokenAuth() - execute(method, params, body) + execute(method, params, timeoutMs, body) } } private fun execute( method: String, params: Map, + timeoutMs: Long?, body: (InputStream) -> T, ): T { val request = @@ -81,7 +84,9 @@ class SubsonicClient( .Builder() .url(uri(method, params)) .build() - http.newCall(request).execute().use { response -> + val call = http.newCall(request) + timeoutMs?.let { call.timeout().timeout(it, TimeUnit.MILLISECONDS) } + call.execute().use { response -> if (!response.isSuccessful) { throw SubsonicException(-1, "HTTP ${response.code}", UiMessage(R.string.error_http, listOf(response.code))) } diff --git a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt index 026dc3b5..6a76f39c 100644 --- a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt +++ b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt @@ -87,7 +87,7 @@ class SubsonicSource( override suspend fun artistBiography(artistId: String): String? = withContext(Dispatchers.IO) { - client.stream("getArtistInfo2", mapOf("id" to artistId)) { input -> + client.stream("getArtistInfo2", mapOf("id" to artistId), timeoutMs = ARTIST_BIO_TIMEOUT_MS) { input -> SubsonicXml.artistBiography(input) } } @@ -372,6 +372,7 @@ class SubsonicSource( const val PAGE_SIZE = 500 const val MAX_PAGES = 20_000 const val THUMBNAIL_SIZE = 384 + const val ARTIST_BIO_TIMEOUT_MS = 15_000L } } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt index adbb0bc2..00e810bd 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt @@ -29,9 +29,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull - -private const val BIO_REFRESH_TIMEOUT_MS = 15_000L @OptIn(ExperimentalCoroutinesApi::class) class ArtistDetailViewModel( @@ -102,8 +99,7 @@ class ArtistDetailViewModel( } _bioLoading.value = true try { - val result = withTimeoutOrNull(BIO_REFRESH_TIMEOUT_MS) { libraryRepository.refreshArtistBio(id, artistId) } - _bioUnavailable.value = result == null || result.isFailure + _bioUnavailable.value = libraryRepository.refreshArtistBio(id, artistId).isFailure } finally { _bioLoading.value = false }