Use Try instead of Either as it makes more sens + add GroupRooms API

This commit is contained in:
ganfra 2018-11-06 16:35:06 +01:00
parent 56bbcf209f
commit 9cc3dc51cc
30 changed files with 276 additions and 254 deletions

View File

@ -6,7 +6,6 @@ import android.widget.Toast
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity
@ -43,7 +42,7 @@ class LoginActivity : RiotActivity() {
goToHome()
}

override fun onFailure(failure: Failure) {
override fun onFailure(failure: Throwable) {
progressBar.visibility = View.GONE
Toast.makeText(this@LoginActivity, "Authenticate failure: $failure", Toast.LENGTH_LONG).show()
}

View File

@ -41,13 +41,14 @@ android {

dependencies {

def arrow_version = "0.7.3"
def arrow_version = "0.8.0"
def support_version = '28.0.0'
def moshi_version = '1.7.0'
def lifecycle_version = "1.1.1"

implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'

implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:recyclerview-v7:$support_version"
@ -59,7 +60,6 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.okio:okio:1.15.0'
@ -77,6 +77,10 @@ dependencies {

// FP
implementation "io.arrow-kt:arrow-core:$arrow_version"
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
implementation "io.arrow-kt:arrow-effects:$arrow_version"
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"

// DI
implementation "org.koin:koin-core:$koin_version"

View File

@ -1,14 +1,12 @@
package im.vector.matrix.android.api

import im.vector.matrix.android.api.failure.Failure

interface MatrixCallback<in T> {

fun onSuccess(data: T) {
//no-op
}

fun onFailure(failure: Failure){
fun onFailure(failure: Throwable) {
//no-op
}


View File

@ -4,5 +4,6 @@ data class GroupSummary(
val groupId: String,
val displayName: String = "",
val shortDescription: String = "",
val avatarUrl: String = ""
val avatarUrl: String = "",
val roomIds: List<String> = emptyList()
)

View File

@ -3,8 +3,7 @@ package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

@ -19,6 +18,6 @@ interface AuthAPI {
* @param loginParams the login parameters
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: PasswordLoginParams): Deferred<Response<Credentials>>
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>

}

View File

@ -1,12 +1,9 @@
package im.vector.matrix.android.internal.auth

import android.util.Patterns
import arrow.core.Either
import arrow.core.leftIfNull
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.Credentials
@ -44,7 +41,7 @@ class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,

val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password)
sessionOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) })
sessionOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
}
return CancelableCoroutine(job)

@ -52,7 +49,7 @@ class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,

private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String,
password: String): Either<Failure, Session> = withContext(coroutineDispatchers.io) {
password: String) = withContext(coroutineDispatchers.io) {

val authAPI = buildAuthAPI(homeServerConnectionConfig)
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
@ -62,8 +59,6 @@ class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,
}
executeRequest<Credentials> {
apiCall = authAPI.login(loginParams)
}.leftIfNull {
Failure.Unknown(IllegalArgumentException("Credentials shouldn't not be null"))
}.map {
val sessionParams = SessionParams(it, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)

View File

@ -1,11 +1,12 @@
package im.vector.matrix.android.internal.auth

import arrow.core.Try
import im.vector.matrix.android.internal.auth.data.SessionParams

interface SessionParamsStore {

fun get(): SessionParams?

fun save(sessionParams: SessionParams)
fun save(sessionParams: SessionParams): Try<SessionParams>

}

View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.auth.db

import arrow.core.Try
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.auth.data.SessionParams
import io.realm.Realm
@ -8,14 +9,17 @@ import io.realm.RealmConfiguration
class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
private val realmConfiguration: RealmConfiguration) : SessionParamsStore {

override fun save(sessionParams: SessionParams) {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.insert(entity)
override fun save(sessionParams: SessionParams): Try<SessionParams> {
return Try {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.insert(entity)
}
realm.close()
}
realm.close()
sessionParams
}
}


View File

@ -11,7 +11,8 @@ object GroupSummaryMapper {
roomSummaryEntity.groupId,
roomSummaryEntity.displayName,
roomSummaryEntity.shortDescription,
roomSummaryEntity.avatarUrl
roomSummaryEntity.avatarUrl,
roomSummaryEntity.roomIds.toList()
)
}
}

