Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d8a3f95f authored by Hasib Prince's avatar Hasib Prince
Browse files

added retry with exponential backoff

parent 6134e8c2
Loading
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -21,12 +21,17 @@ package foundation.e.apps.data
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import foundation.e.apps.data.playstore.utils.GplayHttpRequestException
import foundation.e.apps.data.login.exceptions.GPlayException
import kotlinx.coroutines.delay
import timber.log.Timber
import java.net.SocketTimeoutException

private const val TIMEOUT = "Timeout"
private const val UNKNOWN = "Unknown"
private const val STATUS = "Status:"
private const val ERROR_GPLAY_API = "Gplay api has faced error!"
private const val REGEX_CONTAIN_429_OR_401 = "429|401"
private const val MAX_RETRY_DELAY_IN_SECONDS = 300
private const val ONE_SECOND_IN_MILLIS = 1000L

suspend fun <T> handleNetworkResult(call: suspend () -> T): ResultSupreme<T> {
    return try {
@@ -71,3 +76,39 @@ private fun extractErrorMessage(e: Exception): String {
    }
    return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status"
}

suspend fun <T> retryWithBackoff(operation: suspend () -> T, retryDelayInSecond: Int = -1): T? {
    try {
        if (retryDelayInSecond > 0) {
            delay(ONE_SECOND_IN_MILLIS * retryDelayInSecond)
        }

        val result = operation()

        if (shouldRetry(result, retryDelayInSecond)) {
            Timber.w("Retrying...: $retryDelayInSecond")
            return retryWithBackoff(operation, calculateRetryDelay(retryDelayInSecond))
        }

        return result
    } catch (e: Exception) {
        if (retryDelayInSecond < MAX_RETRY_DELAY_IN_SECONDS) {
            return retryWithBackoff(operation, calculateRetryDelay(retryDelayInSecond))
        }
    }

    return null
}

private fun calculateRetryDelay(retryDelayInSecond: Int) =
    if (retryDelayInSecond < 0) 5 else retryDelayInSecond * 2


private fun <T> shouldRetry(result: T, retryDelayInSecond: Int) =
    result is ResultSupreme<*> && !result.isSuccess() && retryDelayInSecond < MAX_RETRY_DELAY_IN_SECONDS
            && isExceptionAllowedToRetry(result.exception)

private fun isExceptionAllowedToRetry(exception: Exception?): Boolean {
    val message = exception?.message
    return message?.contains(Regex(REGEX_CONTAIN_429_OR_401)) != true // Here, (value != true) is used, because value can be null also and we want to allow retry for null message
}
+11 −9
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import foundation.e.apps.data.login.api.PlayStoreLoginManagerFactory
import foundation.e.apps.data.login.api.PlayStoreLoginManager
import foundation.e.apps.data.login.api.GoogleLoginManager
import foundation.e.apps.data.login.api.PlayStoreLoginWrapper
import foundation.e.apps.data.retryWithBackoff
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
@@ -77,25 +78,26 @@ class PlayStoreAuthenticator @Inject constructor(
    override suspend fun login(): AuthObject.GPlayAuth {
        val savedAuth = getSavedAuthData()

        val authData = (
            savedAuth ?: run {
        val authData = savedAuth ?: run {
            // if no saved data, then generate new auth data.
                generateAuthData().let {
            retryWithBackoff({ generateAuthData() }).let { result ->
                result?.let {
                    if (it.isSuccess()) it.data!!
                    else return AuthObject.GPlayAuth(it, user)
                }
            }
            )
        }


        val formattedAuthData = formatAuthData(authData)
        formattedAuthData.locale = locale
        val formattedAuthData = authData?.let { formatAuthData(it) }
        formattedAuthData?.locale = locale
        val result: ResultSupreme<AuthData?> = ResultSupreme.create(
            status = ResultStatus.OK,
            data = formattedAuthData
        )
        result.otherPayload = formattedAuthData.email
        result.otherPayload = formattedAuthData?.email

        if (savedAuth == null) {
        if (savedAuth == null && formattedAuthData != null) {
            saveAuthData(formattedAuthData)
        }