Merge pull request #381 from vector-im/feature/room_members_perf

Feature/room members perf
This commit is contained in:
Benoit Marty 2019-07-17 15:01:06 +02:00 committed by GitHub
commit fc9ef579ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 274 additions and 357 deletions

View File

@ -99,14 +99,14 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.appcompat:appcompat:1.1.0-beta01" implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.recyclerview:recyclerview:1.1.0-alpha06" implementation "androidx.recyclerview:recyclerview:1.1.0-beta01"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// Network // Network
implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.1' implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.database.helper package im.vector.matrix.android.internal.database.helper
import androidx.annotation.VisibleForTesting
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
@ -103,7 +102,6 @@ internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
} }
} }
@VisibleForTesting
internal fun ChunkEntity.add(roomId: String, internal fun ChunkEntity.add(roomId: String,
event: Event, event: Event,
direction: PaginationDirection, direction: PaginationDirection,
@ -134,7 +132,7 @@ internal fun ChunkEntity.add(roomId: String,
} }
} }
val localId = TimelineEventEntity.nextId(realm) val localId = TimelineEventEntity.nextId(realm)
val eventEntity = TimelineEventEntity(localId).also { val eventEntity = TimelineEventEntity(localId).also {
it.root = event.toEntity(roomId).apply { it.root = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex this.stateIndex = currentStateIndex

View File

@ -37,25 +37,22 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
} }
} }
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>, internal fun RoomEntity.addStateEvent(stateEvent: Event,
stateIndex: Int = Int.MIN_VALUE, stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false, filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
assertIsManaged() assertIsManaged()
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
stateEvents.forEach { event -> return
if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) { } else {
return@forEach val entity = stateEvent.toEntity(roomId).apply {
}
val eventEntity = event.toEntity(roomId).apply {
this.stateIndex = stateIndex this.stateIndex = stateIndex
this.isUnlinked = isUnlinked this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED this.sendState = SendState.SYNCED
} }
untimelinedStateEvents.add(0, eventEntity) untimelinedStateEvents.add(entity)
} }
} }
internal fun RoomEntity.addSendingEvent(event: Event) { internal fun RoomEntity.addSendingEvent(event: Event) {
assertIsManaged() assertIsManaged()
val senderId = event.senderId ?: return val senderId = event.senderId ?: return
@ -64,7 +61,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
} }
val roomMembers = RoomMembers(realm, roomId) val roomMembers = RoomMembers(realm, roomId)
val myUser = roomMembers.get(senderId) val myUser = roomMembers.get(senderId)
val localId = TimelineEventEntity.nextId(realm) val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also { val timelineEventEntity = TimelineEventEntity(localId).also {
it.root = eventEntity it.root = eventEntity
it.eventId = event.eventId ?: "" it.eventId = event.eventId ?: ""

View File

@ -20,8 +20,6 @@ import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import java.util.*
internal open class TimelineEventEntity(var localId: Long = 0, internal open class TimelineEventEntity(var localId: Long = 0,

View File

@ -19,21 +19,15 @@ package im.vector.matrix.android.internal.network
import arrow.core.Try import arrow.core.Try
import arrow.core.failure import arrow.core.failure
import arrow.core.recoverWith 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.JsonDataException import com.squareup.moshi.JsonDataException
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.suspendCancellableCoroutine
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Call import retrofit2.Call
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import kotlin.coroutines.resume
internal suspend inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute() internal suspend inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
@ -43,30 +37,22 @@ internal class Request<DATA> {
lateinit var apiCall: Call<DATA> lateinit var apiCall: Call<DATA>
suspend fun execute(): Try<DATA> { suspend fun execute(): Try<DATA> {
return suspendCancellableCoroutine { continuation -> return Try {
continuation.invokeOnCancellation { val response = apiCall.awaitResponse()
Timber.v("Request is canceled") if (response.isSuccessful) {
apiCall.cancel() response.body()
?: throw IllegalStateException("The request returned a null body")
} else {
throw manageFailure(response.errorBody(), response.code())
} }
val result = Try { }.recoverWith {
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync() when (it) {
if (response.isSuccessful) { is IOException -> Failure.NetworkConnection(it)
response.body() is Failure.ServerError,
?: throw IllegalStateException("The request returned a null body") is Failure.OtherServerError -> it
} else { else -> Failure.Unknown(it)
throw manageFailure(response.errorBody(), response.code()) }.failure()
}
}.recoverWith {
when (it) {
is IOException -> Failure.NetworkConnection(it)
is Failure.ServerError,
is Failure.OtherServerError -> it
else -> Failure.Unknown(it)
}.failure()
}
continuation.resume(result)
} }
} }
private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable { private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable {

View File

@ -0,0 +1,41 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package im.vector.matrix.android.internal.network
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun <T> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
continuation.resume(response)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}

View File

@ -38,7 +38,6 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -129,10 +128,6 @@ internal abstract class SessionModule {
@IntoSet @IntoSet
abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver
@Binds
@IntoSet
abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver
@Binds @Binds
abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService

View File

@ -20,14 +20,13 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.api.session.room.model.RoomMember
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.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
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.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import javax.inject.Inject import javax.inject.Inject
@ -42,32 +41,25 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
fun resolve(roomId: String): String? { fun resolve(roomId: String): String? {
var res: String? = null var res: String? = null
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain() val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain()
res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) { if (!res.isNullOrEmpty()) {
return@doWithRealm return@doWithRealm
} }
val roomMembers = RoomMembers(realm, roomId) val roomMembers = RoomMembers(realm, roomId)
val members = roomMembers.getLoaded() val members = roomMembers.queryRoomMembersEvent().findAll()
if (roomEntity?.membership == Membership.INVITE) { // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) { if (members.size == 1) {
res = members.entries.first().value.avatarUrl res = members.firstOrNull()?.toRoomMember()?.avatarUrl
} else if (members.size > 1) { } else if (members.size == 2) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull() val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, credentials.userId).findFirst()
res = firstOtherMember?.avatarUrl res = firstOtherMember?.toRoomMember()?.avatarUrl
}
} else {
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) {
res = members.entries.first().value.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl
}
} }
} }
return res return res
} }
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
}
} }

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent
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.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.latestEvent
@ -86,12 +87,20 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES) val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.findAll()
.asSequence()
.map { it.stateKey }
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestEvent = latestEvent roomSummaryEntity.latestEvent = latestEvent
roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys) roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
} }
} }