View File

@ -1,12 +1,14 @@
package im.vector.matrix.android.internal.database.model

import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
var displayName: String = "",
var shortDescription: String = "",
var avatarUrl: String = ""
var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList()
) : RealmObject() {

companion object

View File

@ -5,7 +5,6 @@ import im.vector.matrix.android.api.thread.MainThreadExecutor
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.asCoroutineDispatcher
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module

View File

@ -1,6 +1,5 @@
package im.vector.matrix.android.internal.di

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import okhttp3.OkHttpClient
@ -8,7 +7,6 @@ import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
import retrofit2.CallAdapter
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
@ -48,10 +46,6 @@ class NetworkModule : Module {
MoshiConverterFactory.create(get()) as Converter.Factory
}

single {
CoroutineCallAdapterFactory() as CallAdapter.Factory
}

single {
NetworkConnectivityChecker(get())
}
@ -60,7 +54,6 @@ class NetworkModule : Module {
Retrofit.Builder()
.client(get())
.addConverterFactory(get())
.addCallAdapterFactory(get())
}

}.invoke()

View File

@ -1,54 +1,50 @@
package im.vector.matrix.android.internal.network

import arrow.core.Either
import arrow.core.Try
import arrow.core.failure
import arrow.core.recoverWith
import arrow.effects.IO
import arrow.effects.fix
import arrow.effects.instances.io.async.async
import arrow.integrations.retrofit.adapter.runAsync
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.di.MoshiProvider
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.coroutineScope
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.Call
import java.io.IOException

suspend inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()

