diff --git a/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt index fd736daedb59396f9709d241f77be2cfd05608d5..f5c5cdd50a9099331500746b5aa7f51ba3e1757e 100644 --- a/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt @@ -21,12 +21,18 @@ 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_429_OR_401 = "429|401" +private const val MAX_RETRY_DELAY_IN_SECONDS = 300 +private const val ONE_SECOND_IN_MILLIS = 1000L +private const val INITIAL_DELAY_RETRY_IN_SECONDS = 10 suspend fun handleNetworkResult(call: suspend () -> T): ResultSupreme { return try { @@ -71,3 +77,40 @@ private fun extractErrorMessage(e: Exception): String { } return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status" } + +suspend fun retryWithBackoff(retryDelayInSeconds: Int = -1, operation: suspend () -> T): T? { + var result: T? = null + try { + if (retryDelayInSeconds > 0) { + delay(ONE_SECOND_IN_MILLIS * retryDelayInSeconds) + } + + result = operation() + + if (shouldRetry(result, retryDelayInSeconds)) { + Timber.w("Retrying...: $retryDelayInSeconds") + result = retryWithBackoff(calculateRetryDelay(retryDelayInSeconds), operation) + } + + } catch (e: Exception) { + Timber.e(e) + if (retryDelayInSeconds < MAX_RETRY_DELAY_IN_SECONDS) { + return retryWithBackoff(calculateRetryDelay(retryDelayInSeconds), operation) + } + } + + return result +} + +private fun calculateRetryDelay(retryDelayInSecond: Int) = + if (retryDelayInSecond < 0) INITIAL_DELAY_RETRY_IN_SECONDS else retryDelayInSecond * 2 + + +private fun 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 { + // Here, (value != true) is used, because value can be null also and we want to allow retry for null message + return exception?.message?.contains(Regex(REGEX_429_OR_401)) != true +} diff --git a/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt b/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt index 0c89311e6044cc3347198ccbc2b096876fa3f36d..b408ff7f9bdd847bf30c5e532de6af3302ad3e99 100644 --- a/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/data/login/PlayStoreAuthenticator.kt @@ -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,27 @@ class PlayStoreAuthenticator @Inject constructor( override suspend fun login(): AuthObject.GPlayAuth { val savedAuth = getSavedAuthData() - val authData = ( - savedAuth ?: run { - // if no saved data, then generate new auth data. - generateAuthData().let { - if (it.isSuccess()) it.data!! - else return AuthObject.GPlayAuth(it, user) - } + val authData = savedAuth ?: run { + // if no saved data, then generate new auth data. + val result = retryWithBackoff { + generateAuthData() } - ) - val formattedAuthData = formatAuthData(authData) - formattedAuthData.locale = locale + result?.let { + if (it.isSuccess()) it.data!! + else return AuthObject.GPlayAuth(it, user) + } + } + + val formattedAuthData = authData?.let { formatAuthData(it) } + formattedAuthData?.locale = locale val result: ResultSupreme = 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) }