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 506e37891d..5fd7e07b89 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 @@ -26,10 +26,12 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields +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() { @@ -38,14 +40,38 @@ internal fun ChunkEntity.deleteOnCascade() { this.deleteFromRealm() } +internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity, direction: PaginationDirection) { + assertIsManaged() + val eventsToMerge: List + if (direction == PaginationDirection.FORWARDS) { + this.nextToken = chunkToMerge.nextToken + this.isLastForward = chunkToMerge.isLastForward + eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + } else { + this.prevToken = chunkToMerge.prevToken + this.isLastBackward = chunkToMerge.isLastBackward + eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + } + return eventsToMerge + .forEach { + if (timelineEvents.find(it.eventId) == null) { + it.displayIndex = nextDisplayIndex(direction) + this.timelineEvents.add(it) + } + } +} + internal fun ChunkEntity.addTimelineEvent(roomId: String, eventEntity: EventEntity, direction: PaginationDirection, - roomMemberEvent: Event?): TimelineEventEntity { + roomMemberEvent: Event?) { + val eventId = eventEntity.eventId + if (timelineEvents.find(eventId) != null) { + return + } val displayIndex = nextDisplayIndex(direction) val localId = TimelineEventEntity.nextId(realm) - val eventId = eventEntity.eventId val senderId = eventEntity.sender ?: "" val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() @@ -69,24 +95,23 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, } } - val timelineEventEntity = TimelineEventEntity().also { - it.localId = localId - it.root = eventEntity - it.eventId = eventId - it.roomId = roomId - it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() - it.readReceipts = readReceiptsSummaryEntity - it.displayIndex = displayIndex - } - if (roomMemberEvent != null) { - val roomMemberContent = roomMemberEvent.content.toModel() - timelineEventEntity.senderAvatar = roomMemberContent?.avatarUrl - timelineEventEntity.senderName = roomMemberContent?.displayName - timelineEventEntity.isUniqueDisplayName = false - timelineEventEntity.senderMembershipEventId = roomMemberEvent.eventId + val timelineEventEntity = realm.createObject().apply { + this.localId = localId + this.root = eventEntity + this.eventId = eventId + this.roomId = roomId + this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + this.readReceipts = readReceiptsSummaryEntity + this.displayIndex = displayIndex + if (roomMemberEvent != null) { + val roomMemberContent = roomMemberEvent.content.toModel() + this.senderAvatar = roomMemberContent?.avatarUrl + this.senderName = roomMemberContent?.displayName + this.isUniqueDisplayName = false + this.senderMembershipEventId = roomMemberEvent.eventId + } } timelineEvents.add(timelineEventEntity) - return timelineEventEntity } internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int { 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 b1aedbe21a..eb4086e1d6 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 @@ -26,9 +26,7 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, @Index var nextToken: String? = null, var timelineEvents: RealmList = RealmList(), @Index var isLastForward: Boolean = false, - @Index var isLastBackward: Boolean = false, - var backwardsDisplayIndex: Int? = null, - var forwardsDisplayIndex: Int? = null + @Index var isLastBackward: Boolean = false ) : RealmObject() { fun identifier() = "${prevToken}_$nextToken" 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 cb64ecf470..834cb7f520 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 @@ -41,12 +41,6 @@ internal open class EventEntity(@PrimaryKey var eventId: String = "", var decryptionErrorCode: String? = null ) : RealmObject() { - enum class LinkFilterMode { - LINKED_ONLY, - UNLINKED_ONLY, - BOTH - } - private var sendStateStr: String = SendState.UNKNOWN.name var sendState: SendState @@ -59,9 +53,6 @@ internal open class EventEntity(@PrimaryKey var eventId: String = "", companion object - @LinkingObjects("root") - val timelineEventEntity: RealmResults? = null - fun setDecryptionResult(result: MXEventDecryptionResult) { val decryptionResult = OlmDecryptionResult( payload = result.clearEvent, @@ -72,6 +63,5 @@ internal open class EventEntity(@PrimaryKey var eventId: String = "", val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java) decryptionResultJson = adapter.toJson(decryptionResult) decryptionErrorCode = null - timelineEventEntity?.firstOrNull()?.root = this } } 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 63a11b7ff9..0374f860dc 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 @@ -23,7 +23,7 @@ import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey internal open class TimelineEventEntity(var localId: Long = 0, - @PrimaryKey var eventId: String = "", + @Index var eventId: String = "", @Index var roomId: String = "", @Index var displayIndex: Int = 0, var root: EventEntity? = null, 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 590a2162f2..f8f9ab724b 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 @@ -17,12 +17,10 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery -import io.realm.Sort import io.realm.kotlin.where internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { 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 d5e3b13ac2..a182bc63d9 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 @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import io.realm.* import io.realm.kotlin.where diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 5551930bd1..c4bba4cbf6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -129,9 +129,6 @@ internal abstract class RoomModule { @Binds abstract fun bindGetContextOfEventTask(task: DefaultGetContextOfEventTask): GetContextOfEventTask - @Binds - abstract fun bindClearUnlinkedEventsTask(task: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask - @Binds abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask 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 26626ddfcc..bca04bbba4 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 @@ -48,21 +48,23 @@ internal class RoomSummaryUpdater @Inject constructor( private val monarchy: Monarchy) { // TODO: maybe allow user of SDK to give that list - private val PREVIEWABLE_TYPES = listOf( - EventType.MESSAGE, - EventType.STATE_ROOM_NAME, - EventType.STATE_ROOM_TOPIC, - EventType.STATE_ROOM_MEMBER, - EventType.STATE_ROOM_HISTORY_VISIBILITY, - EventType.CALL_INVITE, - EventType.CALL_HANGUP, - EventType.CALL_ANSWER, - EventType.ENCRYPTED, - EventType.STATE_ROOM_ENCRYPTION, - EventType.STATE_ROOM_THIRD_PARTY_INVITE, - EventType.STICKER, - EventType.STATE_ROOM_CREATE - ) + companion object { + val PREVIEWABLE_TYPES = listOf( + EventType.MESSAGE, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER, + EventType.ENCRYPTED, + EventType.STATE_ROOM_ENCRYPTION, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STICKER, + EventType.STATE_ROOM_CREATE + ) + } fun update(realm: Realm, roomId: String, 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 deleted file mode 100644 index 57d53ff4a8..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/ClearUnlinkedEventsTask.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - - */ -package im.vector.matrix.android.internal.session.room.timeline - -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.database.helper.deleteOnCascade -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.ChunkEntityFields -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.awaitTransaction -import javax.inject.Inject - -internal interface ClearUnlinkedEventsTask : Task { - - data class Params(val roomId: String) -} - -internal class DefaultClearUnlinkedEventsTask @Inject constructor() : ClearUnlinkedEventsTask { - - override suspend fun execute(params: ClearUnlinkedEventsTask.Params) { - return - /*monarchy.awaitTransaction { localRealm -> - val unlinkedChunks = ChunkEntity - .where(localRealm, roomId = params.roomId) - .findAll() - unlinkedChunks.forEach { - it.deleteOnCascade() - } - } - */ - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt index 966bdcc1fd..282de3471f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt @@ -27,8 +27,7 @@ internal interface GetContextOfEventTask : Task(eventBus) { - apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, params.limit, filter) + // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. + apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 1, filter) } return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS) } 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 3a2415f593..a2aedf5f4d 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 @@ -71,7 +71,6 @@ internal class DefaultTimeline( private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, - private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask, private val paginationTask: PaginationTask, private val cryptoService: CryptoService, private val timelineEventMapper: TimelineEventMapper, @@ -225,9 +224,6 @@ internal class DefaultTimeline( } eventDecryptor.destroy() } - clearUnlinkedEventsTask - .configureWith(ClearUnlinkedEventsTask.Params(roomId)) - .executeBy(taskExecutor) } } @@ -653,7 +649,7 @@ internal class DefaultTimeline( } private fun fetchEvent(eventId: String) { - val params = GetContextOfEventTask.Params(roomId, eventId, settings.initialSize) + val params = GetContextOfEventTask.Params(roomId, eventId) cancelableBag += contextOfEventTask.configureWith(params) { callback = object : MatrixCallback { override fun onFailure(failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 5ed3c76ed6..3e783f98a4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -44,8 +44,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv private val cryptoService: CryptoService, private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper ) : TimelineService { @AssistedInject.Factory @@ -60,7 +59,6 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv realmConfiguration = monarchy.realmConfiguration, taskExecutor = taskExecutor, contextOfEventTask = contextOfEventTask, - clearUnlinkedEventsTask = clearUnlinkedEventsTask, paginationTask = paginationTask, cryptoService = cryptoService, timelineEventMapper = timelineEventMapper, 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 1e75a52133..167d047ef3 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 @@ -22,17 +22,27 @@ 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.addOrUpdate import im.vector.matrix.android.internal.database.helper.addTimelineEvent +import im.vector.matrix.android.internal.database.helper.deleteOnCascade +import im.vector.matrix.android.internal.database.helper.merge 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.EventEntity import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity 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.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.getOrNull +import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.util.awaitTransaction +import io.realm.Realm +import io.realm.RealmList import io.realm.kotlin.createObject import timber.log.Timber import javax.inject.Inject @@ -117,8 +127,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) val nextToken: String? val prevToken: String? @@ -144,47 +152,9 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy ?: ChunkEntity.create(realm, prevToken, nextToken) if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) { - Timber.v("Reach end of $roomId") - currentChunk.isLastBackward = true + handleReachEnd(realm, roomId, direction, currentChunk) } else { - Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") - val roomMemberEventsByUser = HashMap() - val eventList = if (direction == PaginationDirection.FORWARDS) { - receivedChunk.events - } else { - receivedChunk.events.asReversed() - } - val stateEvents = receivedChunk.stateEvents - for (stateEvent in stateEvents) { - if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null && !stateEvent.isRedacted()) { - roomMemberEventsByUser[stateEvent.stateKey] = stateEvent - } - } - val eventEntities = ArrayList(eventList.size) - for (event in eventList) { - if (event.eventId == null || event.senderId == null) { - continue - } - val eventEntity = event.toEntity(roomId, SendState.SYNCED).also { - realm.copyToRealmOrUpdate(it) - } - if(direction == PaginationDirection.FORWARDS){ - eventEntities.add(eventEntity) - }else { - eventEntities.add(0, eventEntity) - } - if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null && !event.isRedacted()) { - roomMemberEventsByUser[event.stateKey] = event - } - } - for (eventEntity in eventEntities) { - val senderId = eventEntity.sender ?: continue - val roomMemberEvent = roomMemberEventsByUser.getOrPut(senderId) { - CurrentStateEventEntity.getOrNull(realm, roomId, senderId, EventType.STATE_ROOM_MEMBER)?.root?.asDomain() - } - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberEvent) - } - roomEntity.addOrUpdate(currentChunk) + handlePagination(realm, roomId, direction, receivedChunk, currentChunk) } } return if (receivedChunk.events.isEmpty()) { @@ -197,4 +167,81 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy Result.SUCCESS } } + + private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { + Timber.v("Reach end of $roomId") + if (direction == PaginationDirection.FORWARDS) { + val currentLiveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) + if (currentChunk != currentLiveChunk) { + currentChunk.isLastForward = true + currentLiveChunk?.deleteOnCascade() + RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { + latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES) + } + } + } else { + currentChunk.isLastBackward = true + } + } + + private fun handlePagination( + realm: Realm, + roomId: String, + direction: PaginationDirection, + receivedChunk: TokenChunkEvent, + currentChunk: ChunkEntity + ) { + Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") + val roomMemberEventsByUser = HashMap() + val eventList = if (direction == PaginationDirection.FORWARDS) { + receivedChunk.events + } else { + receivedChunk.events.asReversed() + } + val stateEvents = receivedChunk.stateEvents + for (stateEvent in stateEvents) { + if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null && !stateEvent.isRedacted()) { + roomMemberEventsByUser[stateEvent.stateKey] = stateEvent + } + } + val eventIds = ArrayList(eventList.size) + val eventEntities = ArrayList(eventList.size) + for (event in eventList) { + if (event.eventId == null || event.senderId == null) { + continue + } + eventIds.add(event.eventId) + val eventEntity = event.toEntity(roomId, SendState.SYNCED).let { + realm.copyToRealmOrUpdate(it) + } + if (direction == PaginationDirection.FORWARDS) { + eventEntities.add(eventEntity) + } else { + eventEntities.add(0, eventEntity) + } + if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null && !event.isRedacted()) { + roomMemberEventsByUser[event.stateKey] = event + } + } + for (eventEntity in eventEntities) { + val senderId = eventEntity.sender ?: continue + val roomMemberEvent = roomMemberEventsByUser.getOrPut(senderId) { + CurrentStateEventEntity.getOrNull(realm, roomId, senderId, EventType.STATE_ROOM_MEMBER)?.root?.asDomain() + } + currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberEvent) + } + + val chunks = ChunkEntity.findAllIncludingEvents(realm, eventIds) + val chunksToDelete = ArrayList() + chunks.forEach { + if (it != currentChunk) { + currentChunk.merge(it, direction) + chunksToDelete.add(it) + } + } + chunksToDelete.forEach { + it.deleteFromRealm() + } + RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk) + } }