fix: adjusting addon response cap for android

This commit is contained in:
tapframe 2026-04-04 02:04:35 +05:30
parent da7053f426
commit bda164dfa9
3 changed files with 61 additions and 4 deletions

View file

@ -54,7 +54,7 @@ private val addonHttpClient = OkHttpClient.Builder()
.build()
private val jsonMediaType = "application/json; charset=utf-8".toMediaType()
private const val maxResponseBodyBytes = 256 * 1024
private const val maxRawResponseBodyBytes = 1024 * 1024
private const val truncationSuffix = "\n...[truncated]"
private fun requestAllowsBody(method: String): Boolean =
@ -100,7 +100,7 @@ private fun readResponseBodyLimited(body: ResponseBody?): String {
if (body == null) return ""
val charset = body.contentType()?.charset(Charsets.UTF_8) ?: Charsets.UTF_8
val readResult = body.byteStream().use { stream ->
readAtMostBytes(stream, maxResponseBodyBytes)
readAtMostBytes(stream, maxRawResponseBodyBytes)
}
val decoded = try {
@ -116,6 +116,17 @@ private fun readResponseBodyLimited(body: ResponseBody?): String {
}
}
private fun readResponseBody(body: ResponseBody?): String {
if (body == null) return ""
val bytes = body.bytes()
return runCatching {
val charset = body.contentType()?.charset(Charsets.UTF_8) ?: Charsets.UTF_8
String(bytes, charset)
}.getOrElse {
String(bytes, Charsets.UTF_8)
}
}
private suspend fun executeTextRequest(
method: String,
url: String,
@ -140,7 +151,7 @@ private suspend fun executeTextRequest(
}.build()
addonHttpClient.newCall(request).execute().use { response ->
val payload = readResponseBodyLimited(response.body)
val payload = readResponseBody(response.body)
if (!response.isSuccessful) {
error("Request failed with HTTP ${response.code}")
}

View file

@ -97,7 +97,7 @@ fun mergeCatalogItems(
}
}
private fun buildCatalogUrl(
internal fun buildCatalogUrl(
manifestUrl: String,
type: String,
catalogId: String,

View file

@ -1,12 +1,15 @@
package com.nuvio.app.features.search
import co.touchlab.kermit.Logger
import com.nuvio.app.features.addons.AddonCatalog
import com.nuvio.app.features.addons.AddonExtraProperty
import com.nuvio.app.features.addons.ManagedAddon
import com.nuvio.app.features.catalog.buildCatalogUrl
import com.nuvio.app.features.catalog.fetchCatalogPage
import com.nuvio.app.features.catalog.mergeCatalogItems
import com.nuvio.app.features.catalog.supportsPagination
import com.nuvio.app.features.home.HomeCatalogSection
import com.nuvio.app.features.home.MetaPreview
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -19,6 +22,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
object SearchRepository {
private val log = Logger.withTag("SearchRepository")
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val _uiState = MutableStateFlow(SearchUiState())
val uiState: StateFlow<SearchUiState> = _uiState.asStateFlow()
@ -120,6 +124,7 @@ object SearchRepository {
if (activeAddons.isEmpty()) {
activeDiscoverJob?.cancel()
discoverSources = emptyList()
log.d { "Discover refresh aborted: no active addons" }
_discoverUiState.value = DiscoverUiState(
emptyStateReason = DiscoverEmptyStateReason.NoActiveAddons,
)
@ -129,12 +134,17 @@ object SearchRepository {
val sources = buildDiscoverSources(activeAddons)
val current = _discoverUiState.value
if (sources == discoverSources && current.canReuseDiscoverState(sources)) {
log.d {
"Reusing discover state type=${current.selectedType} catalog=${current.selectedCatalogKey} " +
"genre=${current.selectedGenre ?: "<all>"} items=${current.items.size} nextSkip=${current.nextSkip}"
}
return
}
discoverSources = sources
if (sources.isEmpty()) {
activeDiscoverJob?.cancel()
log.d { "Discover refresh found no compatible discover catalogs" }
_discoverUiState.value = DiscoverUiState(
emptyStateReason = DiscoverEmptyStateReason.NoDiscoverCatalogs,
)
@ -162,6 +172,11 @@ object SearchRepository {
errorMessage = null,
)
log.d {
"Discover refresh prepared type=$selectedType catalog=${selectedCatalog.key} " +
"genre=${selectedGenre ?: "<all>"} sources=${sources.size}"
}
loadDiscoverFeed(reset = true)
}
@ -314,6 +329,20 @@ object SearchRepository {
val current = _discoverUiState.value
val selectedCatalog = current.selectedCatalog ?: return
val requestedSkip = if (reset) 0 else current.nextSkip ?: return
val requestUrl = buildCatalogUrl(
manifestUrl = selectedCatalog.manifestUrl,
type = selectedCatalog.type,
catalogId = selectedCatalog.catalogId,
genre = current.selectedGenre,
search = null,
skip = requestedSkip.takeIf { it > 0 },
)
log.d {
"Discover request reset=$reset addon=${selectedCatalog.addonName} type=${selectedCatalog.type} " +
"catalogId=${selectedCatalog.catalogId} catalogKey=${selectedCatalog.key} " +
"genre=${current.selectedGenre ?: "<all>"} skip=$requestedSkip url=$requestUrl"
}
_discoverUiState.value = current.copy(
isLoading = true,
@ -343,6 +372,11 @@ object SearchRepository {
} else {
mergeCatalogItems(latest.items, page.items)
}
log.d {
"Discover response catalogKey=${selectedCatalog.key} returned=${page.items.size} " +
"merged=${mergedItems.size} rawItemCount=${page.rawItemCount} nextSkip=${page.nextSkip} " +
"sample=${page.items.previewNames()}"
}
_discoverUiState.value = latest.copy(
items = mergedItems,
isLoading = false,
@ -356,6 +390,11 @@ object SearchRepository {
if (latest.selectedCatalogKey != selectedCatalog.key || latest.selectedGenre != current.selectedGenre) {
return@fold
}
log.e(error) {
"Discover request failed catalogKey=${selectedCatalog.key} addon=${selectedCatalog.addonName} " +
"type=${selectedCatalog.type} catalogId=${selectedCatalog.catalogId} " +
"genre=${current.selectedGenre ?: "<all>"} skip=$requestedSkip url=$requestUrl"
}
_discoverUiState.value = latest.copy(
items = if (reset) emptyList() else latest.items,
isLoading = false,
@ -429,6 +468,13 @@ private fun DiscoverUiState.canReuseDiscoverState(
return isLoading || items.isNotEmpty() || emptyStateReason != null || errorMessage != null || nextSkip != null
}
private fun List<MetaPreview>.previewNames(limit: Int = 5): String {
if (isEmpty()) return "[]"
return take(limit).joinToString(prefix = "[", postfix = if (size > limit) ", ...]" else "]") { item ->
item.name
}
}
private fun String.displayLabel(): String =
replaceFirstChar { char ->
if (char.isLowerCase()) char.titlecase() else char.toString()