Consolidate raw OkHttp request/response glue into a shared HttpEffectExecutor

StremioAddonManifestClient, StremioAddonResourceClient, and the trailer
effect in FluxaAndroidHeadlessEnvironment each hand-rolled the same
Request.Builder -> execute -> read status/body -> catch pattern. One
shared executor now backs all three call sites.
This commit is contained in:
KhooLy 2026-07-20 02:03:00 +03:00
parent f0cf10d757
commit fc005ba1df
4 changed files with 53 additions and 35 deletions

View file

@ -36,6 +36,7 @@ import com.fluxa.app.player.MediaPlayerController
import com.fluxa.app.player.TorrentStreamManager
import com.fluxa.app.player.TorrentStreamResult
import com.fluxa.app.data.repository.CloudStreamCatalogClient
import com.fluxa.app.data.repository.HttpEffectExecutor
import com.fluxa.app.data.repository.toStremioType
import com.fluxa.app.plugins.PluginManager
import com.fluxa.app.plugins.cloudstream.ExternalExtensionRunner
@ -111,7 +112,8 @@ class FluxaAndroidHeadlessEnvironment @Inject constructor(
internal val gson: Gson,
internal val profileManager: ProfileManager,
internal val externalSyncPushCoordinator: ExternalSyncPushCoordinator,
internal val nuvioAccountImportCoordinator: NuvioAccountImportCoordinator
internal val nuvioAccountImportCoordinator: NuvioAccountImportCoordinator,
internal val httpEffectExecutor: HttpEffectExecutor
) : HeadlessPlatformEnvironment {
internal val primeScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -1117,19 +1119,17 @@ class FluxaAndroidHeadlessEnvironment @Inject constructor(
private fun executeTrailerHttpEffect(effect: NativeHeadlessEffect): HeadlessEffectCompletion {
val payload = effect.payload
val requestBuilder = okhttp3.Request.Builder().url(payload.string("url"))
payload.objectValue("headers")?.forEach { (key, value) ->
requestBuilder.header(key, value.toString())
}
val headers = payload.objectValue("headers")?.mapValues { it.value.toString() }.orEmpty()
val method = payload.string("method", "GET")
val body = payload["body"]?.let {
gson.toJson(it).toRequestBody("application/json".toMediaType())
}
requestBuilder.method(method, body)
trailerHttpClient.newCall(requestBuilder.build()).execute().use { response ->
if (!response.isSuccessful) return error(effect, "http_${response.code}")
return ok(effect, mapOf("body" to response.body.string()))
val result = httpEffectExecutor.execute(trailerHttpClient, payload.string("url"), method, headers, body)
val statusCode = result.statusCode
if (result.error != null || statusCode == null || statusCode !in 200..299) {
return error(effect, "http_${statusCode ?: 0}")
}
return ok(effect, mapOf("body" to result.body))
}
internal fun ok(effect: NativeHeadlessEffect, value: Any?): HeadlessEffectCompletion =

View file

@ -0,0 +1,33 @@
package com.fluxa.app.data.repository
import android.util.Log
import com.fluxa.app.core.rust.models.NativeAddonFetchResult
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class HttpEffectExecutor @Inject constructor() {
fun execute(
client: OkHttpClient,
url: String,
method: String = "GET",
headers: Map<String, String> = emptyMap(),
body: RequestBody? = null
): NativeAddonFetchResult {
return try {
val requestBuilder = Request.Builder().url(url)
headers.forEach { (key, value) -> requestBuilder.header(key, value) }
requestBuilder.method(method, body)
client.newCall(requestBuilder.build()).execute().use { response ->
NativeAddonFetchResult(url = url, statusCode = response.code, body = response.body.string())
}
} catch (e: Exception) {
Log.w("HttpEffectExecutor", "HTTP request failed: $url", e)
NativeAddonFetchResult(url = url, error = e.message)
}
}
}

View file

@ -7,7 +7,6 @@ import com.fluxa.app.common.AppStrings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.URLEncoder
import javax.inject.Inject
@ -16,6 +15,7 @@ import javax.inject.Named
class StremioAddonManifestClient @Inject constructor(
internal val cache: RepositoryMemoryCache,
internal val persistentCache: AddonPersistentCache,
internal val httpEffectExecutor: HttpEffectExecutor,
@param:Named("StremioClient") private val manifestClient: OkHttpClient
) {
private val unknownName: (String?) -> String = { AppStrings.t(it, "auto.unknown") }
@ -80,18 +80,13 @@ class StremioAddonManifestClient @Inject constructor(
}
}
fetchPlan.candidateUrls.forEach { candidateUrl ->
try {
val httpRequest = Request.Builder().url(candidateUrl).build()
val httpResponse = manifestClient.newCall(httpRequest).execute()
val statusCode = httpResponse.code
val body = httpResponse.body.string().also { httpResponse.close() }
if (statusCode !in 200..299) return@forEach
val descriptor = parseAddonManifest(body, candidateUrl) ?: return@forEach
putCache(cacheKey, descriptor)
persistentCache.putManifest(cacheKey, descriptor)
return@withContext descriptor
} catch (_: Exception) {
}
val result = httpEffectExecutor.execute(manifestClient, candidateUrl)
val body = result.body
if (result.error != null || (result.statusCode ?: 0) !in 200..299 || body == null) return@forEach
val descriptor = parseAddonManifest(body, candidateUrl) ?: return@forEach
putCache(cacheKey, descriptor)
persistentCache.putManifest(cacheKey, descriptor)
return@withContext descriptor
}
if (decision.allowStaleFallback) persistentCache.getManifest(cacheKey)?.let {
putCache(cacheKey, it)

View file

@ -4,7 +4,6 @@ import android.util.Log
import com.fluxa.app.core.rust.FluxaCoreNative
import com.fluxa.app.core.rust.models.NativeAddonFetchResult
import com.fluxa.app.core.rust.models.NativeAddonResourceParseResult
import okhttp3.Request
import com.fluxa.app.data.remote.AddonDescriptor
import com.fluxa.app.data.remote.AuthRequest
import com.fluxa.app.data.remote.Meta
@ -45,6 +44,7 @@ class StremioAddonResourceClient @Inject constructor(
private val cache: RepositoryMemoryCache,
private val persistentCache: AddonPersistentCache,
private val addonManifestClient: StremioAddonManifestClient,
private val httpEffectExecutor: HttpEffectExecutor,
@param:Named("AddonResourceClient") private val httpClient: OkHttpClient
) {
private val stremioGson = GsonBuilder().create()
@ -369,18 +369,8 @@ class StremioAddonResourceClient @Inject constructor(
}
}
private fun fetchAddonBodyResult(url: String): NativeAddonFetchResult {
return try {
val httpRequest = Request.Builder().url(url).build()
val httpResponse = httpClient.newCall(httpRequest).execute()
val status = httpResponse.code
val body = httpResponse.body.string().also { httpResponse.close() }
NativeAddonFetchResult(url = url, statusCode = status, body = body)
} catch (e: Exception) {
Log.w("StremioAddonResourceClient", "Addon resource HTTP failed: $url", e)
NativeAddonFetchResult(url = url, error = e.message)
}
}
private fun fetchAddonBodyResult(url: String): NativeAddonFetchResult =
httpEffectExecutor.execute(httpClient, url)
private fun fetchAddonResourcePayload(
transportUrl: String,