Skip to content

Commit 9e211e5

Browse files
author
RiskyN
committed
fix: resolve search & session race conditions under concurrent startup rail loads
1 parent db103bc commit 9e211e5

2 files changed

Lines changed: 50 additions & 32 deletions

File tree

AnimeVerse/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dependencies {
66
testImplementation(files("C:/Users/user/.gradle/caches/cloudstream/cloudstream/cloudstream.jar"))
77
}
88

9-
version = 2
9+
version = 3
1010

1111
cloudstream {
1212
description = "AnimeVerse Provider"

AnimeVerse/src/main/kotlin/com/animeverse/AnimeVerseProvider.kt

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import com.lagradost.cloudstream3.utils.newExtractorLink
1313
import java.util.EnumSet
1414
import javax.crypto.Mac
1515
import javax.crypto.spec.SecretKeySpec
16+
import kotlinx.coroutines.sync.Mutex
17+
import kotlinx.coroutines.sync.withLock
1618

1719
class AnimeVerseProvider : MainAPI() {
1820
override var mainUrl = "https://animeverse.to"
@@ -35,6 +37,9 @@ class AnimeVerseProvider : MainAPI() {
3537
private var catalogCache: List<CatalogItem>? = null
3638
private var cacheTimestamp: Long = 0
3739

40+
private val sessionMutex = Mutex()
41+
private val catalogMutex = Mutex()
42+
3843
// Custom browser fingerprint parameters accepted by the API
3944
private val fingerprint = mapOf(
4045
"ua" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
@@ -86,29 +91,35 @@ class AnimeVerseProvider : MainAPI() {
8691

8792
// Requests a signed API session — retries up to 3 times since the server is flaky
8893
private suspend fun establishSession() {
89-
var lastError: Exception? = null
90-
repeat(3) { attempt ->
91-
try {
92-
val response = app.post(
93-
"$mainUrl/api/v1/session",
94-
json = mapOf("fp" to fingerprint),
95-
headers = mapOf(
96-
"User-Agent" to fingerprint["ua"].toString(),
97-
"Referer" to "$mainUrl/"
94+
sessionMutex.withLock {
95+
val currentTime = System.currentTimeMillis() / 1000
96+
if (clientAuthKey != null && currentTime < expiresAt) {
97+
return
98+
}
99+
var lastError: Exception? = null
100+
repeat(3) { attempt ->
101+
try {
102+
val response = app.post(
103+
"$mainUrl/api/v1/session",
104+
json = mapOf("fp" to fingerprint),
105+
headers = mapOf(
106+
"User-Agent" to fingerprint["ua"].toString(),
107+
"Referer" to "$mainUrl/"
108+
)
98109
)
99-
)
100-
val res = localMapper.readValue(response.text, SessionResponse::class.java)
101-
clientAuthKey = res.clientAuthKey
102-
expiresAt = res.expiresAt
103-
sessionCookies = response.cookies
104-
return // success — exit early
105-
} catch (e: Exception) {
106-
lastError = e
107-
// brief pause before next attempt
108-
kotlinx.coroutines.delay((attempt + 1) * 1000L)
110+
val res = localMapper.readValue(response.text, SessionResponse::class.java)
111+
clientAuthKey = res.clientAuthKey
112+
expiresAt = res.expiresAt
113+
sessionCookies = response.cookies
114+
return // success — exit early
115+
} catch (e: Exception) {
116+
lastError = e
117+
// brief pause before next attempt
118+
kotlinx.coroutines.delay((attempt + 1) * 1000L)
119+
}
109120
}
121+
throw lastError ?: Exception("Failed to establish session after 3 attempts")
110122
}
111-
throw lastError ?: Exception("Failed to establish session after 3 attempts")
112123
}
113124

114125
// Signed GET — re-establishes session when expired or missing
@@ -137,17 +148,24 @@ class AnimeVerseProvider : MainAPI() {
137148
return cache
138149
}
139150

140-
return try {
141-
val jsonText = signedGet("/api/v1/catalog")
142-
val response = localMapper.readValue(jsonText, CatalogResponse::class.java)
143-
val items = response.items.filter { it.slug.isNotEmpty() && it.title.isNotEmpty() }
144-
catalogCache = items
145-
cacheTimestamp = current
146-
Log.d("AnimeVerse", "Catalog loaded: ${items.size} items")
147-
items
148-
} catch (e: Exception) {
149-
Log.e("AnimeVerse", "getCatalog FAILED: ${e.javaClass.simpleName}: ${e.message}")
150-
emptyList()
151+
return catalogMutex.withLock {
152+
val doubleCheckCache = catalogCache
153+
if (doubleCheckCache != null && System.currentTimeMillis() - cacheTimestamp < 10 * 60 * 1000) {
154+
return@withLock doubleCheckCache
155+
}
156+
157+
try {
158+
val jsonText = signedGet("/api/v1/catalog")
159+
val response = localMapper.readValue(jsonText, CatalogResponse::class.java)
160+
val items = response.items.filter { it.slug.isNotEmpty() && it.title.isNotEmpty() }
161+
catalogCache = items
162+
cacheTimestamp = System.currentTimeMillis()
163+
Log.d("AnimeVerse", "Catalog loaded: ${items.size} items")
164+
items
165+
} catch (e: Exception) {
166+
Log.e("AnimeVerse", "getCatalog FAILED: ${e.javaClass.simpleName}: ${e.message}")
167+
emptyList()
168+
}
151169
}
152170
}
153171

0 commit comments

Comments
 (0)