diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index f60c37512c..90236a4f98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -47,7 +47,7 @@ internal fun ChunkEntity.deleteOnCascade() { internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, - direction: PaginationDirection) { + direction: PaginationDirection): List { assertIsManaged() val isChunkToMergeUnlinked = chunkToMerge.isUnlinked() val isCurrentChunkUnlinked = this.isUnlinked() @@ -66,23 +66,11 @@ internal fun ChunkEntity.merge(roomId: String, this.isLastBackward = chunkToMerge.isLastBackward eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } - val timelineEvents = eventsToMerge + return eventsToMerge .mapNotNull { val event = it.root?.asDomain() ?: return@mapNotNull null add(roomId, event, direction, isUnlinked = isUnlinked) } - updateSenderDataFor(timelineEvents) -} - -internal fun ChunkEntity.updateSenderDataFor(events: List) { - val cache = RoomMembersCache() - events.forEach { - val result = cache.get(it) - it.isUniqueDisplayName = result.isUniqueDisplayName - it.senderAvatar = result.senderAvatar - it.senderName = result.senderName - it.senderMembershipEventId = result.senderMembershipEventId - } } internal fun ChunkEntity.add(roomId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt index c7bedaee21..0bf02aa92f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -16,72 +16,9 @@ package im.vector.matrix.android.internal.database.helper -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.RoomMemberContent -import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.query.next -import im.vector.matrix.android.internal.database.query.prev -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.extensions.assertIsManaged -import im.vector.matrix.android.internal.session.room.membership.RoomMembers +import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import io.realm.Realm -import io.realm.RealmList -import io.realm.RealmQuery - -internal fun TimelineEventEntity.updateSenderData() { - assertIsManaged() - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return - val stateIndex = root?.stateIndex ?: return - val senderId = root?.sender ?: return - val chunkEntity = chunk?.firstOrNull() ?: return - val isUnlinked = chunkEntity.isUnlinked() - var senderMembershipEvent: EventEntity? - var senderRoomMemberContent: String? - var senderRoomMemberPrevContent: String? - - if (stateIndex <= 0) { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.prevContent - senderRoomMemberPrevContent = senderMembershipEvent?.content - } else { - senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root - senderRoomMemberContent = senderMembershipEvent?.content - senderRoomMemberPrevContent = senderMembershipEvent?.prevContent - } - -// We fallback to untimelinedStateEvents if we can't find membership events in timeline - if (senderMembershipEvent == null) { - senderMembershipEvent = roomEntity.untimelinedStateEvents - .where() - .equalTo(EventEntityFields.STATE_KEY, senderId) - .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) - .prev(since = stateIndex) - senderRoomMemberContent = senderMembershipEvent?.content - senderRoomMemberPrevContent = senderMembershipEvent?.prevContent - } - - ContentMapper.map(senderRoomMemberContent).toModel()?.also { - this.senderAvatar = it.avatarUrl - this.senderName = it.displayName - this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) - } - -// We try to fallback on prev content if we got a room member state events with null fields - if (root?.type == EventType.STATE_ROOM_MEMBER) { - ContentMapper.map(senderRoomMemberPrevContent).toModel()?.also { - if (this.senderAvatar == null && it.avatarUrl != null) { - this.senderAvatar = it.avatarUrl - } - if (this.senderName == null && it.displayName != null) { - this.senderName = it.displayName - this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName) - } - } - } - this.senderMembershipEventId = senderMembershipEvent?.eventId -} internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID) @@ -91,10 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { currentIdNum.toLong() + 1 } } - -private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { - return where() - .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) - .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) - .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt similarity index 84% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt index fb2c367e64..2e8b02d6aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomMembersCache.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt @@ -25,15 +25,17 @@ import im.vector.matrix.android.internal.database.query.next import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.membership.RoomMembers import io.realm.RealmList import io.realm.RealmQuery -import timber.log.Timber +import javax.inject.Inject /** * This is an internal cache to avoid querying all the time the room member events */ -internal class RoomMembersCache { +@SessionScope +internal class TimelineEventSenderVisitor @Inject constructor() { internal data class Key( val roomId: String, @@ -50,24 +52,34 @@ internal class RoomMembersCache { private val values = HashMap() - fun get(timelineEventEntity: TimelineEventEntity): Value { + fun clear() { + values.clear() + } + + fun clear(roomId: String, senderId: String) { + val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId } + keysToRemove.forEach { + values.remove(it) + } + } + + fun visit(timelineEventEntities: List) = timelineEventEntities.forEach { visit(it) } + + fun visit(timelineEventEntity: TimelineEventEntity) { val key = Key( roomId = timelineEventEntity.roomId, stateIndex = timelineEventEntity.root?.stateIndex ?: 0, senderId = timelineEventEntity.root?.sender ?: "" ) - val result: Value - val start = System.currentTimeMillis() - result = values.getOrPut(key) { - doQueryAndBuildValue(timelineEventEntity) + val result = values.getOrPut(key) { + timelineEventEntity.computeValue() + } + timelineEventEntity.apply { + this.isUniqueDisplayName = result.isUniqueDisplayName + this.senderAvatar = result.senderAvatar + this.senderName = result.senderName + this.senderMembershipEventId = result.senderMembershipEventId } - val end = System.currentTimeMillis() - Timber.v("Get value took: ${end - start} millis") - return result - } - - private fun doQueryAndBuildValue(timelineEventEntity: TimelineEventEntity): Value { - return timelineEventEntity.computeValue() } private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { @@ -80,7 +92,6 @@ internal class RoomMembersCache { private fun TimelineEventEntity.computeValue(): Value { assertIsManaged() val result = Value() - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result val stateIndex = root?.stateIndex ?: return result val senderId = root?.sender ?: return result diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 64cbcb3527..dd91875f98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.session.room.membership import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor 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.model.RoomEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest @@ -44,7 +44,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP private val monarchy: Monarchy, private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, - private val roomMemberEventHandler: RoomMemberEventHandler + private val roomMemberEventHandler: RoomMemberEventHandler, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -68,8 +69,9 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP roomEntity.addStateEvent(roomMemberEvent) roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } + timelineEventSenderVisitor.clear() roomEntity.chunks.flatMap { it.timelineEvents }.forEach { - it.updateSenderData() + timelineEventSenderVisitor.visit(it) } roomEntity.areAllMembersLoaded = true roomSummaryUpdater.update(realm, roomId, updateMembers = true) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index de3eb1eab2..8228136f10 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -20,7 +20,7 @@ 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.LocalEcho import im.vector.matrix.android.api.session.events.model.UnsignedData -import im.vector.matrix.android.internal.database.helper.updateSenderData +import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.model.EventEntity @@ -41,7 +41,8 @@ internal interface PruneEventTask : Task { ) } -internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask { +internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor) : PruneEventTask { override suspend fun execute(params: PruneEventTask.Params) { monarchy.awaitTransaction { realm -> @@ -65,12 +66,14 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() ?: return - val allowedKeys = computeAllowedKeys(eventToPrune.type) + val typeToPrune = eventToPrune.type + val stateKey = eventToPrune.stateKey + val allowedKeys = computeAllowedKeys(typeToPrune) if (allowedKeys.isNotEmpty()) { val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) } eventToPrune.content = ContentMapper.map(prunedContent) } else { - when (eventToPrune.type) { + when (typeToPrune) { EventType.ENCRYPTED, EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") @@ -94,11 +97,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M // } } } - if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) { + if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) { + timelineEventSenderVisitor.clear(roomId = eventToPrune.roomId, senderId = stateKey) val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId) - for (timelineEvent in timelineEventsToUpdate) { - timelineEvent.updateSenderData() - } + timelineEventSenderVisitor.visit(timelineEventsToUpdate) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 342a40052c..9773eff507 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -36,7 +36,8 @@ import javax.inject.Inject /** * Insert Chunk in DB, and eventually merge with existing chunk event */ -internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy) { +internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor) { /** *
@@ -170,7 +171,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
                         for (stateEvent in receivedChunk.stateEvents) {
                             roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
                         }
-                        currentChunk.updateSenderDataFor(timelineEvents)
+                        timelineEventSenderVisitor.visit(timelineEvents)
                     }
                 }
         return if (receivedChunk.events.isEmpty()) {
@@ -191,11 +192,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
         // We always merge the bottom chunk into top chunk, so we are always merging backwards
         Timber.v("Merge ${currentChunk.prevToken} | ${currentChunk.nextToken} with ${otherChunk.prevToken} | ${otherChunk.nextToken}")
         return if (direction == PaginationDirection.BACKWARDS && !otherChunk.isLastForward) {
-            currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
+            val events = currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
+            timelineEventSenderVisitor.visit(events)
             roomEntity.deleteOnCascade(otherChunk)
             currentChunk
         } else {
-            otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
+            val events = otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
+            timelineEventSenderVisitor.visit(events)
             roomEntity.deleteOnCascade(currentChunk)
             otherChunk
         }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
index f3c0cd9511..647635343e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt
@@ -48,7 +48,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                                    private val roomTagHandler: RoomTagHandler,
                                                    private val roomFullyReadHandler: RoomFullyReadHandler,
                                                    private val cryptoService: DefaultCryptoService,
-                                                   private val roomMemberEventHandler: RoomMemberEventHandler) {
+                                                   private val roomMemberEventHandler: RoomMemberEventHandler,
+                                                   private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map) : HandlingStrategy()
@@ -209,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             }
             roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
         }
-        chunkEntity.updateSenderDataFor(timelineEvents)
+        timelineEventSenderVisitor.visit(timelineEvents)
         return chunkEntity
     }