class Request<DATA> {

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

suspend fun execute(): Either<Failure, DATA?> = coroutineScope {
try {
val response = apiCall.await()
fun execute(): Try<DATA> {
return Try {
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
if (response.isSuccessful) {
val result = response.body()
Either.Right(result)
response.body() ?: throw IllegalStateException("The request returned a null body")
} else {
val failure = manageFailure(response.errorBody())
Either.Left(failure)
}

} catch (e: Exception) {
when (e) {
is IOException -> Either.Left(Failure.NetworkConnection(e))
else -> Either.Left(Failure.Unknown(e))
throw manageFailure(response.errorBody())
}
}.recoverWith {
when (it) {
is IOException -> Failure.NetworkConnection(it)
is Failure.ServerError -> it
else -> Failure.Unknown(it)
}.failure()
}
}

private fun manageFailure(errorBody: ResponseBody?): Failure {
return try {
val matrixError = errorBody?.let {
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
matrixErrorAdapter.fromJson(errorBody.source())
} ?: throw RuntimeException("Matrix error should not be null")

Failure.ServerError(matrixError)

} catch (e: Exception) {
Failure.Unknown(e)
}
private fun manageFailure(errorBody: ResponseBody?): Throwable {
val matrixError = errorBody?.let {
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
matrixErrorAdapter.fromJson(errorBody.source())
} ?: return RuntimeException("Matrix error should not be null")
return Failure.ServerError(matrixError)
}

}

View File

@ -0,0 +1,70 @@
package im.vector.matrix.android.internal.session.group

import arrow.core.Try
import arrow.core.fix
import arrow.instances.`try`.monad.monad
import arrow.typeclasses.binding
import com.zhuinden.monarchy.Monarchy
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.executeRequest
import im.vector.matrix.android.internal.session.group.model.GroupRooms
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
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

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

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

private suspend fun execute(groupId: String) = withContext(coroutineDispatchers.io) {
Try.monad().binding {
val groupSummary = executeRequest<GroupSummaryResponse> {
apiCall = groupAPI.getSummary(groupId)
}.bind()

val groupRooms = executeRequest<GroupRooms> {
apiCall = groupAPI.getRooms(groupId)
}.bind()
insertInDb(groupSummary, groupRooms, groupId).bind()
}.fix()
}

private fun insertInDb(groupSummary: GroupSummaryResponse, groupRooms: GroupRooms, groupId: String): Try<Unit> {
return monarchy
.tryTransactionSync { realm ->
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
?: realm.createObject(groupId)

groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""

val roomIds = groupRooms.rooms.mapNotNull { it.roomId }
groupSummaryEntity.roomIds.clear()
groupSummaryEntity.roomIds.addAll(roomIds)
}
}


}

View File

@ -1,68 +0,0 @@
package im.vector.matrix.android.internal.session.group

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
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.executeRequest
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class GetGroupSummaryRequest(
private val groupAPI: GroupAPI,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) {

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

private suspend fun execute(groupId: String) = withContext(coroutineDispatchers.io) {

return@withContext executeRequest<GroupSummaryResponse> {
apiCall = groupAPI.getSummary(groupId)
}.leftIfNull {
Failure.Unknown(RuntimeException("GroupSummary shouldn't be null"))
}.flatMap { groupSummary ->
try {
insertInDb(groupSummary, groupId)
Either.right(groupSummary)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
}
}

private fun insertInDb(groupSummary: GroupSummaryResponse, groupId: String) {
monarchy.runTransactionSync { realm ->
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
?: realm.createObject(groupId)

groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""


}
}


}

View File

@ -1,9 +1,9 @@
package im.vector.matrix.android.internal.session.group

import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.group.model.GroupRooms
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

@ -15,7 +15,15 @@ interface GroupAPI {
* @param groupId the group id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary")
fun getSummary(@Path("groupId") groupId: String): Deferred<Response<GroupSummaryResponse>>
fun getSummary(@Path("groupId") groupId: String): Call<GroupSummaryResponse>

/**
* Request the rooms list.
*
* @param groupId the group id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms")
fun getRooms(@Path("groupId") groupId: String): Call<GroupRooms>


}

View File

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

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

}.invoke()

View File

@ -7,12 +7,11 @@ import im.vector.matrix.android.api.session.group.Group
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean

internal class GroupSummaryUpdater(private val monarchy: Monarchy,
private val getGroupSummaryRequest: GetGroupSummaryRequest
private val getGroupDataRequest: GetGroupDataRequest
) : Observer<Monarchy.ManagedChangeSet<GroupEntity>> {

private var isStarted = AtomicBoolean(false)
@ -57,7 +56,7 @@ internal class GroupSummaryUpdater(private val monarchy: Monarchy,
if (group == null) {
return
}
getGroupSummaryRequest.execute(group.groupId, object : MatrixCallback<GroupSummaryResponse> {})
getGroupDataRequest.execute(group.groupId, object : MatrixCallback<Boolean> {})
}

}

View File

@ -0,0 +1,19 @@
package im.vector.matrix.android.internal.session.group.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class GroupRoom(

@Json(name = "aliases") val aliases: List<String>? = null,
@Json(name = "canonical_alias") val canonicalAlias: String? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "num_joined_members") val numJoinedMembers: Int = 0,
@Json(name = "room_id") val roomId: String? = null,
@Json(name = "topic") val topic: String? = null,
@Json(name = "world_readable") val worldReadable: Boolean = false,
@Json(name = "guest_can_join") val guestCanJoin: Boolean = false,
@Json(name = "avatar_url") val avatarUrl: String? = null

)

View File

@ -0,0 +1,12 @@
package im.vector.matrix.android.internal.session.group.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class GroupRooms(

@Json(name = "total_room_count_estimate") val totalRoomCountEstimate: Int? = null,
@Json(name = "chunk") val rooms: List<GroupRoom> = emptyList()

)

View File

@ -4,6 +4,7 @@ import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
import kotlinx.coroutines.Deferred
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
@ -26,7 +27,7 @@ interface RoomAPI {
@Query("dir") dir: String,
@Query("limit") limit: Int,
@Query("filter") filter: String?
): Deferred<Response<TokenChunkEvent>>
): Call<TokenChunkEvent>


/**
@ -42,7 +43,7 @@ interface RoomAPI {
@Query("at") syncToken: String?,
@Query("membership") membership: String?,
@Query("not_membership") notMembership: String?
): Deferred<Response<RoomMembersResponse>>
): Call<RoomMembersResponse>


}

View File

@ -1,11 +1,8 @@
package im.vector.matrix.android.internal.session.room.members

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.RoomEntity
@ -15,6 +12,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -31,7 +29,7 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val responseOrFailure = execute(roomId, streamToken, excludeMembership)
responseOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(true) })
responseOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(true) })
}
return CancelableCoroutine(job)
}
@ -41,35 +39,30 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
streamToken: String?,
excludeMembership: Membership?) = withContext(coroutineDispatchers.io) {

return@withContext executeRequest<RoomMembersResponse> {
executeRequest<RoomMembersResponse> {
apiCall = roomAPI.getMembers(roomId, null, null, excludeMembership?.value)
}.leftIfNull {
Failure.Unknown(RuntimeException("RoomMembersResponse shouldn't be null"))
}.flatMap { response ->
try {
insertInDb(response, roomId)
Either.right(response)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
insertInDb(response, roomId)
}
}

private fun insertInDb(response: RoomMembersResponse, roomId: String) {
monarchy.runTransactionSync { realm ->
// We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> {
return monarchy
.tryTransactionSync { realm ->
// We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")

val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }

val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
if (!roomEntity.chunks.contains(chunk)) {
roomEntity.chunks.add(chunk)
}
roomEntity.areAllMembersLoaded = true
}
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
if (!roomEntity.chunks.contains(chunk)) {
roomEntity.chunks.add(chunk)
}
roomEntity.areAllMembersLoaded = true
}
.map { response }
}

}

View File

@ -1,11 +1,9 @@
package im.vector.matrix.android.internal.session.room.timeline

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import arrow.core.Try
import arrow.core.failure
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -21,6 +19,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -40,7 +39,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
val job = GlobalScope.launch(coroutineDispatchers.main) {
val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
val chunkOrFailure = execute(roomId, from, direction, limit, filter)
chunkOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) })
chunkOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
}
return CancelableCoroutine(job)
}
@ -52,66 +51,59 @@ class PaginationRequest(private val roomAPI: RoomAPI,
filter: String?) = withContext(coroutineDispatchers.io) {

if (from == null) {
return@withContext Either.left(
Failure.Unknown(RuntimeException("From token shouldn't be null"))
)
return@withContext RuntimeException("From token shouldn't be null").failure<TokenChunkEvent>()
}
return@withContext executeRequest<TokenChunkEvent> {
executeRequest<TokenChunkEvent> {
apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter)
}.leftIfNull {
Failure.Unknown(RuntimeException("TokenChunkEvent shouldn't be null"))
}.flatMap { chunk ->
try {
insertInDb(chunk, roomId)
Either.right(chunk)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
insertInDb(chunk, roomId)
}
}

private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) {
monarchy.runTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String): Try<TokenChunkEvent> {
return monarchy
.tryTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")

val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken)
?: realm.createObject()
val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken)
?: realm.createObject()

currentChunk.prevToken = receivedChunk.prevToken
currentChunk.prevToken = receivedChunk.prevToken

val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken)
val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken)

val eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! }
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds)
val hasOverlapped = chunksOverlapped.isNotEmpty()
val eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! }
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds)
val hasOverlapped = chunksOverlapped.isNotEmpty()

val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents)
if (!roomEntity.chunks.contains(stateEventsChunk)) {
roomEntity.chunks.add(stateEventsChunk)
}
val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents)
if (!roomEntity.chunks.contains(stateEventsChunk)) {
roomEntity.chunks.add(stateEventsChunk)
}

receivedChunk.events.addManagedToChunk(currentChunk)
receivedChunk.events.addManagedToChunk(currentChunk)

if (prevChunk != null) {
currentChunk.events.addAll(prevChunk.events)
roomEntity.chunks.remove(prevChunk)
if (prevChunk != null) {
currentChunk.events.addAll(prevChunk.events)
roomEntity.chunks.remove(prevChunk)

} else if (hasOverlapped) {
chunksOverlapped.forEach { overlapped ->
overlapped.events.forEach { event ->
if (!currentChunk.events.fastContains(event)) {
currentChunk.events.add(event)
} else if (hasOverlapped) {
chunksOverlapped.forEach { overlapped ->
overlapped.events.forEach { event ->
if (!currentChunk.events.fastContains(event)) {
currentChunk.events.add(event)
}
}
currentChunk.prevToken = overlapped.prevToken
roomEntity.chunks.remove(overlapped)
}
}
currentChunk.prevToken = overlapped.prevToken
roomEntity.chunks.remove(overlapped)
if (!roomEntity.chunks.contains(currentChunk)) {
roomEntity.chunks.add(currentChunk)
}
}
}
if (!roomEntity.chunks.contains(currentChunk)) {
roomEntity.chunks.add(currentChunk)
}
}
.map { receivedChunk }
}

}

View File

@ -3,7 +3,6 @@ package im.vector.matrix.android.internal.session.room.timeline
import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
@ -54,7 +53,7 @@ class TimelineBoundaryCallback(private val roomId: String,
pagingRequestCallback.recordSuccess()
}

override fun onFailure(failure: Failure) {
override fun onFailure(failure: Throwable) {
pagingRequestCallback.recordFailure(failure)
}
}

View File

@ -1,15 +1,14 @@
package im.vector.matrix.android.internal.session.sync

import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred
import retrofit2.Response
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.QueryMap

interface SyncAPI {

@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync")
fun sync(@QueryMap params: Map<String, String>): Deferred<Response<SyncResponse>>
fun sync(@QueryMap params: Map<String, String>): Call<SyncResponse>

}

View File

@ -1,10 +1,6 @@
package im.vector.matrix.android.internal.session.sync

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.legacy.rest.model.filter.FilterBody
import im.vector.matrix.android.internal.legacy.util.FilterUtil
@ -24,7 +20,7 @@ internal class SyncRequest(private val syncAPI: SyncAPI,
fun execute(token: String?, callback: MatrixCallback<SyncResponse>): Cancelable {
val job = GlobalScope.launch {
val syncOrFailure = execute(token)
syncOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) })
syncOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
}
return CancelableCoroutine(job)
}
@ -42,15 +38,8 @@ internal class SyncRequest(private val syncAPI: SyncAPI,
params["filter"] = filterBody.toJSONString()
executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params)
}.leftIfNull {
Failure.Unknown(RuntimeException("Sync response shouln't be null"))
}.flatMap {
try {
syncResponseHandler.handleResponse(it, token, false)
Either.right(it)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
}.flatMap { syncResponse ->
syncResponseHandler.handleResponse(syncResponse, token, false)
}
}


View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.session.sync

import arrow.core.Try
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import timber.log.Timber

@ -7,21 +8,20 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler) {

fun handleResponse(syncResponse: SyncResponse?, fromToken: String?, isCatchingUp: Boolean) {
if (syncResponse == null) {
return
}
Timber.v("Handle sync response")
if (syncResponse.rooms != null) {
roomSyncHandler.handle(syncResponse.rooms)
}
if (syncResponse.groups != null) {
groupSyncHandler.handle(syncResponse.groups)
}
if (syncResponse.accountData != null) {
userAccountDataSyncHandler.handle(syncResponse.accountData)
fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
return Try {
Timber.v("Handle sync response")
if (syncResponse.rooms != null) {
roomSyncHandler.handle(syncResponse.rooms)
}
if (syncResponse.groups != null) {
groupSyncHandler.handle(syncResponse.groups)
}
if (syncResponse.accountData != null) {
userAccountDataSyncHandler.handle(syncResponse.accountData)
}
syncResponse
}
}


}

View File

@ -84,7 +84,7 @@ internal class SyncThread(private val syncRequest: SyncRequest,
latch.countDown()
}

override fun onFailure(failure: Failure) {
override fun onFailure(failure: Throwable) {
if (failure !is Failure.NetworkConnection) {
// Wait 10s before retrying
sleep(RETRY_WAIT_TIME_MS)

View File

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

import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm

fun Monarchy.tryTransactionSync(transaction: (realm: Realm) -> Unit): Try<Unit> {
return Try {
this.runTransactionSync(transaction)
}
}

fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit): Try<Unit> {
return Try {
this.writeAsync(transaction)
}
}