From 1d8b81bb04decabe46b1933261e0f3fbb19a5130 Mon Sep 17 00:00:00 2001 From: Ganard Date: Fri, 24 Jan 2020 18:43:35 +0100 Subject: [PATCH] Try reworking events/timeline process [WIP] --- .../session/room/timeline/ChunkEntityTest.kt | 2 +- .../internal/crypto/DefaultCryptoService.kt | 3 +- .../database/helper/ChunkEntityHelper.kt | 98 +++--------- .../database/helper/RoomEntityHelper.kt | 24 +-- .../helper/TimelineEventSenderVisitor.kt | 148 ------------------ .../internal/database/mapper/EventMapper.kt | 7 +- .../database/mapper/TimelineEventMapper.kt | 2 +- .../internal/database/model/ChunkEntity.kt | 5 +- .../database/model/CurrentStateEventEntity.kt | 31 ++++ .../internal/database/model/EventEntity.kt | 9 +- .../internal/database/model/RoomEntity.kt | 1 - .../database/model/TimelineEventEntity.kt | 4 +- .../database/query/ChunkEntityQueries.kt | 4 +- .../query/CurrentStateEventEntityQueries.kt | 49 ++++++ .../database/query/EventEntityQueries.kt | 47 +----- .../internal/database/query/ReadQueries.kt | 16 +- .../query/TimelineEventEntityQueries.kt | 50 ++---- .../session/room/RoomAvatarResolver.kt | 7 +- .../session/room/RoomSummaryUpdater.kt | 17 +- .../room/membership/LoadRoomMembersTask.kt | 20 ++- .../membership/RoomDisplayNameResolver.kt | 19 ++- .../room/membership/RoomMemberHelper.kt | 20 +-- .../session/room/prune/PruneEventTask.kt | 10 +- .../session/room/send/LocalEchoRepository.kt | 8 +- .../session/room/state/DefaultStateService.kt | 11 +- .../room/timeline/ClearUnlinkedEventsTask.kt | 7 +- .../session/room/timeline/DefaultTimeline.kt | 39 ++--- .../timeline/TimelineHiddenReadReceipts.kt | 6 +- .../room/timeline/TokenChunkEventPersistor.kt | 77 ++++----- .../internal/session/sync/RoomSyncHandler.kt | 55 ++++--- .../media/ImageMediaViewerActivity.kt | 2 +- 31 files changed, 287 insertions(+), 511 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/CurrentStateEventEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/CurrentStateEventEntityQueries.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index 3980094175..f720672e0b 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -181,7 +181,7 @@ internal class ChunkEntityTest : InstrumentedTest { direction: PaginationDirection, stateIndexOffset: Int = 0) { events.forEach { event -> - add(roomId, event, direction, stateIndexOffset) + add(roomId, event, direction) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 783b7eb4ad..6ef7c262fb 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -60,6 +60,7 @@ import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.query.whereType import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.session.SessionScope @@ -482,7 +483,7 @@ internal class DefaultCryptoService @Inject constructor( */ override fun isRoomEncrypted(roomId: String): Boolean { val encryptionEvent = monarchy.fetchCopied { - EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() + EventEntity.whereType(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() } return encryptionEvent != null } 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 3fa355fe3c..034607c75a 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 @@ -17,17 +17,13 @@ package im.vector.matrix.android.internal.database.helper 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.room.send.SendState -import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.mapper.toEntity +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.model.* -import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.getOrCreate 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.timeline.PaginationDirection -import io.realm.Sort import io.realm.kotlin.createObject internal fun ChunkEntity.deleteOnCascade() { @@ -36,42 +32,11 @@ internal fun ChunkEntity.deleteOnCascade() { this.deleteFromRealm() } -internal fun ChunkEntity.merge(roomId: String, - chunkToMerge: ChunkEntity, - direction: PaginationDirection): List { - assertIsManaged() - val isChunkToMergeUnlinked = chunkToMerge.isUnlinked - val isCurrentChunkUnlinked = isUnlinked +internal fun ChunkEntity.addTimelineEvent(roomId: String, + eventEntity: EventEntity, + direction: PaginationDirection, + roomMemberEvent: Event?): TimelineEventEntity { - if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) { - this.timelineEvents.forEach { it.root?.isUnlinked = false } - } - val eventsToMerge: List - if (direction == PaginationDirection.FORWARDS) { - this.nextToken = chunkToMerge.nextToken - this.isLastForward = chunkToMerge.isLastForward - eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) - } else { - this.prevToken = chunkToMerge.prevToken - this.isLastBackward = chunkToMerge.isLastBackward - eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) - } - return eventsToMerge - .mapNotNull { - val event = it.root?.asDomain() ?: return@mapNotNull null - add(roomId, event, direction) - } -} - -internal fun ChunkEntity.add(roomId: String, - event: Event, - direction: PaginationDirection, - stateIndexOffset: Int = 0 -): TimelineEventEntity? { - assertIsManaged() - if (event.eventId != null && timelineEvents.find(event.eventId) != null) { - return null - } var currentDisplayIndex = lastDisplayIndex(direction, 0) if (direction == PaginationDirection.FORWARDS) { currentDisplayIndex += 1 @@ -80,22 +45,10 @@ internal fun ChunkEntity.add(roomId: String, currentDisplayIndex -= 1 backwardsDisplayIndex = currentDisplayIndex } - var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset) - if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) { - currentStateIndex += 1 - forwardsStateIndex = currentStateIndex - } else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) { - val lastEventType = timelineEvents.last()?.root?.type ?: "" - if (EventType.isStateEvent(lastEventType)) { - currentStateIndex -= 1 - backwardsStateIndex = currentStateIndex - } - } - val isChunkUnlinked = isUnlinked val localId = TimelineEventEntity.nextId(realm) - val eventId = event.eventId ?: "" - val senderId = event.senderId ?: "" + val eventId = eventEntity.eventId + val senderId = eventEntity.sender ?: "" val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() ?: realm.createObject(eventId).apply { @@ -104,8 +57,9 @@ internal fun ChunkEntity.add(roomId: String, // Update RR for the sender of a new message with a dummy one - if (event.originServerTs != null) { - val timestampOfEvent = event.originServerTs.toDouble() + val originServerTs = eventEntity.originServerTs + if (originServerTs != null) { + val timestampOfEvent = originServerTs.toDouble() val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId) // If the synced RR is older, update if (timestampOfEvent > readReceiptOfSender.originServerTs) { @@ -117,23 +71,24 @@ internal fun ChunkEntity.add(roomId: String, } } - val rootEvent = event.toEntity(roomId).apply { - this.stateIndex = currentStateIndex - this.displayIndex = currentDisplayIndex - this.sendState = SendState.SYNCED - this.isUnlinked = isChunkUnlinked - } - val eventEntity = realm.createObject().also { + val timelineEventEntity = TimelineEventEntity().also { it.localId = localId - it.root = realm.copyToRealm(rootEvent) + it.root = realm.copyToRealm(eventEntity) it.eventId = eventId it.roomId = roomId it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() it.readReceipts = readReceiptsSummaryEntity + it.displayIndex = currentDisplayIndex } - val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size - timelineEvents.add(position, eventEntity) - return eventEntity + if (roomMemberEvent != null) { + val roomMemberContent = roomMemberEvent.content.toModel() + timelineEventEntity.senderAvatar = roomMemberContent?.avatarUrl + timelineEventEntity.senderName = roomMemberContent?.displayName + timelineEventEntity.isUniqueDisplayName = false + timelineEventEntity.senderMembershipEventId = roomMemberEvent.eventId + } + timelineEvents.add(timelineEventEntity) + return timelineEventEntity } internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { @@ -142,10 +97,3 @@ internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaul PaginationDirection.BACKWARDS -> backwardsDisplayIndex } ?: defaultValue } - -internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { - return when (direction) { - PaginationDirection.FORWARDS -> forwardsStateIndex - PaginationDirection.BACKWARDS -> backwardsStateIndex - } ?: defaultValue -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 9d48d477bf..aa3490d2c4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -22,8 +22,6 @@ import im.vector.matrix.android.internal.database.mapper.toEntity 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.TimelineEventEntity -import im.vector.matrix.android.internal.database.query.fastContains -import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import io.realm.Realm @@ -38,27 +36,9 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) { } } -internal fun RoomEntity.addStateEvent(stateEvent: Event, - stateIndex: Int = Int.MIN_VALUE, - filterDuplicates: Boolean = false, - isUnlinked: Boolean = false) { - assertIsManaged() - if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) { - return - } else { - val entity = stateEvent.toEntity(roomId).apply { - this.stateIndex = stateIndex - this.isUnlinked = isUnlinked - this.sendState = SendState.SYNCED - } - untimelinedStateEvents.add(entity) - } -} internal fun RoomEntity.addSendingEvent(realm: Realm, event: Event) { val senderId = event.senderId ?: return - val eventEntity = event.toEntity(roomId).apply { - this.sendState = SendState.UNSENT - } + val eventEntity = event.toEntity(roomId, SendState.UNSENT) val roomMembers = RoomMemberHelper(realm, roomId) val myUser = roomMembers.getLastRoomMember(senderId) val localId = TimelineEventEntity.nextId(realm) @@ -68,7 +48,7 @@ internal fun RoomEntity.addSendingEvent(realm: Realm, event: Event) { it.roomId = roomId it.senderName = myUser?.displayName it.senderAvatar = myUser?.avatarUrl - it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName) + it.isUniqueDisplayName = roomMembers.isUniqueDisplayName() } sendingTimelineEvents.add(0, timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt deleted file mode 100644 index 17103ce337..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventSenderVisitor.kt +++ /dev/null @@ -1,148 +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.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.SessionScope -import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper -import io.realm.RealmList -import io.realm.RealmQuery -import javax.inject.Inject - -/** - * This is an internal cache to avoid querying all the time the room member events - */ -@SessionScope -internal class TimelineEventSenderVisitor @Inject constructor() { - - internal data class Key( - val roomId: String, - val stateIndex: Int, - val senderId: String - ) - - internal class Value( - var senderAvatar: String? = null, - var senderName: String? = null, - var isUniqueDisplayName: Boolean = false, - var senderMembershipEventId: String? = null - ) - - private val values = HashMap() - - 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) { - if (!timelineEventEntity.isValid) { - return - } - val key = Key( - roomId = timelineEventEntity.roomId, - stateIndex = timelineEventEntity.root?.stateIndex ?: 0, - senderId = timelineEventEntity.root?.sender ?: "" - ) - 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 - } - } - - 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) - } - - 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 - val chunkEntity = chunk?.firstOrNull() ?: return result - 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 { - result.senderAvatar = it.avatarUrl - result.senderName = it.displayName - result.isUniqueDisplayName = RoomMemberHelper(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 (result.senderAvatar == null && it.avatarUrl != null) { - result.senderAvatar = it.avatarUrl - } - if (result.senderName == null && it.displayName != null) { - result.senderName = it.displayName - result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName) - } - } - } - result.senderMembershipEventId = senderMembershipEvent?.eventId - return result - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index ed5f04ef75..65fd382960 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.JsonDataException import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.di.MoshiProvider @@ -90,6 +91,8 @@ internal fun EventEntity.asDomain(): Event { return EventMapper.map(this) } -internal fun Event.toEntity(roomId: String): EventEntity { - return EventMapper.map(this, roomId) +internal fun Event.toEntity(roomId: String, sendState: SendState): EventEntity { + return EventMapper.map(this, roomId).apply { + this.sendState = sendState + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 146b5c3ae4..aa282fb673 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -39,7 +39,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS ?: Event("", timelineEventEntity.eventId), annotations = timelineEventEntity.annotations?.asDomain(), localId = timelineEventEntity.localId, - displayIndex = timelineEventEntity.root?.displayIndex ?: 0, + displayIndex = timelineEventEntity.displayIndex, senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt index 94d4a9043f..b1aedbe21a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt @@ -28,10 +28,7 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false, var backwardsDisplayIndex: Int? = null, - var forwardsDisplayIndex: Int? = null, - var backwardsStateIndex: Int? = null, - var forwardsStateIndex: Int? = null, - var isUnlinked: Boolean = false + var forwardsDisplayIndex: Int? = null ) : RealmObject() { fun identifier() = "${prevToken}_$nextToken" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/CurrentStateEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/CurrentStateEventEntity.kt new file mode 100644 index 0000000000..ba532501b6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/CurrentStateEventEntity.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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.database.model + +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey + +internal open class CurrentStateEventEntity(var eventId: String = "", + var root: EventEntity? = null, + @Index var roomId: String = "", + @Index var type: String = "", + @Index var stateKey: String = "" +) : RealmObject() { + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 4def7aec5d..cb64ecf470 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -24,8 +24,9 @@ import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey -internal open class EventEntity(@Index var eventId: String = "", +internal open class EventEntity(@PrimaryKey var eventId: String = "", @Index var roomId: String = "", @Index var type: String = "", var content: String? = null, @@ -36,9 +37,6 @@ internal open class EventEntity(@Index var eventId: String = "", var age: Long? = 0, var unsignedData: String? = null, var redacts: String? = null, - @Index var stateIndex: Int = 0, - @Index var displayIndex: Int = 0, - @Index var isUnlinked: Boolean = false, var decryptionResultJson: String? = null, var decryptionErrorCode: String? = null ) : RealmObject() { @@ -61,9 +59,6 @@ internal open class EventEntity(@Index var eventId: String = "", companion object - @LinkingObjects("untimelinedStateEvents") - val room: RealmResults? = null - @LinkingObjects("root") val timelineEventEntity: RealmResults? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt index 7de9451c32..6dededff91 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt @@ -23,7 +23,6 @@ import io.realm.annotations.PrimaryKey internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), - var untimelinedStateEvents: RealmList = RealmList(), var sendingTimelineEvents: RealmList = RealmList(), var areAllMembersLoaded: Boolean = false ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index 22f4b9c506..63a11b7ff9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -20,10 +20,12 @@ import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey internal open class TimelineEventEntity(var localId: Long = 0, - @Index var eventId: String = "", + @PrimaryKey var eventId: String = "", @Index var roomId: String = "", + @Index var displayIndex: Int = 0, var root: EventEntity? = null, var annotations: EventAnnotationsSummaryEntity? = null, var senderName: String? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt index b8c058e667..009ee4b7fe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt @@ -60,12 +60,10 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str internal fun ChunkEntity.Companion.create( realm: Realm, prevToken: String?, - nextToken: String?, - isUnlinked: Boolean + nextToken: String? ): ChunkEntity { return realm.createObject().apply { this.prevToken = prevToken this.nextToken = nextToken - this.isUnlinked = isUnlinked } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/CurrentStateEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/CurrentStateEventEntityQueries.kt new file mode 100644 index 0000000000..25dec57e46 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/CurrentStateEventEntityQueries.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020 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.database.query + +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.createObject + +internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: String, stateKey: String, type: String): RealmQuery { + return realm.where(CurrentStateEventEntity::class.java) + .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) + .equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey) + .equalTo(CurrentStateEventEntityFields.TYPE, type) +} + +internal fun CurrentStateEventEntity.Companion.getOrNull(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity? { + return where(realm, roomId, stateKey, type).findFirst() +} + +internal fun CurrentStateEventEntity.Companion.getOrCreate(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity { + return getOrNull(realm, roomId, stateKey, type) ?: create(realm, roomId, stateKey, type) +} + +private fun create(realm: Realm, roomId: String, stateKey: String, type: String): CurrentStateEventEntity { + return realm.createObject().apply { + this.type = type + this.roomId = roomId + this.stateKey = stateKey + } +} + + 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 f3f8db0ea0..590a2162f2 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 @@ -35,22 +35,15 @@ internal fun EventEntity.Companion.where(realm: Realm, eventIds: List): .`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray()) } -internal fun EventEntity.Companion.where(realm: Realm, - roomId: String? = null, - type: String? = null, - linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery { +internal fun EventEntity.Companion.whereType(realm: Realm, + type: String, + roomId: String? = null +): RealmQuery { val query = realm.where() if (roomId != null) { query.equalTo(EventEntityFields.ROOM_ID, roomId) } - if (type != null) { - query.equalTo(EventEntityFields.TYPE, type) - } - return when (linkFilterMode) { - LINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, false) - UNLINKED_ONLY -> query.equalTo(EventEntityFields.IS_UNLINKED, true) - BOTH -> query - } + return query.equalTo(EventEntityFields.TYPE, type) } internal fun EventEntity.Companion.types(realm: Realm, @@ -60,36 +53,6 @@ internal fun EventEntity.Companion.types(realm: Realm, return query } -internal fun RealmQuery.descending(since: Int? = null, strict: Boolean = false): RealmQuery { - if (since != null) { - if (strict) { - this.lessThan(EventEntityFields.STATE_INDEX, since) - } else { - this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since) - } - } - return this.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) -} - -internal fun RealmQuery.ascending(from: Int? = null, strict: Boolean = true): RealmQuery { - if (from != null) { - if (strict) { - this.greaterThan(EventEntityFields.STATE_INDEX, from) - } else { - this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from) - } - } - return this.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING) -} - -internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): EventEntity? { - return this.ascending(from, strict).findFirst() -} - -internal fun RealmQuery.prev(since: Int? = null, strict: Boolean = false): EventEntity? { - return descending(since, strict).findFirst() -} - internal fun RealmList.find(eventId: String): EventEntity? { return this.where() .equalTo(EventEntityFields.EVENT_ID, eventId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt index c214886ec8..bf638654d5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt @@ -36,15 +36,15 @@ internal fun isEventRead(monarchy: Monarchy, monarchy.doWithRealm { realm -> val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm - val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root + val eventToCheck = liveChunk.timelineEvents.find(eventId) - isEventRead = if (eventToCheck?.sender == userId) { + isEventRead = if (eventToCheck?.root?.sender == userId) { true } else { val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: return@doWithRealm - val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex - ?: Int.MIN_VALUE + ?: return@doWithRealm + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex + ?: Int.MIN_VALUE val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE eventToCheckIndex <= readReceiptIndex @@ -62,11 +62,11 @@ internal fun isReadMarkerMoreRecent(monarchy: Monarchy, } return Realm.getInstance(monarchy.realmConfiguration).use { realm -> val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return false - val eventToCheck = liveChunk.timelineEvents.find(eventId)?.root + val eventToCheck = liveChunk.timelineEvents.find(eventId) val readMarker = ReadMarkerEntity.where(realm, roomId).findFirst() ?: return false - val readMarkerIndex = liveChunk.timelineEvents.find(readMarker.eventId)?.root?.displayIndex - ?: Int.MIN_VALUE + val readMarkerIndex = liveChunk.timelineEvents.find(readMarker.eventId)?.displayIndex + ?: Int.MIN_VALUE val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE eventToCheckIndex <= readMarkerIndex } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 221e8ccb46..d5e3b13ac2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -34,22 +34,20 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, e .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray()) } -internal fun TimelineEventEntity.Companion.where(realm: Realm, - roomId: String? = null, - type: String? = null, - linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery { +internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm, + roomId: String): RealmQuery { + return realm.where().equalTo(TimelineEventEntityFields.ROOM_ID, roomId) +} + +internal fun TimelineEventEntity.Companion.whereType(realm: Realm, + type: String, + roomId: String? = null): RealmQuery { val query = realm.where() + query.equalTo(TimelineEventEntityFields.ROOT.TYPE, type) if (roomId != null) { query.equalTo(TimelineEventEntityFields.ROOM_ID, roomId) } - if (type != null) { - query.equalTo(TimelineEventEntityFields.ROOT.TYPE, type) - } - return when (linkFilterMode) { - LINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false) - UNLINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, true) - BOTH -> query - } + return query } internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List { @@ -71,7 +69,7 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, liveEvents } return query - ?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + ?.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) ?.findFirst() } @@ -83,32 +81,6 @@ internal fun RealmQuery.filterTypes(filterTypes: List.next(from: Int? = null, strict: Boolean = true): TimelineEventEntity? { - if (from != null) { - if (strict) { - this.greaterThan(TimelineEventEntityFields.ROOT.STATE_INDEX, from) - } else { - this.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, from) - } - } - return this - .sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.ASCENDING) - .findFirst() -} - -internal fun RealmQuery.prev(since: Int? = null, strict: Boolean = false): TimelineEventEntity? { - if (since != null) { - if (strict) { - this.lessThan(TimelineEventEntityFields.ROOT.STATE_INDEX, since) - } else { - this.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, since) - } - } - return this - .sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.DESCENDING) - .findFirst() -} - internal fun RealmList.find(eventId: String): TimelineEventEntity? { return this.where() .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt index 2893bf5126..9f5259bcb6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt @@ -21,10 +21,9 @@ 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.RoomAvatarContent import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields -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.getOrNull import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import javax.inject.Inject @@ -40,7 +39,7 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona fun resolve(roomId: String): String? { var res: String? = null monarchy.doWithRealm { realm -> - val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev() + val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")?.root res = ContentMapper.map(roomName?.content).toModel()?.avatarUrl if (!res.isNullOrEmpty()) { return@doWithRealm diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index bbb5feba15..26626ddfcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -24,11 +24,14 @@ 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.RoomTopicContent import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields 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.query.* +import im.vector.matrix.android.internal.database.query.getOrCreate +import im.vector.matrix.android.internal.database.query.getOrNull +import im.vector.matrix.android.internal.database.query.isEventRead +import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper @@ -89,10 +92,10 @@ internal class RoomSummaryUpdater @Inject constructor( } val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES) - val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() - val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() - val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() - val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev() + val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root + val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root + val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root + val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events 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 3610511dbf..e5e5d7ec5a 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,9 +18,11 @@ 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.api.session.room.send.SendState +import im.vector.matrix.android.internal.database.mapper.toEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.query.getOrCreate 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 @@ -47,7 +49,6 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, - private val timelineEventSenderVisitor: TimelineEventSenderVisitor, private val eventBus: EventBus ) : LoadRoomMembersTask { @@ -69,13 +70,16 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( ?: realm.createObject(roomId) for (roomMemberEvent in response.roomMemberEvents) { - roomEntity.addStateEvent(roomMemberEvent) + if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null) { + continue + } + val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED) + CurrentStateEventEntity.getOrCreate(realm, roomId, roomMemberEvent.stateKey, roomMemberEvent.type).apply { + eventId = roomMemberEvent.eventId + root = eventEntity + } roomMemberEventHandler.handle(realm, roomId, roomMemberEvent) } - timelineEventSenderVisitor.clear() - roomEntity.chunks.flatMap { it.timelineEvents }.forEach { - 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/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index 67b222ecb8..4056de1052 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -21,14 +21,17 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R 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.* +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.RoomCanonicalAliasContent +import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity +import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields 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.getOrNull import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import javax.inject.Inject @@ -57,19 +60,19 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: var name: CharSequence? = null monarchy.doWithRealm { realm -> val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).prev() + val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root name = ContentMapper.map(roomName?.content).toModel()?.name if (!name.isNullOrEmpty()) { return@doWithRealm } - val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() + val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root name = ContentMapper.map(canonicalAlias?.content).toModel()?.canonicalAlias if (!name.isNullOrEmpty()) { return@doWithRealm } - val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() + val aliases = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root name = ContentMapper.map(aliases?.content).toModel()?.aliases?.firstOrNull() if (!name.isNullOrEmpty()) { return@doWithRealm @@ -126,7 +129,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?, roomMemberHelper: RoomMemberHelper): String? { if (roomMemberSummary == null) return null - val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) + val isUnique = roomMemberHelper.isUniqueDisplayName() return if (isUnique) { roomMemberSummary.displayName } else { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberHelper.kt index 029afcbe40..094b4d2530 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberHelper.kt @@ -22,10 +22,10 @@ import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.where import io.realm.Realm import io.realm.RealmQuery -import io.realm.Sort /** * This class is an helper around STATE_ROOM_MEMBER events. @@ -41,11 +41,7 @@ internal class RoomMemberHelper(private val realm: Realm, } fun getLastStateEvent(userId: String): EventEntity? { - return EventEntity - .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .equalTo(EventEntityFields.STATE_KEY, userId) - .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) - .findFirst() + return CurrentStateEventEntity.getOrNull(realm, roomId, userId, EventType.STATE_ROOM_MEMBER)?.root } fun getLastRoomMember(userId: String): RoomMemberSummaryEntity? { @@ -54,16 +50,8 @@ internal class RoomMemberHelper(private val realm: Realm, .findFirst() } - fun isUniqueDisplayName(displayName: String?): Boolean { - if (displayName.isNullOrEmpty()) { - return true - } - return EventEntity - .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .contains(EventEntityFields.CONTENT, "\"displayname\":\"$displayName\"") - .distinct(EventEntityFields.STATE_KEY) - .findAll() - .size == 1 + fun isUniqueDisplayName(): Boolean { + return false } fun queryRoomMembersEvent(): RealmQuery { 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 8228136f10..89b5ae5a83 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,6 @@ 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.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,8 +40,7 @@ internal interface PruneEventTask : Task { ) } -internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy, - private val timelineEventSenderVisitor: TimelineEventSenderVisitor) : PruneEventTask { +internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask { override suspend fun execute(params: PruneEventTask.Params) { monarchy.awaitTransaction { realm -> @@ -97,10 +95,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M // } } } + // TODO : make it work again. Maybe waits for SQL rework... if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) { - timelineEventSenderVisitor.clear(roomId = eventToPrune.roomId, senderId = stateKey) - val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId) - timelineEventSenderVisitor.visit(timelineEventsToUpdate) + TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt index 5cdf4d1d4f..ab014596b0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt @@ -52,9 +52,7 @@ internal class LocalEchoRepository @Inject constructor(private val monarchy: Mon eventBus.post(DefaultTimeline.OnNewTimelineEvents(roomId = roomId, eventIds = listOf(eventId))) monarchy.awaitTransaction { realm -> val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@awaitTransaction - val eventEntity = event.toEntity(roomId).apply { - this.sendState = SendState.UNSENT - } + val eventEntity = event.toEntity(roomId, SendState.UNSENT) val roomMemberHelper = RoomMemberHelper(realm, roomId) val myUser = roomMemberHelper.getLastRoomMember(senderId) val localId = TimelineEventEntity.nextId(realm) @@ -64,7 +62,7 @@ internal class LocalEchoRepository @Inject constructor(private val monarchy: Mon it.roomId = roomId it.senderName = myUser?.displayName it.senderAvatar = myUser?.avatarUrl - it.isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(myUser?.displayName) + it.isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName() } roomEntity.sendingTimelineEvents.add(0, timelineEventEntity) roomSummaryUpdater.update(realm, roomId) @@ -103,7 +101,7 @@ internal class LocalEchoRepository @Inject constructor(private val monarchy: Mon return Realm.getInstance(monarchy.realmConfiguration).use { realm -> TimelineEventEntity .findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES) - .sortedByDescending { it.root?.displayIndex ?: 0 } + .sortedByDescending { it.displayIndex } .mapNotNull { it.root?.asDomain() } .filter { event -> when (event.getClearType()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index eb7208ea0d..9b027ede6f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -29,9 +29,8 @@ import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM 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.query.descending -import im.vector.matrix.android.internal.database.query.prev +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -51,14 +50,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override fun getStateEvent(eventType: String): Event? { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - EventEntity.where(realm, roomId, eventType).prev()?.asDomain() + CurrentStateEventEntity.getOrNull(realm, roomId, type = eventType, stateKey = "")?.root?.asDomain() } } override fun getStateEventLive(eventType: String): LiveData> { val liveData = monarchy.findAllMappedWithChanges( - { realm -> EventEntity.where(realm, roomId, eventType).descending() }, - { it.asDomain() } + { realm -> CurrentStateEventEntity.where(realm, roomId, type = eventType, stateKey = "") }, + { it.root?.asDomain() } ) return Transformations.map(liveData) { results -> results.firstOrNull().toOptional() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt index b532d61914..57d53ff4a8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt @@ -31,17 +31,18 @@ internal interface ClearUnlinkedEventsTask : Task + return + /*monarchy.awaitTransaction { localRealm -> val unlinkedChunks = ChunkEntity .where(localRealm, roomId = params.roomId) - .equalTo(ChunkEntityFields.IS_UNLINKED, true) .findAll() unlinkedChunks.forEach { it.deleteOnCascade() } } + */ } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 62268349b3..3a2415f593 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -38,6 +38,7 @@ import im.vector.matrix.android.internal.database.query.FilterContent import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.whereInRoom +import im.vector.matrix.android.internal.database.query.whereRoomId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.Debouncer @@ -177,7 +178,7 @@ internal class DefaultTimeline( } } - nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING).findAll() + nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() filteredEvents = nonFilteredEvents.where() .filterEventsWithSettings() .findAll() @@ -260,7 +261,7 @@ internal class DefaultTimeline( // Otherwise, we should check if the event is in the db, but is hidden because of filters return Realm.getInstance(realmConfiguration).use { localRealm -> val nonFilteredEvents = buildEventQuery(localRealm) - .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .findAll() val nonFilteredEvent = nonFilteredEvents.where() @@ -275,11 +276,11 @@ internal class DefaultTimeline( .findFirst() == null if (isHidden) { - val displayIndex = nonFilteredEvent?.root?.displayIndex + val displayIndex = nonFilteredEvent?.displayIndex if (displayIndex != null) { // Then we are looking for the first displayable event after the hidden one val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex) + .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) .findFirst() firstDisplayedEvent?.eventId } else { @@ -358,14 +359,14 @@ internal class DefaultTimeline( updateState(Timeline.Direction.FORWARDS) { it.copy( - hasMoreInCache = firstBuiltEvent == null || firstBuiltEvent.displayIndex < firstCacheEvent?.root?.displayIndex ?: Int.MIN_VALUE, + hasMoreInCache = firstBuiltEvent == null || firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE, hasReachedEnd = chunkEntity?.isLastForward ?: false ) } updateState(Timeline.Direction.BACKWARDS) { it.copy( - hasMoreInCache = lastBuiltEvent == null || lastBuiltEvent.displayIndex > lastCacheEvent?.root?.displayIndex ?: Int.MAX_VALUE, + hasMoreInCache = lastBuiltEvent == null || lastBuiltEvent.displayIndex > lastCacheEvent?.displayIndex ?: Int.MAX_VALUE, hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE ) } @@ -440,14 +441,14 @@ internal class DefaultTimeline( var shouldFetchInitialEvent = false val currentInitialEventId = initialEventId val initialDisplayIndex = if (currentInitialEventId == null) { - filteredEvents.firstOrNull()?.root?.displayIndex + filteredEvents.firstOrNull()?.displayIndex } else { val initialEvent = nonFilteredEvents.where() .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId) .findFirst() shouldFetchInitialEvent = initialEvent == null - initialEvent?.root?.displayIndex + initialEvent?.displayIndex } prevDisplayIndex = initialDisplayIndex nextDisplayIndex = initialDisplayIndex @@ -476,9 +477,9 @@ internal class DefaultTimeline( var postSnapshot = false changeSet.insertionRanges.forEach { range -> val (startDisplayIndex, direction) = if (range.startIndex == 0) { - Pair(filteredEvents[range.length - 1]!!.root!!.displayIndex, Timeline.Direction.FORWARDS) + Pair(filteredEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS) } else { - Pair(filteredEvents[range.startIndex]!!.root!!.displayIndex, Timeline.Direction.BACKWARDS) + Pair(filteredEvents[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS) } val state = getState(direction) if (state.isPaginating) { @@ -579,7 +580,7 @@ internal class DefaultTimeline( if (offsetResults.isEmpty()) { return 0 } - val offsetIndex = offsetResults.last()!!.root!!.displayIndex + val offsetIndex = offsetResults.last()!!.displayIndex if (direction == Timeline.Direction.BACKWARDS) { prevDisplayIndex = offsetIndex - 1 } else { @@ -620,18 +621,18 @@ internal class DefaultTimeline( strict: Boolean): RealmResults { val offsetQuery = filteredEvents.where() if (direction == Timeline.Direction.BACKWARDS) { - offsetQuery.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + offsetQuery.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) if (strict) { - offsetQuery.lessThan(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) + offsetQuery.lessThan(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) } else { - offsetQuery.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) + offsetQuery.lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) } } else { - offsetQuery.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) + offsetQuery.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) if (strict) { - offsetQuery.greaterThan(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) + offsetQuery.greaterThan(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) } else { - offsetQuery.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) + offsetQuery.greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) } } return offsetQuery @@ -642,11 +643,11 @@ internal class DefaultTimeline( private fun buildEventQuery(realm: Realm): RealmQuery { return if (initialEventId == null) { TimelineEventEntity - .where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY) + .whereRoomId(realm, roomId = roomId) .equalTo("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true) } else { TimelineEventEntity - .where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH) + .whereRoomId(realm, roomId = roomId) .`in`("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID}", arrayOf(initialEventId)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index 1a1f90c7a3..05912ba240 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -76,12 +76,12 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue val isLoaded = nonFilteredEvents.where() .equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null - val displayIndex = timelineEvent.root?.displayIndex + val displayIndex = timelineEvent.displayIndex - if (isLoaded && displayIndex != null) { + if (isLoaded) { // Then we are looking for the first displayable event after the hidden one val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex) + .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) .findFirst() // If we find one, we should 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 87c59e832b..177cfcdbbc 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 @@ -17,12 +17,18 @@ package im.vector.matrix.android.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy +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.room.send.SendState import im.vector.matrix.android.internal.database.helper.* +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity 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.find -import im.vector.matrix.android.internal.database.query.findAllIncludingEvents +import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.kotlin.createObject @@ -32,8 +38,7 @@ import javax.inject.Inject /** * Insert Chunk in DB, and eventually merge with existing chunk event */ -internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy, - private val timelineEventSenderVisitor: TimelineEventSenderVisitor) { +internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy) { /** *
@@ -110,7 +115,6 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
         monarchy
                 .awaitTransaction { realm ->
                     Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
-
                     val roomEntity = RoomEntity.where(realm, roomId).findFirst()
                             ?: realm.createObject(roomId)
 
@@ -124,50 +128,44 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
                         prevToken = receivedChunk.end
                     }
 
-                    val shouldSkip = ChunkEntity.find(realm, roomId, nextToken = nextToken) != null
-                            || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null
-
                     val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
                     val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
 
                     // The current chunk is the one we will keep all along the merge processChanges.
                     // We try to look for a chunk next to the token,
                     // otherwise we create a whole new one which is unlinked (not live)
-
-                    var currentChunk = if (direction == PaginationDirection.FORWARDS) {
+                    val currentChunk = if (direction == PaginationDirection.FORWARDS) {
                         prevChunk?.apply { this.nextToken = nextToken }
                     } else {
                         nextChunk?.apply { this.prevToken = prevToken }
                     }
-                            ?: ChunkEntity.create(realm, prevToken, nextToken, isUnlinked = true)
+                            ?: ChunkEntity.create(realm, prevToken, nextToken)
 
                     if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
                         Timber.v("Reach end of $roomId")
                         currentChunk.isLastBackward = true
-                    } else if (!shouldSkip) {
+                    } else {
                         Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
-                        val timelineEvents = receivedChunk.events.mapNotNull {
-                            currentChunk.add(roomId, it, direction)
-                        }
-                        // Then we merge chunks if needed
-                        if (currentChunk != prevChunk && prevChunk != null) {
-                            currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk)
-                        } else if (currentChunk != nextChunk && nextChunk != null) {
-                            currentChunk = handleMerge(roomEntity, direction, currentChunk, nextChunk)
+                        val roomMemberEventsByUser = HashMap()
+                        val eventList = if (direction == PaginationDirection.FORWARDS) {
+                            receivedChunk.events
                         } else {
-                            val newEventIds = receivedChunk.events.mapNotNull { it.eventId }
-                            val overlappedChunks = ChunkEntity.findAllIncludingEvents(realm, newEventIds)
-                            overlappedChunks
-                                    .filter { it != currentChunk }
-                                    .forEach { overlapped ->
-                                        currentChunk = handleMerge(roomEntity, direction, currentChunk, overlapped)
-                                    }
+                            receivedChunk.events.asReversed()
+                        }
+                        for (event in eventList) {
+                            if (event.eventId == null || event.senderId == null) {
+                                continue
+                            }
+                            val eventEntity = event.toEntity(roomId, SendState.SYNCED)
+                            if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) {
+                                roomMemberEventsByUser[event.stateKey] = event
+                            }
+                            val roomMemberEvent = roomMemberEventsByUser.getOrPut(event.senderId) {
+                                CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root?.asDomain()
+                            }
+                            currentChunk.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberEvent)
                         }
                         roomEntity.addOrUpdate(currentChunk)
-                        for (stateEvent in receivedChunk.stateEvents) {
-                            roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked)
-                        }
-                        timelineEventSenderVisitor.visit(timelineEvents)
                     }
                 }
         return if (receivedChunk.events.isEmpty()) {
@@ -180,23 +178,4 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
             Result.SUCCESS
         }
     }
