Skip to content

Commit

Permalink
fix rating request and flow combination
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Apr 20, 2024
1 parent 32782dd commit b98519b
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.datetime.TimeZone
import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class AiringTodayStateMachine(
Expand All @@ -38,7 +39,7 @@ class AiringTodayStateMachine(
return@onEnter state.override { State.Success(query, it) }
}

val response = CatchResult.repeat(times = 2) {
val response = CatchResult.repeat(times = 2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import dev.datlag.aniflow.model.mapError
import dev.datlag.aniflow.model.saveFirstOrNull
import dev.datlag.tooling.async.suspendCatching
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class MediumStateMachine(
private val client: ApolloClient,
private val fallbackClient: ApolloClient,
private val crashlytics: FirebaseFactory.Crashlytics?,
private val id: Int
private val id: Int,
) : FlowReduxStateMachine<MediumStateMachine.State, MediumStateMachine.Action>(
initialState = State.Loading(id)
) {
Expand All @@ -31,7 +32,7 @@ class MediumStateMachine(
currentState = it
}
onEnter { state ->
val response = CatchResult.repeat(times = 2) {
val response = CatchResult.repeat(times = 2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dev.datlag.aniflow.model.mapError
import dev.datlag.aniflow.model.saveFirstOrNull
import dev.datlag.tooling.async.suspendCatching
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class PopularNextSeasonStateMachine(
Expand All @@ -31,7 +32,7 @@ class PopularNextSeasonStateMachine(
return@onEnter state.override { SeasonState.Success(query, it) }
}

val response = CatchResult.repeat(times = 2) {
val response = CatchResult.repeat(times = 2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class PopularSeasonStateMachine(
Expand All @@ -42,7 +43,7 @@ class PopularSeasonStateMachine(
return@onEnter state.override { SeasonState.Success(query, it) }
}

val response = CatchResult.repeat(times = 2) {
val response = CatchResult.repeat(times = 2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dev.datlag.aniflow.firebase.FirebaseFactory
import dev.datlag.aniflow.model.*
import dev.datlag.tooling.async.suspendCatching
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class, ApolloExperimental::class)
class TrendingAnimeStateMachine(
Expand All @@ -32,7 +33,7 @@ class TrendingAnimeStateMachine(
return@onEnter state.override { State.Success(query, it) }
}

val response = CatchResult.repeat(2) {
val response = CatchResult.repeat(2, timeoutDuration = 30.seconds) {
val query = client.query(state.snapshot.query)

query.execute().data ?: query.toFlow().saveFirstOrNull()?.dataOrThrow()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
package dev.datlag.aniflow

import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.essenty.backhandler.backHandler
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.lifecycle.essentyLifecycle
import dev.datlag.aniflow.other.BurningSeriesResolver
import dev.datlag.aniflow.ui.navigation.RootComponent
import dev.datlag.tooling.compose.openInBrowser
import dev.datlag.tooling.compose.withIOContext
import dev.datlag.tooling.decompose.lifecycle.LocalLifecycleOwner
import dev.datlag.tooling.safeCast
import io.github.aakira.napier.Napier
import org.kodein.di.DIAware
import org.kodein.di.instance
import org.publicvalue.multiplatform.oidc.appsupport.AndroidCodeAuthFlowFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,56 @@ class TokenRefreshHandler(
private val storeUserSettings: Settings.PlatformUserSettings
) {

private val mutex = Mutex()
private var lastRefresh: Int = 0
private var memoryAccessToken: String? = null

suspend fun getAccessToken(): String? {
return mutex.withLock(storeUserSettings) {
storeUserSettings.aniList.saveFirstOrNull()?.accessToken?.ifBlank { null }
} ?: memoryAccessToken
return storeUserSettings.aniList.saveFirstOrNull()?.accessToken?.ifBlank { null } ?: memoryAccessToken
}

suspend fun refreshAndSaveToken(client: OpenIdConnectClient, oldAccessToken: String): OauthTokens {
return refreshAndSaveToken(client::refreshToken, oldAccessToken)
}

suspend fun refreshAndSaveToken(refreshCall: suspend (String) -> AccessTokenResponse, oldAccessToken: String): OauthTokens {
mutex.withLock {
val storeData = mutex.withLock(storeUserSettings) {
storeUserSettings.aniList.saveFirstOrNull()
}
val currentTokens = storeData?.let {
OauthTokens(
accessToken = it.accessToken ?: return@let null,
refreshToken = it.refreshToken,
idToken = it.idToken
)
}
val storeData = storeUserSettings.aniList.saveFirstOrNull()
val currentTokens = storeData?.let {
OauthTokens(
accessToken = it.accessToken ?: return@let null,
refreshToken = it.refreshToken,
idToken = it.idToken
)
}

val nowMinus10Minutes = Clock.System.now().minus(10.minutes).epochSeconds
val requiresRefresh = lastRefresh <= nowMinus10Minutes || nowMinus10Minutes > (storeData?.expires ?: 0)
val nowMinus10Minutes = Clock.System.now().minus(10.minutes).epochSeconds
val requiresRefresh = lastRefresh <= nowMinus10Minutes || nowMinus10Minutes > (storeData?.expires ?: 0)

return if (currentTokens != null && currentTokens.accessToken != oldAccessToken && !requiresRefresh) {
currentTokens
} else {
val refreshToken = mutex.withLock(storeUserSettings) {
storeUserSettings.aniListRefreshToken.saveFirstOrNull()
}
val newTokens = refreshCall(refreshToken ?: "")
updateStoredToken(newTokens)
lastRefresh = Clock.System.now().epochSeconds.toInt()
return if (currentTokens != null && currentTokens.accessToken != oldAccessToken && !requiresRefresh) {
currentTokens
} else {
val refreshToken = storeUserSettings.aniListRefreshToken.saveFirstOrNull()
val newTokens = refreshCall(refreshToken ?: "")
updateStoredToken(newTokens)
lastRefresh = Clock.System.now().epochSeconds.toInt()

OauthTokens(
accessToken = newTokens.access_token,
refreshToken = newTokens.refresh_token,
idToken = newTokens.id_token
)
}
OauthTokens(
accessToken = newTokens.access_token,
refreshToken = newTokens.refresh_token,
idToken = newTokens.id_token
)
}
}

suspend fun updateStoredToken(tokenResponse: AccessTokenResponse) {
mutex.withLock(storeUserSettings) {
memoryAccessToken = tokenResponse.access_token
memoryAccessToken = tokenResponse.access_token

storeUserSettings.setAniListTokens(
access = tokenResponse.access_token,
refresh = tokenResponse.refresh_token,
id = tokenResponse.id_token,
expires = (tokenResponse.refresh_token_expires_in ?: tokenResponse.expires_in)?.let {
Clock.System.now().epochSeconds + it
}?.toInt()
)
}
storeUserSettings.setAniListTokens(
access = tokenResponse.access_token,
refresh = tokenResponse.refresh_token,
id = tokenResponse.id_token,
expires = (tokenResponse.refresh_token_expires_in ?: tokenResponse.expires_in)?.let {
Clock.System.now().epochSeconds + it
}?.toInt()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import dev.datlag.aniflow.common.nullableFirebaseInstance
import dev.datlag.aniflow.common.onRenderApplyCommonScheme
import dev.datlag.aniflow.common.popular
import dev.datlag.aniflow.common.rated
import dev.datlag.aniflow.model.CatchResult
import dev.datlag.aniflow.model.alsoTrue
import dev.datlag.aniflow.model.saveFirstOrNull
import dev.datlag.aniflow.model.*
import dev.datlag.aniflow.other.Constants
import dev.datlag.aniflow.other.TokenRefreshHandler
import dev.datlag.aniflow.settings.Settings
import dev.datlag.tooling.alsoTrue
import dev.datlag.tooling.async.suspendCatching
import dev.datlag.tooling.compose.ioDispatcher
import dev.datlag.tooling.compose.withMainContext
Expand Down Expand Up @@ -147,21 +146,21 @@ class MediumScreenComponent(
)

override val episodes: StateFlow<Int> = combine(
mediumSuccessState.mapNotNull {
mediumSuccessState.map {
it?.data?.episodes
},
nextAiringEpisode
) { episodes, airing ->
if (episodes > -1) {
episodes
} else if (airing != null) {
if (Instant.fromEpochSeconds(airing.airingAt.toLong()) <= Clock.System.now()) {
airing.episode
episodes.ifValueOrNull(-1) {
if (airing != null) {
if (Instant.fromEpochSeconds(airing.airingAt.toLong()) <= Clock.System.now()) {
airing.episode
} else {
airing.episode - 1
}
} else {
airing.episode - 1
-1
}
} else {
episodes
}
}.flowOn(
context = ioDispatcher()
Expand Down Expand Up @@ -264,15 +263,15 @@ class MediumScreenComponent(

private val changedRating: MutableStateFlow<Int> = MutableStateFlow(-1)
override val rating: StateFlow<Int> = combine(
mediumSuccessState.mapNotNull {
mediumSuccessState.map {
it?.data?.entry?.score?.toInt()
}.flowOn(ioDispatcher()),
changedRating
) { t1, t2 ->
if (t2 > -1) {
t2
} else {
t1
t1 ?: t2
}
}.flowOn(
context = ioDispatcher()
Expand Down Expand Up @@ -351,7 +350,7 @@ class MediumScreenComponent(
}.asNullableSuccess()

execution?.data?.MediaList?.let { entry ->
changedRating.emit(entry.score?.toInt() ?: changedRating.value)
changedRating.update { entry.score?.toInt() ?: it }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.materialkolor.ktx.isDisliked
import dev.datlag.aniflow.LocalDarkMode
import dev.datlag.tooling.compose.ioDispatcher
import dev.datlag.tooling.compose.launchIO
import dev.datlag.tooling.compose.toLegacyColors
import dev.datlag.tooling.compose.withIOContext
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import io.github.aakira.napier.Napier
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ oidc = "0.9.2"
sekret = "2.0.0-alpha-04"
serialization = "1.6.3"
splashscreen = "1.0.1"
tooling = "1.3.2"
tooling = "1.4.0"
translate = "17.0.2"
versions = "0.51.0"
windowsize = "0.5.0"
Expand Down
39 changes: 23 additions & 16 deletions model/src/commonMain/kotlin/dev/datlag/aniflow/model/CatchResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ sealed interface CatchResult<T> {

companion object {
suspend fun <T> result(block: suspend CoroutineScope.() -> T): CatchResult<T & Any> = coroutineScope {
val result = suspendCatching(block)
val result = suspendCatching(block = block)
return@coroutineScope if (result.isFailure) {
Error(result.exceptionOrNull())
} else {
Expand All @@ -97,31 +97,38 @@ sealed interface CatchResult<T> {
suspend fun <T> repeat(
times: Int,
delayDuration: Duration = 0.seconds,
timeoutDuration: Duration = 0.seconds,
block: suspend CoroutineScope.() -> T
): CatchResult<T & Any> = coroutineScope {
var result = suspendCatching(block)
var result = if (timeoutDuration > 0.seconds) {
timeout(timeoutDuration, block)
} else {
result(block)
}
var request = 1

while (result.isFailure && request < times) {
while (result.isError && request < times) {
delay(delayDuration)
result = suspendCatching(block)
result = if (timeoutDuration > 0.seconds) {
timeout(timeoutDuration, block)
} else {
result(block)
}
request++
}
return@coroutineScope if (result.isFailure) {
Error(result.exceptionOrNull())
} else {
result.getOrNull()?.let(::Success) ?: Error(result.exceptionOrNull())
}
return@coroutineScope result
}

suspend fun <T> timeout(
time: Duration,
block: suspend CoroutineScope.() -> T
): CatchResult<T & Any> = coroutineScope {
val result: Result<T> = try {
Result.success(withTimeout(time, block))
} catch (timeout: TimeoutCancellationException) {
Result.failure(timeout)
val result = suspendCatching(
catchTimeout = true
) {
withTimeout(time) {
block()
}
}

return@coroutineScope if (result.isFailure) {
Expand All @@ -147,13 +154,13 @@ suspend inline fun <T : Any> CatchResult<T>.resultOnError(noinline block: Corout
}
}

suspend inline fun <reified M : Any> CatchResult<*>.mapError(block: () -> M?): CatchResult<M> {
inline fun <reified M : Any> CatchResult<*>.mapError(block: (Throwable?) -> M?): CatchResult<M> {
return when (this) {
is CatchResult.Error -> {
block()?.let(::Success) ?: CatchResult.Error(null)
block(this.throwable)?.let(::Success) ?: CatchResult.Error(null)
}
is Success -> {
(this.data as? M)?.let(::Success) ?: block()?.let(::Success) ?: CatchResult.Error(null)
(this.data as? M)?.let(::Success) ?: block(null)?.let(::Success) ?: CatchResult.Error(null)
}
}
}
Expand Down
Loading

0 comments on commit b98519b

Please sign in to comment.