diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt index 8a640fa61b9..c781f29b68e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/EnrichedEventInterceptor.kt @@ -4,9 +4,9 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent interface EnrichedEventInterceptor { - fun enrich(roomId: String, event: EnrichedEvent) - fun canEnrich(event: EnrichedEvent): Boolean + fun enrich(event: EnrichedEvent) + } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt index 77042029fc5..d2936ca01c3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt @@ -6,18 +6,10 @@ data class EnrichedEvent(val root: Event) { val metadata = HashMap() - fun enrichWith(events: List) { - events.forEach { enrichWith(it) } - } - - fun enrichWith(event: Event?) { - if (event == null) { + fun enrichWith(key: String?, data: Any?) { + if (key == null || data == null) { return } - enrichWith(event.type, event) - } - - fun enrichWith(key: String, data: Any) { if (!metadata.containsKey(key)) { metadata[key] = data } @@ -35,5 +27,5 @@ data class EnrichedEvent(val root: Event) { } fun EnrichedEvent.roomMember(): RoomMember? { - return getMetadata(EventType.STATE_ROOM_MEMBER)?.content() + return getMetadata(EventType.STATE_ROOM_MEMBER) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 86c6b00c838..d9ebb1ecbf6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -41,6 +41,14 @@ data class Event( return moshiAdapter.fromJsonValue(data) } + internal inline fun pickContent(stateIndex: Int): T? { + return if (stateIndex < 0) { + prevContent() + } else { + content() + } + } + companion object { internal val CONTENT_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) } 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 b9382a7313a..789adbb2fa0 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 @@ -3,7 +3,9 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.fastContains +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection internal fun ChunkEntity.add(event: Event, stateIndex: Int, paginationDirection: PaginationDirection) { @@ -11,6 +13,13 @@ internal fun ChunkEntity.add(event: Event, stateIndex: Int, paginationDirection: throw IllegalStateException("Chunk entity should be managed to use fast contains") } + if (event.eventId == null) { + return + } + if (EventEntity.where(realm, event.eventId).findFirst() != null) { + return + } + val eventEntity = event.asEntity() eventEntity.stateIndex = stateIndex diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 9ca8267d098..7b689897cc8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -26,11 +26,21 @@ internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, t return query } - -internal fun RealmQuery.last(from: Int? = null): EventEntity? { - if (from != null) { - this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, from) +internal fun RealmQuery.findMostSuitableStateEvent(stateIndex: Int): EventEntity? { + val sort: Sort = if (stateIndex < 0) { + this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, stateIndex) + Sort.ASCENDING + } else { + this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, stateIndex) + Sort.DESCENDING } + return this + .sort(EventEntityFields.STATE_INDEX, sort) + .findFirst() +} + + +internal fun RealmQuery.last(): EventEntity? { return this .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .findFirst() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt index a3010dcaa45..e6f565fd963 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt @@ -4,20 +4,19 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EventType -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.EventEntityFields -import im.vector.matrix.android.internal.database.query.last import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor -internal class MessageEventInterceptor(val monarchy: Monarchy) : EnrichedEventInterceptor { +internal class MessageEventInterceptor(private val monarchy: Monarchy, + private val roomId: String) : EnrichedEventInterceptor { override fun canEnrich(event: EnrichedEvent): Boolean { return event.root.type == EventType.MESSAGE } - override fun enrich(roomId: String, event: EnrichedEvent) { + override fun enrich(event: EnrichedEvent) { monarchy.doWithRealm { realm -> if (event.root.eventId == null) { @@ -25,14 +24,10 @@ internal class MessageEventInterceptor(val monarchy: Monarchy) : EnrichedEventIn } val rootEntity = EventEntity.where(realm, eventId = event.root.eventId).findFirst() - ?: return@doWithRealm - - val roomMember = EventEntity - .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .equalTo(EventEntityFields.STATE_KEY, event.root.sender) - .last(from = rootEntity.stateIndex) - ?.asDomain() - event.enrichWith(roomMember) + ?: return@doWithRealm + + val roomMember = RoomMemberExtractor(realm, roomId).extractFrom(rootEntity) + event.enrichWith(EventType.STATE_ROOM_MEMBER, roomMember) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt index 18147810f6e..b24b3bf834d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt @@ -5,10 +5,12 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.database.model.EventEntity 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 import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -51,12 +53,12 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI, .tryTransactionSync { realm -> // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val roomMembers = RoomMembers(realm, roomId).getLoaded() val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } - val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) + val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert, PaginationDirection.BACKWARDS) if (!roomEntity.chunks.contains(chunk)) { roomEntity.chunks.add(chunk) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt new file mode 100644 index 00000000000..be2bb86367a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt @@ -0,0 +1,41 @@ +package im.vector.matrix.android.internal.session.room.members + +import im.vector.matrix.android.api.session.events.model.EventType +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.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.query.findMostSuitableStateEvent +import im.vector.matrix.android.internal.database.query.last +import im.vector.matrix.android.internal.database.query.where +import io.realm.Realm +import io.realm.RealmQuery + +internal class RoomMemberExtractor(private val realm: Realm, + private val roomId: String) { + + fun extractFrom(event: EventEntity): RoomMember? { + val stateIndex = event.stateIndex + + // First of all, try to get the most suitable state event coming from a chunk + return buildQuery(realm, roomId, event.sender) + .findMostSuitableStateEvent(stateIndex = stateIndex) + ?.asDomain() + ?.pickContent(stateIndex) + + // If the content is null, we try get the last state event, not coming from a chunk + ?: buildQuery(realm, roomId, event.sender) + .last() + ?.asDomain() + ?.content() + + } + + private fun buildQuery(realm: Realm, roomId: String, sender: String?): RealmQuery { + return EventEntity + .where(realm, roomId, EventType.STATE_ROOM_MEMBER) + .equalTo(EventEntityFields.STATE_KEY, sender) + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt index b83c18454e5..40b3d9f9a00 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt @@ -23,7 +23,7 @@ internal class DefaultTimelineHolder(private val roomId: String, init { boundaryCallback.limit = PAGE_SIZE - eventInterceptors.add(MessageEventInterceptor(monarchy)) + eventInterceptors.add(MessageEventInterceptor(monarchy, roomId)) } override fun liveTimeline(): LiveData> { @@ -41,7 +41,7 @@ internal class DefaultTimelineHolder(private val roomId: String, .filter { it.canEnrich(enrichedEvent) }.forEach { - it.enrich(roomId, enrichedEvent) + it.enrich(enrichedEvent) } enrichedEvent } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt index f6e52789167..2530885bc96 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt @@ -8,12 +8,9 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.model.ChunkEntity +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.query.fastContains -import im.vector.matrix.android.internal.database.query.findAllIncludingEvents -import im.vector.matrix.android.internal.database.query.findWithNextToken -import im.vector.matrix.android.internal.database.query.findWithPrevToken -import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.query.* import im.vector.matrix.android.internal.legacy.util.FilterUtil import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI @@ -65,10 +62,10 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, return monarchy .tryTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") + ?: throw IllegalStateException("You shouldn't use this method without a room") val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken) - ?: realm.createObject() + ?: realm.createObject() currentChunk.prevToken = receivedChunk.prevToken @@ -114,7 +111,7 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, currentChunk.updateStateIndex(currentStateIndex, direction) - val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents) + val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents, direction) if (!roomEntity.chunks.contains(stateEventsChunk)) { roomEntity.chunks.add(stateEventsChunk) } 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 7c6b0a51832..b1bcfa96171 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 @@ -12,11 +12,7 @@ import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection -import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync -import im.vector.matrix.android.internal.session.sync.model.RoomSync -import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral -import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary -import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse +import im.vector.matrix.android.internal.session.sync.model.* import io.realm.Realm import io.realm.kotlin.createObject @@ -43,9 +39,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy) { val rooms = when (handlingStrategy) { - is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } + is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } - is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } + is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } } realm.insertOrUpdate(rooms) } @@ -55,7 +51,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSync: RoomSync): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: RoomEntity(roomId) + ?: RoomEntity(roomId) if (roomEntity.membership == MyMembership.INVITED) { roomEntity.chunks.deleteAllFromRealm() @@ -64,7 +60,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomEntity.membership = MyMembership.JOINED if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { - val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events) + val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events, PaginationDirection.FORWARDS) if (!roomEntity.chunks.contains(chunkEntity)) { roomEntity.chunks.add(chunkEntity) } @@ -117,7 +113,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomSummary: RoomSyncSummary) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) + ?: RoomSummaryEntity(roomId) if (roomSummary.heroes.isNotEmpty()) { roomSummaryEntity.heroes.clear() @@ -153,7 +149,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, var currentStateIndex = chunkEntity.nextStateIndex eventList.forEach { event -> - if (event.isStateEvent() && currentStateIndex != 0) { + if (event.isStateEvent()) { currentStateIndex += 1 } chunkEntity.add(event, currentStateIndex, PaginationDirection.FORWARDS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt index bc98000bf27..d46ecd0a779 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt @@ -4,7 +4,6 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.DBConstants import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.findWithNextToken import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Realm @@ -12,18 +11,21 @@ import io.realm.kotlin.createObject internal class StateEventsChunkHandler { - fun handle(realm: Realm, roomId: String, stateEvents: List): ChunkEntity { + fun handle(realm: Realm, roomId: String, stateEvents: List, direction: PaginationDirection): ChunkEntity { val chunkEntity = ChunkEntity.findWithNextToken(realm, roomId, DBConstants.STATE_EVENTS_CHUNK_TOKEN) - ?: realm.createObject() - .apply { - prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN - nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN - } - - + ?: realm.createObject() + .apply { + prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN + nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN + nextStateIndex = Int.MIN_VALUE / 2 + prevStateIndex = Int.MIN_VALUE / 2 + } + + val stateIndex = chunkEntity.stateIndex(direction) + direction.incrementStateIndex stateEvents.forEach { event -> - chunkEntity.add(event, EventEntity.DEFAULT_STATE_INDEX, PaginationDirection.FORWARDS) + chunkEntity.add(event, stateIndex, PaginationDirection.FORWARDS) } + chunkEntity.updateStateIndex(stateIndex, direction) return chunkEntity }