-
-    private fun handleMerge(roomEntity: RoomEntity,
-                            direction: PaginationDirection,
-                            currentChunk: ChunkEntity,
-                            otherChunk: ChunkEntity): ChunkEntity {
-        // 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) {
-            val events = currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
-            timelineEventSenderVisitor.visit(events)
-            roomEntity.deleteOnCascade(otherChunk)
-            currentChunk
-        } else {
-            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 9a24eb502a..77762dfb89 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
@@ -22,14 +22,18 @@ 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.Membership
 import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
+import im.vector.matrix.android.api.session.room.send.SendState
 import im.vector.matrix.android.internal.crypto.DefaultCryptoService
 import im.vector.matrix.android.internal.database.helper.*
+import im.vector.matrix.android.internal.database.mapper.asDomain
+import im.vector.matrix.android.internal.database.mapper.toEntity
 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.CurrentStateEventEntity
 import im.vector.matrix.android.internal.database.model.RoomEntity
-import im.vector.matrix.android.internal.database.model.TimelineEventEntity
 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.getOrCreate
+import im.vector.matrix.android.internal.database.query.getOrNull
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
 import im.vector.matrix.android.internal.session.mapWithProgress
@@ -52,7 +56,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                                    private val roomFullyReadHandler: RoomFullyReadHandler,
                                                    private val cryptoService: DefaultCryptoService,
                                                    private val roomMemberEventHandler: RoomMemberEventHandler,
-                                                   private val timelineEventSenderVisitor: TimelineEventSenderVisitor,
                                                    private val eventBus: EventBus) {
 
     sealed class HandlingStrategy {
@@ -118,13 +121,16 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         roomEntity.membership = Membership.JOIN
 
         // State event
-
         if (roomSync.state?.events?.isNotEmpty() == true) {
-            val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
-                    ?: Int.MIN_VALUE
-            val untimelinedStateIndex = minStateIndex + 1
-            roomSync.state.events.forEach { event ->
-                roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
+            for (event in roomSync.state.events) {
+                if (event.eventId == null || event.stateKey == null) {
+                    continue
+                }
+                val eventEntity = event.toEntity(roomId, SendState.SYNCED)
+                CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
+                    eventId = event.eventId
+                    root = eventEntity
+                }
                 // Give info to crypto module
                 cryptoService.onStateEvent(roomId, event)
                 roomMemberEventHandler.handle(realm, roomId, event)
@@ -193,28 +199,37 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                      prevToken: String? = null,
                                      isLimited: Boolean = true): ChunkEntity {
         val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId)
-        var stateIndexOffset = 0
         val chunkEntity = if (!isLimited && lastChunk != null) {
             lastChunk
         } else {
             realm.createObject().apply { this.prevToken = prevToken }
         }
-        if (isLimited && lastChunk != null) {
-            stateIndexOffset = lastChunk.lastStateIndex(PaginationDirection.FORWARDS)
-        }
         lastChunk?.isLastForward = false
         chunkEntity.isLastForward = true
-        chunkEntity.isUnlinked = false
 
-        val timelineEvents = ArrayList(eventList.size)
         val eventIds = ArrayList(eventList.size)
+        val roomMemberEventsByUser = HashMap()
+
         for (event in eventList) {
-            if(event.eventId != null) {
-                eventIds.add(event.eventId)
+            if (event.eventId == null || event.senderId == null) {
+                continue
             }
-            chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)?.also {
-                timelineEvents.add(it)
+            eventIds.add(event.eventId)
+            val eventEntity = event.toEntity(roomId, SendState.SYNCED)
+            if (event.isStateEvent() && event.stateKey != null) {
+                CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
+                    eventId = event.eventId
+                    root = eventEntity
+                }
+                if (event.type == EventType.STATE_ROOM_MEMBER) {
+                    roomMemberEventsByUser[event.stateKey] = event
+                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
+                }
             }
+            val roomMemberEvent = roomMemberEventsByUser.getOrPut(event.senderId) {
+                CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root?.asDomain()
+            }
+            chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberEvent)
             // Give info to crypto module
             cryptoService.onLiveEvent(roomEntity.roomId, event)
             // Try to remove local echo
@@ -227,9 +242,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     Timber.v("Can't find corresponding local echo for tx:$it")
                 }
             }
-            roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
         }
-        timelineEventSenderVisitor.visit(timelineEvents)
         // posting new events to timeline if any is registered
         eventBus.post(DefaultTimeline.OnNewTimelineEvents(roomId = roomId, eventIds = eventIds))
         return chunkEntity
diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt
index 45ceeed49a..d9da08254f 100644
--- a/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt
@@ -61,7 +61,7 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
             ViewCompat.setTransitionName(imageTransitionView, it)
         }
         if (mediaData.url.isNullOrEmpty()) {
-            finish()
+            supportFinishAfterTransition()
             return
         }