View File

@ -17,9 +17,10 @@
package im.vector.matrix.android.internal.session.room.membership package im.vector.matrix.android.internal.session.room.membership
import arrow.core.Try import arrow.core.Try
import com.squareup.moshi.JsonReader
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
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.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.addStateEvent
import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.helper.updateSenderData
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
@ -27,10 +28,13 @@ import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import okhttp3.ResponseBody
import okio.Okio
import javax.inject.Inject import javax.inject.Inject
internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> { internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> {
@ -60,23 +64,26 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
} }
} }
private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> { private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<Unit> {
return monarchy return monarchy
.tryTransactionSync { realm -> .tryTransactionSync { realm ->
// We ignore all the already known members // We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } for (roomMemberEvent in response.roomMemberEvents) {
roomEntity.addStateEvents(eventsToInsert) roomEntity.addStateEvent(roomMemberEvent)
UserEntityFactory.createOrNull(roomMemberEvent)?.also {
realm.insertOrUpdate(it)
}
}
roomEntity.chunks.flatMap { it.timelineEvents }.forEach { roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData() it.updateSenderData()
} }
roomEntity.areAllMembersLoaded = true roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId) roomSummaryUpdater.update(realm, roomId)
} }
.map { response }
} }
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {

View File

@ -25,13 +25,16 @@ import im.vector.matrix.android.api.session.events.model.toModel
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.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomNameContent
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.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import io.realm.RealmResults
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -39,7 +42,6 @@ import javax.inject.Inject
*/ */
internal class RoomDisplayNameResolver @Inject constructor(private val context: Context, internal class RoomDisplayNameResolver @Inject constructor(private val context: Context,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver,
private val credentials: Credentials private val credentials: Credentials
) { ) {
@ -78,48 +80,59 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
} }
val roomMembers = RoomMembers(realm, roomId) val roomMembers = RoomMembers(realm, roomId)
val loadedMembers = roomMembers.getLoaded() val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
val otherRoomMembers = loadedMembers.filterKeys { it != credentials.userId } val otherMembersSubset = loadedMembers.where()
.notEqualTo(EventEntityFields.STATE_KEY, credentials.userId)
.limit(3)
.findAll()
if (roomEntity?.membership == Membership.INVITE) { if (roomEntity?.membership == Membership.INVITE) {
val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst() val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
val inviterId = inviteMeEvent?.sender val inviterId = inviteMeEvent?.sender
name = if (inviterId != null && otherRoomMembers.containsKey(inviterId)) { name = if (inviterId != null) {
roomMemberDisplayNameResolver.resolve(inviterId, otherRoomMembers) val inviterMemberEvent = loadedMembers.where().equalTo(EventEntityFields.STATE_KEY, inviterId).findFirst()
inviterMemberEvent?.toRoomMember()?.displayName
} else { } else {
context.getString(R.string.room_displayname_room_invite) context.getString(R.string.room_displayname_room_invite)
} }
} else { } else {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) { val memberIds: List<String> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes roomSummary.heroes
} else { } else {
otherRoomMembers.keys.toList() otherMembersSubset.mapNotNull { it.stateKey }
} }
name = when (memberIds.size) {
val nbOfOtherMembers = memberIds.size 0 -> context.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMember(otherMembersSubset[0], roomMembers)
when (nbOfOtherMembers) { 2 -> context.getString(R.string.room_displayname_two_members,
0 -> name = context.getString(R.string.room_displayname_empty_room) resolveRoomMember(otherMembersSubset[0], roomMembers),
1 -> name = roomMemberDisplayNameResolver.resolve(memberIds[0], otherRoomMembers) resolveRoomMember(otherMembersSubset[1], roomMembers)
2 -> { )
val member1 = memberIds[0] else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
val member2 = memberIds[1] roomMembers.getNumberOfJoinedMembers() - 1,
name = context.getString(R.string.room_displayname_two_members, resolveRoomMember(otherMembersSubset[0], roomMembers),
roomMemberDisplayNameResolver.resolve(member1, otherRoomMembers), roomMembers.getNumberOfJoinedMembers() - 1)
roomMemberDisplayNameResolver.resolve(member2, otherRoomMembers)
)
}
else -> {
val member = memberIds[0]
name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
roomMemberDisplayNameResolver.resolve(member, otherRoomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)
}
} }
} }
return@doWithRealm return@doWithRealm
} }
return name ?: roomId return name ?: roomId
} }
private fun resolveRoomMember(eventEntity: EventEntity?,
roomMembers: RoomMembers): String? {
if (eventEntity == null) return null
val roomMember = eventEntity.toRoomMember() ?: return null
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
return if (isUnique) {
roomMember.displayName
} else {
"${roomMember.displayName} ( ${eventEntity.stateKey} )"
}
}
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
}
} }

