From f51df4363715d11cc931e2e1f6350da1555ade44 Mon Sep 17 00:00:00 2001 From: sonyx Date: Wed, 28 Oct 2026 21:52:23 -0300 Subject: [PATCH] fix: refresh transient availability metadata on explicit retry --- .../lagradost/cloudstream3/ui/APIRepository.kt | 13 +++++++++++-- .../cloudstream3/ui/LoadResponseCachePolicy.kt | 8 ++++++++ .../ui/result/ResultFragmentPhone.kt | 3 +++ .../cloudstream3/ui/result/ResultFragmentTv.kt | 4 ++++ .../main/res/values-es/availability_options.xml | 3 +++ .../main/res/values/availability_options.xml | 3 +++ .../ui/LoadResponseCachePolicyTest.kt | 17 +++++++++++++++++ 7 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicy.kt create mode 100644 app/src/main/res/values-es/availability_options.xml create mode 100644 app/src/main/res/values/availability_options.xml create mode 100644 app/src/test/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicyTest.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 8ec082520fb..290e587eb01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -58,6 +58,10 @@ class APIRepository(val api: MainAPI) { private val cache = atomicListOf() private var cacheIndex: Int = 0 const val CACHE_SIZE = 20 + fun invalidateLoadResponse(apiName: String, url: String) { + val fixedUrl = com.lagradost.cloudstream3.APIHolder.getApiFromNameNull(apiName)?.fixUrl(url) ?: url + cache.withLock { cache.removeAll { it.hash == (apiName to fixedUrl) } } + } fun getTimeout(desired: Long?): Long { return (desired ?: DEFAULT_TIMEOUT).coerceIn(MIN_TIMEOUT, MAX_TIMEOUT) @@ -93,7 +97,7 @@ class APIRepository(val api: MainAPI) { var found: LoadResponse? = null for (item in cache) { // 10 min save - if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) { + if (item.hash == lookingForHash && LoadResponseCachePolicy.isFresh(item.unixTime, unixTime, item.response.comingSoon)) { found = item.response break } @@ -108,7 +112,7 @@ class APIRepository(val api: MainAPI) { val add = SavedLoadResponse(unixTime, response, lookingForHash) cache.withLock { - if (cache.size > CACHE_SIZE) { + if (cache.size >= CACHE_SIZE) { cache[cacheIndex] = add // rolling cache cacheIndex = (cacheIndex + 1) % CACHE_SIZE } else { @@ -212,6 +216,11 @@ class APIRepository(val api: MainAPI) { withTimeout(getTimeout(api.loadLinksTimeoutMs)) { api.loadLinks(data, isCasting, subtitleCallback, callback) } + } catch (timeout: kotlinx.coroutines.TimeoutCancellationException) { + logError(timeout) + return false + } catch (cancelled: kotlinx.coroutines.CancellationException) { + throw cancelled } catch (throwable: Throwable) { logError(throwable) return false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicy.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicy.kt new file mode 100644 index 00000000000..e582fa93212 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicy.kt @@ -0,0 +1,8 @@ +package com.lagradost.cloudstream3.ui + +internal object LoadResponseCachePolicy { + fun isFresh(savedAt: Long, now: Long, unavailable: Boolean): Boolean { + val age = now - savedAt + return age >= 0 && age < if (unavailable) 30 else 600 + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 38b24b26517..ce2100fcf10 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -492,6 +492,7 @@ open class ResultFragmentPhone : BaseFragment( resultBinding?.apply { resultReloadConnectionerror.setOnClickListener { + com.lagradost.cloudstream3.ui.APIRepository.invalidateLoadResponse(storedData.apiName, storedData.url) viewModel.load( activity, storedData.url, @@ -989,6 +990,8 @@ open class ResultFragmentPhone : BaseFragment( populateChips(resultTag, d.tags) resultComingSoon.isVisible = d.comingSoon + resultComingSoon.setText(R.string.playback_availability_unknown) + resultComingSoon.setOnClickListener { resultReloadConnectionerror.performClick() } resultDataHolder.isGone = d.comingSoon val prefs = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index c316fae1715..3a568c26fea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -390,6 +390,7 @@ class ResultFragmentTv : BaseFragment( }*/ resultReloadConnectionerror.setOnClickListener { + com.lagradost.cloudstream3.ui.APIRepository.invalidateLoadResponse(storedData.apiName, storedData.url) viewModel.load( activity, storedData.url, @@ -922,6 +923,9 @@ class ResultFragmentTv : BaseFragment( comingSoon = d.comingSoon resultTvComingSoon.isVisible = d.comingSoon + resultTvComingSoon.setText(R.string.playback_availability_unknown) + resultTvComingSoon.isFocusable = d.comingSoon + resultTvComingSoon.setOnClickListener { resultReloadConnectionerror.performClick() } populateChips(resultTag, d.tags) val prefs = diff --git a/app/src/main/res/values-es/availability_options.xml b/app/src/main/res/values-es/availability_options.xml new file mode 100644 index 00000000000..021a1b2f112 --- /dev/null +++ b/app/src/main/res/values-es/availability_options.xml @@ -0,0 +1,3 @@ + + Esta fuente todavĂ­a no ha proporcionado contenido reproducible. Puede no estar disponible o no haberse estrenado. Pulsa para reintentar. + diff --git a/app/src/main/res/values/availability_options.xml b/app/src/main/res/values/availability_options.xml new file mode 100644 index 00000000000..b707068170c --- /dev/null +++ b/app/src/main/res/values/availability_options.xml @@ -0,0 +1,3 @@ + + This provider has not supplied playable content yet. It may be unavailable or upcoming. Tap to retry. + diff --git a/app/src/test/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicyTest.kt b/app/src/test/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicyTest.kt new file mode 100644 index 00000000000..80b5245f0a2 --- /dev/null +++ b/app/src/test/java/com/lagradost/cloudstream3/ui/LoadResponseCachePolicyTest.kt @@ -0,0 +1,17 @@ +package com.lagradost.cloudstream3.ui + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class LoadResponseCachePolicyTest { + @Test fun transientUnavailableResultsDoNotStickForTenMinutes() { + assertTrue(LoadResponseCachePolicy.isFresh(100, 120, true)) + assertFalse(LoadResponseCachePolicy.isFresh(100, 131, true)) + assertTrue(LoadResponseCachePolicy.isFresh(100, 131, false)) + } + @Test fun normalEntriesExpireAndClockRollbackInvalidates() { + assertFalse(LoadResponseCachePolicy.isFresh(100, 700, false)) + assertFalse(LoadResponseCachePolicy.isFresh(100, 99, false)) + } +}