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.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig 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.session.Session
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.RiotActivity
@ -43,7 +42,7 @@ class LoginActivity : RiotActivity() {
goToHome() goToHome()
} }
override fun onFailure(failure: Failure) { override fun onFailure(failure: Throwable) {
progressBar.visibility = View.GONE progressBar.visibility = View.GONE
Toast.makeText(this@LoginActivity, "Authenticate failure: $failure", Toast.LENGTH_LONG).show() Toast.makeText(this@LoginActivity, "Authenticate failure: $failure", Toast.LENGTH_LONG).show()
} }

View File

@ -41,13 +41,14 @@ android {
dependencies { dependencies {
def arrow_version = "0.7.3" def arrow_version = "0.8.0"
def support_version = '28.0.0' def support_version = '28.0.0'
def moshi_version = '1.7.0' def moshi_version = '1.7.0'
def lifecycle_version = "1.1.1" def lifecycle_version = "1.1.1"
implementation fileTree(dir: 'libs', include: ['*.aar']) implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 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:appcompat-v7:$support_version"
implementation "com.android.support:recyclerview-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:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson: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:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.okio:okio:1.15.0' implementation 'com.squareup.okio:okio:1.15.0'
@ -77,6 +77,10 @@ dependencies {
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" 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 // DI
implementation "org.koin:koin-core:$koin_version" implementation "org.koin:koin-core:$koin_version"

View File

@ -1,14 +1,12 @@
package im.vector.matrix.android.api package im.vector.matrix.android.api
import im.vector.matrix.android.api.failure.Failure
interface MatrixCallback<in T> { interface MatrixCallback<in T> {
fun onSuccess(data: T) { fun onSuccess(data: T) {
//no-op //no-op
} }
fun onFailure(failure: Failure){ fun onFailure(failure: Throwable) {
//no-op //no-op
} }

View File

@ -4,5 +4,6 @@ data class GroupSummary(
val groupId: String, val groupId: String,
val displayName: String = "", val displayName: String = "",
val shortDescription: 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.Credentials
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
@ -19,6 +18,6 @@ interface AuthAPI {
* @param loginParams the login parameters * @param loginParams the login parameters
*/ */
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") @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 package im.vector.matrix.android.internal.auth
import android.util.Patterns 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.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig 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.session.Session
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.Credentials 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 job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password) 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) return CancelableCoroutine(job)
@ -52,7 +49,7 @@ class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,
private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String, login: String,
password: String): Either<Failure, Session> = withContext(coroutineDispatchers.io) { password: String) = withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) { val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
@ -62,8 +59,6 @@ class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,
} }
executeRequest<Credentials> { executeRequest<Credentials> {
apiCall = authAPI.login(loginParams) apiCall = authAPI.login(loginParams)
}.leftIfNull {
Failure.Unknown(IllegalArgumentException("Credentials shouldn't not be null"))
}.map { }.map {
val sessionParams = SessionParams(it, homeServerConnectionConfig) val sessionParams = SessionParams(it, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams) sessionParamsStore.save(sessionParams)

View File

@ -1,11 +1,12 @@
package im.vector.matrix.android.internal.auth package im.vector.matrix.android.internal.auth
import arrow.core.Try
import im.vector.matrix.android.internal.auth.data.SessionParams import im.vector.matrix.android.internal.auth.data.SessionParams
interface SessionParamsStore { interface SessionParamsStore {
fun get(): SessionParams? 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 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.SessionParamsStore
import im.vector.matrix.android.internal.auth.data.SessionParams import im.vector.matrix.android.internal.auth.data.SessionParams
import io.realm.Realm import io.realm.Realm
@ -8,14 +9,17 @@ import io.realm.RealmConfiguration
class RealmSessionParamsStore(private val mapper: SessionParamsMapper, class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
private val realmConfiguration: RealmConfiguration) : SessionParamsStore { private val realmConfiguration: RealmConfiguration) : SessionParamsStore {
override fun save(sessionParams: SessionParams) { override fun save(sessionParams: SessionParams): Try<SessionParams> {
val entity = mapper.map(sessionParams) return Try {
if (entity != null) { val entity = mapper.map(sessionParams)
val realm = Realm.getInstance(realmConfiguration) if (entity != null) {
realm.executeTransaction { val realm = Realm.getInstance(realmConfiguration)
it.insert(entity) realm.executeTransaction {
it.insert(entity)
}
realm.close()
} }
realm.close() sessionParams
} }
} }

View File

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

View File

@ -1,12 +1,14 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
open class GroupSummaryEntity(@PrimaryKey var groupId: String = "", open class GroupSummaryEntity(@PrimaryKey var groupId: String = "",
var displayName: String = "", var displayName: String = "",
var shortDescription: String = "", var shortDescription: String = "",
var avatarUrl: String = "" var avatarUrl: String = "",
var roomIds: RealmList<String> = RealmList()
) : RealmObject() { ) : RealmObject() {
companion object 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.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module

View File

@ -1,6 +1,5 @@
package im.vector.matrix.android.internal.di 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.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -8,7 +7,6 @@ import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module
import org.koin.dsl.module.module import org.koin.dsl.module.module
import retrofit2.CallAdapter
import retrofit2.Converter import retrofit2.Converter
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
@ -48,10 +46,6 @@ class NetworkModule : Module {
MoshiConverterFactory.create(get()) as Converter.Factory MoshiConverterFactory.create(get()) as Converter.Factory
} }
single {
CoroutineCallAdapterFactory() as CallAdapter.Factory
}
single { single {
NetworkConnectivityChecker(get()) NetworkConnectivityChecker(get())
} }
@ -60,7 +54,6 @@ class NetworkModule : Module {
Retrofit.Builder() Retrofit.Builder()
.client(get()) .client(get())
.addConverterFactory(get()) .addConverterFactory(get())
.addCallAdapterFactory(get())
} }
}.invoke() }.invoke()

View File

@ -1,54 +1,50 @@
package im.vector.matrix.android.internal.network 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 com.squareup.moshi.Moshi
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.coroutineScope
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Response import retrofit2.Call
import java.io.IOException 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> { class Request<DATA> {
var moshi: Moshi = MoshiProvider.providesMoshi() var moshi: Moshi = MoshiProvider.providesMoshi()
lateinit var apiCall: Deferred<Response<DATA>> lateinit var apiCall: Call<DATA>
suspend fun execute(): Either<Failure, DATA?> = coroutineScope { fun execute(): Try<DATA> {
try { return Try {
val response = apiCall.await() val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
if (response.isSuccessful) { if (response.isSuccessful) {
val result = response.body() response.body() ?: throw IllegalStateException("The request returned a null body")
Either.Right(result)
} else { } else {
val failure = manageFailure(response.errorBody()) throw manageFailure(response.errorBody())
Either.Left(failure)
}
} catch (e: Exception) {
when (e) {
is IOException -> Either.Left(Failure.NetworkConnection(e))
else -> Either.Left(Failure.Unknown(e))
} }
}.recoverWith {
when (it) {
is IOException -> Failure.NetworkConnection(it)
is Failure.ServerError -> it
else -> Failure.Unknown(it)
}.failure()
} }
} }
private fun manageFailure(errorBody: ResponseBody?): Failure { private fun manageFailure(errorBody: ResponseBody?): Throwable {
return try { val matrixError = errorBody?.let {
val matrixError = errorBody?.let { val matrixErrorAdapter = moshi.adapter(MatrixError::class.java)
val matrixErrorAdapter = moshi.adapter(MatrixError::class.java) matrixErrorAdapter.fromJson(errorBody.source())
matrixErrorAdapter.fromJson(errorBody.source()) } ?: return RuntimeException("Matrix error should not be null")
} ?: throw RuntimeException("Matrix error should not be null") return Failure.ServerError(matrixError)
Failure.ServerError(matrixError)
} catch (e: Exception) {
Failure.Unknown(e)
}
} }
} }

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 package im.vector.matrix.android.internal.session.group
import im.vector.matrix.android.internal.network.NetworkConstants 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 im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import kotlinx.coroutines.Deferred import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
@ -15,7 +15,15 @@ interface GroupAPI {
* @param groupId the group id * @param groupId the group id
*/ */
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary") @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) { scope(DefaultSession.SCOPE) {
GetGroupSummaryRequest(get(), get(), get()) GetGroupDataRequest(get(), get(), get())
} }
}.invoke() }.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.mapper.asDomain
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
internal class GroupSummaryUpdater(private val monarchy: Monarchy, internal class GroupSummaryUpdater(private val monarchy: Monarchy,
private val getGroupSummaryRequest: GetGroupSummaryRequest private val getGroupDataRequest: GetGroupDataRequest
) : Observer<Monarchy.ManagedChangeSet<GroupEntity>> { ) : Observer<Monarchy.ManagedChangeSet<GroupEntity>> {
private var isStarted = AtomicBoolean(false) private var isStarted = AtomicBoolean(false)
@ -57,7 +56,7 @@ internal class GroupSummaryUpdater(private val monarchy: Monarchy,
if (group == null) { if (group == null) {
return 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.members.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
@ -26,7 +27,7 @@ interface RoomAPI {
@Query("dir") dir: String, @Query("dir") dir: String,
@Query("limit") limit: Int, @Query("limit") limit: Int,
@Query("filter") filter: String? @Query("filter") filter: String?
): Deferred<Response<TokenChunkEvent>> ): Call<TokenChunkEvent>
/** /**
@ -42,7 +43,7 @@ interface RoomAPI {
@Query("at") syncToken: String?, @Query("at") syncToken: String?,
@Query("membership") membership: String?, @Query("membership") membership: String?,
@Query("not_membership") notMembership: 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 package im.vector.matrix.android.internal.session.room.members
import arrow.core.Either import arrow.core.Try
import arrow.core.flatMap
import arrow.core.leftIfNull
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback 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.session.room.model.Membership
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.RoomEntity 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.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -31,7 +29,7 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
): Cancelable { ): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val responseOrFailure = execute(roomId, streamToken, excludeMembership) 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) return CancelableCoroutine(job)
} }
@ -41,35 +39,30 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
streamToken: String?, streamToken: String?,
excludeMembership: Membership?) = withContext(coroutineDispatchers.io) { excludeMembership: Membership?) = withContext(coroutineDispatchers.io) {
return@withContext executeRequest<RoomMembersResponse> { executeRequest<RoomMembersResponse> {
apiCall = roomAPI.getMembers(roomId, null, null, excludeMembership?.value) apiCall = roomAPI.getMembers(roomId, null, null, excludeMembership?.value)
}.leftIfNull {
Failure.Unknown(RuntimeException("RoomMembersResponse shouldn't be null"))
}.flatMap { response -> }.flatMap { response ->
try { insertInDb(response, roomId)
insertInDb(response, roomId)
Either.right(response)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
} }
} }
private fun insertInDb(response: RoomMembersResponse, roomId: String) { private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> {
monarchy.runTransactionSync { realm -> return monarchy
// We ignore all the already known members .tryTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst() // We ignore all the already known members
?: throw IllegalStateException("You shouldn't use this method without a room") 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 roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
if (!roomEntity.chunks.contains(chunk)) { if (!roomEntity.chunks.contains(chunk)) {
roomEntity.chunks.add(chunk) roomEntity.chunks.add(chunk)
} }
roomEntity.areAllMembersLoaded = true roomEntity.areAllMembersLoaded = true
} }
.map { response }
} }
} }

View File

@ -1,11 +1,9 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Either import arrow.core.Try
import arrow.core.flatMap import arrow.core.failure
import arrow.core.leftIfNull
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback 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.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addManagedToChunk import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity 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.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -40,7 +39,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString() val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
val chunkOrFailure = execute(roomId, from, direction, limit, filter) 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) return CancelableCoroutine(job)
} }
@ -52,66 +51,59 @@ class PaginationRequest(private val roomAPI: RoomAPI,
filter: String?) = withContext(coroutineDispatchers.io) { filter: String?) = withContext(coroutineDispatchers.io) {
if (from == null) { if (from == null) {
return@withContext Either.left( return@withContext RuntimeException("From token shouldn't be null").failure<TokenChunkEvent>()
Failure.Unknown(RuntimeException("From token shouldn't be null"))
)
} }
return@withContext executeRequest<TokenChunkEvent> { executeRequest<TokenChunkEvent> {
apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter) apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter)
}.leftIfNull {
Failure.Unknown(RuntimeException("TokenChunkEvent shouldn't be null"))
}.flatMap { chunk -> }.flatMap { chunk ->
try { insertInDb(chunk, roomId)
insertInDb(chunk, roomId)
Either.right(chunk)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
} }
} }
private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) { private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String): Try<TokenChunkEvent> {
monarchy.runTransactionSync { realm -> return monarchy
val roomEntity = RoomEntity.where(realm, roomId).findFirst() .tryTransactionSync { realm ->
?: throw IllegalStateException("You shouldn't use this method without a room") 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) val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken)
?: realm.createObject() ?: 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 eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! }
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds) val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds)
val hasOverlapped = chunksOverlapped.isNotEmpty() val hasOverlapped = chunksOverlapped.isNotEmpty()
val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents) val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents)
if (!roomEntity.chunks.contains(stateEventsChunk)) { if (!roomEntity.chunks.contains(stateEventsChunk)) {
roomEntity.chunks.add(stateEventsChunk) roomEntity.chunks.add(stateEventsChunk)
} }
receivedChunk.events.addManagedToChunk(currentChunk) receivedChunk.events.addManagedToChunk(currentChunk)
if (prevChunk != null) { if (prevChunk != null) {
currentChunk.events.addAll(prevChunk.events) currentChunk.events.addAll(prevChunk.events)
roomEntity.chunks.remove(prevChunk) roomEntity.chunks.remove(prevChunk)
} else if (hasOverlapped) { } else if (hasOverlapped) {
chunksOverlapped.forEach { overlapped -> chunksOverlapped.forEach { overlapped ->
overlapped.events.forEach { event -> overlapped.events.forEach { event ->
if (!currentChunk.events.fastContains(event)) { if (!currentChunk.events.fastContains(event)) {
currentChunk.events.add(event) currentChunk.events.add(event)
}
}
currentChunk.prevToken = overlapped.prevToken
roomEntity.chunks.remove(overlapped)
} }
} }
currentChunk.prevToken = overlapped.prevToken if (!roomEntity.chunks.contains(currentChunk)) {
roomEntity.chunks.remove(overlapped) roomEntity.chunks.add(currentChunk)
}
} }
} .map { receivedChunk }
if (!roomEntity.chunks.contains(currentChunk)) {
roomEntity.chunks.add(currentChunk)
}
}
} }
} }

View File

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

View File

@ -1,15 +1,14 @@
package im.vector.matrix.android.internal.session.sync 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 im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import retrofit2.Response import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.QueryMap import retrofit2.http.QueryMap
interface SyncAPI { interface SyncAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") @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 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.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable 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.rest.model.filter.FilterBody
import im.vector.matrix.android.internal.legacy.util.FilterUtil 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 { fun execute(token: String?, callback: MatrixCallback<SyncResponse>): Cancelable {
val job = GlobalScope.launch { val job = GlobalScope.launch {
val syncOrFailure = execute(token) val syncOrFailure = execute(token)
syncOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) }) syncOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
} }
return CancelableCoroutine(job) return CancelableCoroutine(job)
} }
@ -42,15 +38,8 @@ internal class SyncRequest(private val syncAPI: SyncAPI,
params["filter"] = filterBody.toJSONString() params["filter"] = filterBody.toJSONString()
executeRequest<SyncResponse> { executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params) apiCall = syncAPI.sync(params)
}.leftIfNull { }.flatMap { syncResponse ->
Failure.Unknown(RuntimeException("Sync response shouln't be null")) syncResponseHandler.handleResponse(syncResponse, token, false)
}.flatMap {
try {
syncResponseHandler.handleResponse(it, token, false)
Either.right(it)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
} }
} }

View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import arrow.core.Try
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import timber.log.Timber import timber.log.Timber
@ -7,21 +8,20 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler,
private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler) { private val groupSyncHandler: GroupSyncHandler) {
fun handleResponse(syncResponse: SyncResponse?, fromToken: String?, isCatchingUp: Boolean) { fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
if (syncResponse == null) { return Try {
return Timber.v("Handle sync response")
} if (syncResponse.rooms != null) {
Timber.v("Handle sync response") roomSyncHandler.handle(syncResponse.rooms)
if (syncResponse.rooms != null) { }
roomSyncHandler.handle(syncResponse.rooms) if (syncResponse.groups != null) {
} groupSyncHandler.handle(syncResponse.groups)
if (syncResponse.groups != null) { }
groupSyncHandler.handle(syncResponse.groups) if (syncResponse.accountData != null) {
} userAccountDataSyncHandler.handle(syncResponse.accountData)
if (syncResponse.accountData != null) { }
userAccountDataSyncHandler.handle(syncResponse.accountData) syncResponse
} }
} }
} }

View File

@ -84,7 +84,7 @@ internal class SyncThread(private val syncRequest: SyncRequest,
latch.countDown() latch.countDown()
} }
override fun onFailure(failure: Failure) { override fun onFailure(failure: Throwable) {
if (failure !is Failure.NetworkConnection) { if (failure !is Failure.NetworkConnection) {
// Wait 10s before retrying // Wait 10s before retrying
sleep(RETRY_WAIT_TIME_MS) 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)
}
}