diff --git a/app/src/main/java/com/fluxa/app/core/rust/FluxaAndroidHeadlessEnvironment.kt b/app/src/main/java/com/fluxa/app/core/rust/FluxaAndroidHeadlessEnvironment.kt index 333ecf9..034b7c5 100644 --- a/app/src/main/java/com/fluxa/app/core/rust/FluxaAndroidHeadlessEnvironment.kt +++ b/app/src/main/java/com/fluxa/app/core/rust/FluxaAndroidHeadlessEnvironment.kt @@ -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 = diff --git a/data/src/androidMain/kotlin/com/fluxa/app/data/repository/HttpEffectExecutor.kt b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/HttpEffectExecutor.kt new file mode 100644 index 0000000..ba03a30 --- /dev/null +++ b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/HttpEffectExecutor.kt @@ -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 = 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) + } + } +} diff --git a/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonManifestClient.kt b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonManifestClient.kt index 396fd01..c7d403a 100644 --- a/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonManifestClient.kt +++ b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonManifestClient.kt @@ -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) diff --git a/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonResourceClient.kt b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonResourceClient.kt index 9920a4b..8c36cf9 100644 --- a/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonResourceClient.kt +++ b/data/src/androidMain/kotlin/com/fluxa/app/data/repository/StremioAddonResourceClient.kt @@ -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,