View File

@ -1,54 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import javax.inject.Inject
internal class RoomMemberDisplayNameResolver @Inject constructor() {
fun resolve(userId: String, members: Map<String, RoomMember>): String? {
val currentMember = members[userId]
var displayName = currentMember?.displayName
// Get the user display name from the member list of the room
// Do not consider null display name
if (currentMember != null && !currentMember.displayName.isNullOrEmpty()) {
val hasNameCollision = members
.filterValues { it != currentMember && it.displayName == currentMember.displayName }
.isNotEmpty()
if (hasNameCollision) {
displayName = "${currentMember.displayName} ( $userId )"
}
}
// TODO handle invited users
/*else if (null != member && TextUtils.equals(member!!.membership, RoomMember.MEMBERSHIP_INVITE)) {
val user = (mDataHandler as MXDataHandler).getUser(userId)
if (null != user) {
displayName = user!!.displayname
}
}
*/
if (displayName == null) {
// By default, use the user ID
displayName = userId
}
return displayName
}
}

View File

@ -33,6 +33,7 @@ import io.realm.Sort
* This class is an helper around STATE_ROOM_MEMBER events. * This class is an helper around STATE_ROOM_MEMBER events.
* It allows to get the live membership of a user. * It allows to get the live membership of a user.
*/ */
internal class RoomMembers(private val realm: Realm, internal class RoomMembers(private val realm: Realm,
private val roomId: String private val roomId: String
) { ) {
@ -72,27 +73,27 @@ internal class RoomMembers(private val realm: Realm,
.isNotNull(EventEntityFields.CONTENT) .isNotNull(EventEntityFields.CONTENT)
} }
fun queryJoinedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"")
}
fun queryInvitedRoomMembersEvent(): RealmQuery<EventEntity> {
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"")
}
fun queryRoomMemberEvent(userId: String): RealmQuery<EventEntity> { fun queryRoomMemberEvent(userId: String): RealmQuery<EventEntity> {
return queryRoomMembersEvent() return queryRoomMembersEvent()
.equalTo(EventEntityFields.STATE_KEY, userId) .equalTo(EventEntityFields.STATE_KEY, userId)
} }
fun getLoaded(): Map<String, RoomMember> {
return queryRoomMembersEvent()
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content.toModel<RoomMember>()!! }
}
fun getNumberOfJoinedMembers(): Int { fun getNumberOfJoinedMembers(): Int {
return roomSummary?.joinedMembersCount return roomSummary?.joinedMembersCount
?: getLoaded().filterValues { it.membership == Membership.JOIN }.size ?: queryJoinedRoomMembersEvent().findAll().size
} }
fun getNumberOfInvitedMembers(): Int { fun getNumberOfInvitedMembers(): Int {
return roomSummary?.invitedMembersCount return roomSummary?.invitedMembersCount
?: getLoaded().filterValues { it.membership == Membership.INVITE }.size ?: queryInvitedRoomMembersEvent().findAll().size
} }
fun getNumberOfMembers(): Int { fun getNumberOfMembers(): Int {

View File

@ -43,7 +43,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
private const val INITIAL_LOAD_SIZE = 10 private const val INITIAL_LOAD_SIZE = 30
private const val MIN_FETCHING_COUNT = 30 private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE

View File

@ -18,18 +18,14 @@ package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.merge
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.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.create
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
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.user.UserEntityFactory
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
@ -117,7 +113,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
val nextToken: String? val nextToken: String?
val prevToken: String? val prevToken: String?
@ -146,15 +142,21 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
} else { } else {
nextChunk?.apply { this.prevToken = prevToken } nextChunk?.apply { this.prevToken = prevToken }
} }
?: ChunkEntity.create(realm, prevToken, nextToken) ?: ChunkEntity.create(realm, prevToken, nextToken)
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) { if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
Timber.v("Reach end of $roomId") Timber.v("Reach end of $roomId")
currentChunk.isLastBackward = true currentChunk.isLastBackward = true
} else { } else {
Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) val eventIds = ArrayList<String>(receivedChunk.events.size)
for (event in receivedChunk.events) {
event.eventId?.also { eventIds.add(it) }
currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked())
UserEntityFactory.createOrNull(event)?.also {
realm.insertOrUpdate(it)
}
}
// Then we merge chunks if needed // Then we merge chunks if needed
if (currentChunk != prevChunk && prevChunk != null) { if (currentChunk != prevChunk && prevChunk != null) {
currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk) currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk)
@ -170,7 +172,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
} }
} }
roomEntity.addOrUpdate(currentChunk) roomEntity.addOrUpdate(currentChunk)
roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = currentChunk.isUnlinked()) for (stateEvent in receivedChunk.stateEvents) {
roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
UserEntityFactory.createOrNull(stateEvent)?.also {
realm.insertOrUpdate(it)
}
}
currentChunk.updateSenderDataFor(eventIds)
} }
} }
.map { .map {

View File

@ -24,12 +24,11 @@ import im.vector.matrix.android.api.session.events.model.toModel
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.session.room.model.tag.RoomTagContent import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.lastStateIndex
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.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
@ -40,6 +39,7 @@ import im.vector.matrix.android.internal.session.notification.ProcessEventForPus
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.sync.model.* import im.vector.matrix.android.internal.session.sync.model.*
import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import io.realm.Realm import io.realm.Realm
@ -125,51 +125,31 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
} }
roomEntity.membership = Membership.JOIN roomEntity.membership = Membership.JOIN
val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
val isInitialSync = lastChunk == null
val lastStateIndex = lastChunk?.lastStateIndex(PaginationDirection.FORWARDS) ?: 0
val numberOfStateEvents = roomSync.state?.events?.size ?: 0
val stateIndexOffset = lastStateIndex + numberOfStateEvents
// State event // State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex) ?: Int.MIN_VALUE
val untimelinedStateIndex = minStateIndex + 1
// Give info to crypto module roomSync.state.events.forEach { event ->
roomSync.state.events.forEach { roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
cryptoManager.onStateEvent(roomId, it) // Give info to crypto module
cryptoManager.onStateEvent(roomId, event)
UserEntityFactory.createOrNull(event)?.also {
realm.insertOrUpdate(it)
}
} }
} }
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val timelineStateOffset = if (isInitialSync || roomSync.timeline.limited.not()) 0 else stateIndexOffset
val chunkEntity = handleTimelineEvents( val chunkEntity = handleTimelineEvents(
realm, realm,
roomId, roomEntity,
roomSync.timeline.events, roomSync.timeline.events,
roomSync.timeline.prevToken, roomSync.timeline.prevToken,
roomSync.timeline.limited, roomSync.timeline.limited,
timelineStateOffset 0
) )
roomEntity.addOrUpdate(chunkEntity) roomEntity.addOrUpdate(chunkEntity)
// Give info to crypto module
roomSync.timeline.events.forEach {
cryptoManager.onLiveEvent(roomId, it)
}
// Try to remove local echo
val transactionIds = roomSync.timeline.events.mapNotNull { it.unsignedData?.transactionId }
transactionIds.forEach {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
} else {
Timber.v("Can't find corresponding local echo for tx:$it")
}
}
} }
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications) roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
@ -192,7 +172,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
?: realm.createObject(roomId) ?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events) val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity) roomEntity.addOrUpdate(chunkEntity)
} }
roomSummaryUpdater.update(realm, roomId, Membership.INVITE) roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
@ -212,13 +192,13 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
} }
private fun handleTimelineEvents(realm: Realm, private fun handleTimelineEvents(realm: Realm,
roomId: String, roomEntity: RoomEntity,
eventList: List<Event>, eventList: List<Event>,
prevToken: String? = null, prevToken: String? = null,
isLimited: Boolean = true, isLimited: Boolean = true,
stateIndexOffset: Int = 0): ChunkEntity { stateIndexOffset: Int = 0): ChunkEntity {
val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId)
val chunkEntity = if (!isLimited && lastChunk != null) { val chunkEntity = if (!isLimited && lastChunk != null) {
lastChunk lastChunk
} else { } else {
@ -226,13 +206,32 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
} }
lastChunk?.isLastForward = false lastChunk?.isLastForward = false
chunkEntity.isLastForward = true chunkEntity.isLastForward = true
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS, stateIndexOffset)
//update eventAnnotationSummary here?
val eventIds = ArrayList<String>(eventList.size)
for (event in eventList) {
event.eventId?.also { eventIds.add(it) }
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
// Give info to crypto module
cryptoManager.onLiveEvent(roomEntity.roomId, event)
// Try to remove local echo
event.unsignedData?.transactionId?.also {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
} else {
Timber.v("Can't find corresponding local echo for tx:$it")
}
}
UserEntityFactory.createOrNull(event)?.also {
realm.insertOrUpdate(it)
}
}
chunkEntity.updateSenderDataFor(eventIds)
return chunkEntity return chunkEntity
} }
private fun handleEphemeral(realm: Realm, private fun handleEphemeral(realm: Realm,
roomId: String, roomId: String,
ephemeral: RoomSyncEphemeral) { ephemeral: RoomSyncEphemeral) {

View File

@ -20,8 +20,6 @@ import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.user.DefaultUpdateUserTask
import im.vector.matrix.android.internal.session.user.UpdateUserTask
import retrofit2.Retrofit import retrofit2.Retrofit
@Module @Module

View File

@ -1,58 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.user
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import javax.inject.Inject
internal interface UpdateUserTask : Task<UpdateUserTask.Params, Unit> {
data class Params(val eventIds: List<String>)
}
internal class DefaultUpdateUserTask @Inject constructor(private val monarchy: Monarchy) : UpdateUserTask {
override suspend fun execute(params: UpdateUserTask.Params): Try<Unit> {
return monarchy.tryTransactionSync { realm ->
params.eventIds.forEach { eventId ->
val event = EventEntity.where(realm, eventId).findFirst()?.asDomain()
?: return@forEach
val roomId = event.roomId ?: return@forEach
val userId = event.stateKey ?: return@forEach
val roomMember = RoomMembers(realm, roomId).get(userId) ?: return@forEach
if (roomMember.membership != Membership.JOIN) return@forEach
val userEntity = UserEntity.where(realm, userId).findFirst()
?: realm.createObject(UserEntity::class.java, userId)
userEntity.displayName = roomMember.displayName ?: ""
userEntity.avatarUrl = roomMember.avatarUrl ?: ""
}
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.user
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.model.UserEntity
internal object UserEntityFactory {
fun createOrNull(event: Event): UserEntity? {
if (event.type != EventType.STATE_ROOM_MEMBER) {
return null
}
val roomMember = event.content.toModel<RoomMember>() ?: return null
return UserEntity(event.stateKey ?: "",
roomMember.displayName ?: "",
roomMember.avatarUrl ?: ""
)
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.user
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import io.realm.Sort
import javax.inject.Inject
internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
private val updateUserTask: UpdateUserTask,
private val taskExecutor: TaskExecutor)
: RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
override val query = Monarchy.Query<EventEntity> {
EventEntity
.types(it, listOf(EventType.STATE_ROOM_MEMBER))
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.distinct(EventEntityFields.STATE_KEY)
}
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
val roomMembersEvents = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.eventId }
.toList()
val taskParams = UpdateUserTask.Params(roomMembersEvents)
updateUserTask
.configureWith(taskParams)
.executeOn(TaskThread.IO)
.executeBy(taskExecutor)
}
}

View File

@ -26,7 +26,4 @@ internal abstract class UserModule {
@Binds @Binds
abstract fun bindUserService(userService: DefaultUserService): UserService abstract fun bindUserService(userService: DefaultUserService): UserService
@Binds
abstract fun bindUpdateUserTask(updateUserTask: DefaultUpdateUserTask): UpdateUserTask
} }

View File

@ -18,7 +18,18 @@ package im.vector.riotx.core.platform
import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.riotx.BuildConfig import im.vector.riotx.BuildConfig
abstract class VectorViewModel<S : MvRxState>(initialState: S) abstract class VectorViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) : BaseMvRxViewModel<S>(initialState, false) {
protected val cancelableBag = CancelableBag()
override fun onCleared() {
super.onCleared()
cancelableBag.cancel()
}
}

View File

@ -94,7 +94,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
observeRoomSummary() observeRoomSummary()
observeEventDisplayedActions() observeEventDisplayedActions()
observeInvitationState() observeInvitationState()
room.loadRoomMembersIfNeeded() cancelableBag += room.loadRoomMembersIfNeeded()
timeline.start() timeline.start()
setState { copy(timeline = this@RoomDetailViewModel.timeline) } setState { copy(timeline = this@RoomDetailViewModel.timeline) }
} }