Request : add some way to retry + introduce RequestExecutor

This commit is contained in:
ganfra 2018-11-08 13:56:07 +01:00
parent 240b4715fc
commit b2bb89ac94
8 changed files with 75 additions and 9 deletions

View File

@ -5,7 +5,7 @@ import java.io.IOException
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {

data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
data class NetworkConnection(val ioException: IOException) : Failure(ioException)
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
data class ServerError(val error: MatrixError) : Failure(RuntimeException(error.toString()))

abstract class FeatureFailure : Failure()

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.di

import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.RequestExecutor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.context.ModuleDefinition
@ -50,6 +51,10 @@ class NetworkModule : Module {
NetworkConnectivityChecker(get())
}

single {
RequestExecutor(get(), get())
}

factory {
Retrofit.Builder()
.client(get())

View File

@ -19,7 +19,7 @@ internal inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Req

internal class Request<DATA> {

var moshi: Moshi = MoshiProvider.providesMoshi()
private val moshi: Moshi = MoshiProvider.providesMoshi()
lateinit var apiCall: Call<DATA>

fun execute(): Try<DATA> {

View File

@ -0,0 +1,34 @@
package im.vector.matrix.android.internal.network

import arrow.core.Try
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.retry
import kotlinx.coroutines.withContext

internal class RequestExecutor(private val networkConnectivityChecker: NetworkConnectivityChecker,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {


suspend fun <T> execute(retryTimes: Int = Int.MAX_VALUE,
initialDelay: Long = 100,
maxDelay: Long = 10_000,
factor: Double = 2.0,
block: suspend () -> Try<T>): Try<T> = withContext(coroutineDispatchers.io) {

retry(retryTimes, initialDelay, maxDelay, factor) {
executeIfConnected { block() }
}

}

private suspend fun <T> executeIfConnected(block: suspend () -> Try<T>): Try<T> {
return if (networkConnectivityChecker.isConnected()) {
block()
} else {
Try.raise(Failure.NetworkConnection())
}
}


}

View File

@ -9,6 +9,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.RequestExecutor
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.group.model.GroupRooms
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
@ -19,26 +20,27 @@ import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

internal class GetGroupDataRequest(
private val groupAPI: GroupAPI,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val requestExecutor: RequestExecutor
) {

fun execute(groupId: String,
callback: MatrixCallback<Boolean>
): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val groupOrFailure = execute(groupId)
val groupOrFailure = requestExecutor.execute { getGroupData(groupId) }
groupOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(true) })
}
return CancelableCoroutine(job)
}

private suspend fun execute(groupId: String) = withContext(coroutineDispatchers.io) {
Try.monad().binding {
private fun getGroupData(groupId: String): Try<Unit> {
return Try.monad().binding {

val groupSummary = executeRequest<GroupSummaryResponse> {
apiCall = groupAPI.getSummary(groupId)
}.bind()
@ -50,6 +52,7 @@ internal class GetGroupDataRequest(
val groupUsers = executeRequest<GroupUsers> {
apiCall = groupAPI.getUsers(groupId)
}.bind()

insertInDb(groupSummary, groupRooms, groupUsers, groupId).bind()
}.fix()
}
@ -61,7 +64,7 @@ internal class GetGroupDataRequest(
return monarchy
.tryTransactionSync { realm ->
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
?: realm.createObject(groupId)
?: realm.createObject(groupId)

groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name

View File

@ -16,7 +16,7 @@ class GroupModule : Module {
}

scope(DefaultSession.SCOPE) {
GetGroupDataRequest(get(), get(), get())
GetGroupDataRequest(get(), get(), get(), get())
}

}.invoke()

View File

@ -0,0 +1,24 @@
package im.vector.matrix.android.internal.util

import arrow.core.Try
import kotlinx.coroutines.delay

suspend fun <T> retry(
times: Int = Int.MAX_VALUE,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 10_000, // 10 second
factor: Double = 2.0,
block: suspend () -> Try<T>): Try<T> {

var currentDelay = initialDelay
repeat(times - 1) {
val blockResult = block()
if (blockResult.isSuccess()) {
return blockResult
} else {
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
}
return block()
}