diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 62a24bc881..86c6b00c83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -1,10 +1,10 @@ package im.vector.matrix.android.api.session.events.model -import com.google.gson.JsonObject import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.squareup.moshi.Types import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.legacy.util.JsonUtils +import java.lang.reflect.ParameterizedType typealias Content = Map @@ -23,14 +23,8 @@ data class Event( ) { - val contentAsJsonObject: JsonObject? by lazy { - val gson = JsonUtils.getGson(true) - gson.toJsonTree(content).asJsonObject - } - - val prevContentAsJsonObject: JsonObject? by lazy { - val gson = JsonUtils.getGson(true) - gson.toJsonTree(prevContent).asJsonObject + fun isStateEvent(): Boolean { + return EventType.isStateEvent(type) } inline fun content(): T? { @@ -47,4 +41,7 @@ data class Event( return moshiAdapter.fromJsonValue(data) } + companion object { + internal val CONTENT_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) + } } \ No newline at end of file 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 new file mode 100644 index 0000000000..b9382a7313 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -0,0 +1,21 @@ +package im.vector.matrix.android.internal.database.helper + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.internal.database.mapper.asEntity +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.query.fastContains +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection + +internal fun ChunkEntity.add(event: Event, stateIndex: Int, paginationDirection: PaginationDirection) { + if (!this.isManaged) { + throw IllegalStateException("Chunk entity should be managed to use fast contains") + } + + val eventEntity = event.asEntity() + eventEntity.stateIndex = stateIndex + + if (!this.events.fastContains(eventEntity)) { + val position = if (paginationDirection == PaginationDirection.FORWARDS) 0 else this.events.size + this.events.add(position, eventEntity) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt deleted file mode 100644 index 24fdac069d..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/EventListHelper.kt +++ /dev/null @@ -1,18 +0,0 @@ -package im.vector.matrix.android.internal.database.helper - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.internal.database.mapper.asEntity -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.query.fastContains - -internal fun List.addManagedToChunk(chunkEntity: ChunkEntity) { - if (!chunkEntity.isManaged) { - throw IllegalStateException("Chunk entity should be managed to use fast contains") - } - this.forEach { event -> - val eventEntity = event.asEntity() - if (!chunkEntity.events.fastContains(eventEntity)) { - chunkEntity.events.add(eventEntity) - } - } -} \ No newline at end of file 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 1233a41da0..425896a8cf 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 @@ -1,6 +1,5 @@ package im.vector.matrix.android.internal.database.mapper -import com.squareup.moshi.Types 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.internal.database.model.EventEntity @@ -10,8 +9,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider internal object EventMapper { private val moshi = MoshiProvider.providesMoshi() - private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) - private val adapter = moshi.adapter>(type) + private val adapter = moshi.adapter>(Event.CONTENT_TYPE) fun map(event: Event): EventEntity { val eventEntity = EventEntity() 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 354720df84..31ea66e966 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 @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.database.model +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.RealmList import io.realm.RealmObject import io.realm.RealmResults @@ -7,6 +8,8 @@ import io.realm.annotations.LinkingObjects internal open class ChunkEntity(var prevToken: String? = null, var nextToken: String? = null, + var prevStateIndex: Int = -1, + var nextStateIndex: Int = 1, var isLast: Boolean = false, var events: RealmList = RealmList() ) : RealmObject() { @@ -15,4 +18,20 @@ internal open class ChunkEntity(var prevToken: String? = null, val room: RealmResults? = null companion object + + fun stateIndex(direction: PaginationDirection): Int { + return when (direction) { + PaginationDirection.FORWARDS -> nextStateIndex + PaginationDirection.BACKWARDS -> prevStateIndex + } + } + + fun updateStateIndex(stateIndex: Int, direction: PaginationDirection){ + when (direction) { + PaginationDirection.FORWARDS -> nextStateIndex = stateIndex + PaginationDirection.BACKWARDS -> prevStateIndex = stateIndex + } + } + + } \ No newline at end of file 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 f54fa93f9b..49b78ca50d 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 @@ -3,20 +3,22 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects -import io.realm.annotations.PrimaryKey -internal open class EventEntity(@PrimaryKey var eventId: String = "", - var type: String = "", - var content: String = "", - var prevContent: String? = null, - var stateKey: String? = null, - var originServerTs: Long? = null, - var sender: String? = null, - var age: Long? = 0, - var redacts: String? = null +internal open class EventEntity(var eventId: String = "", + var type: String = "", + var content: String = "", + var prevContent: String? = null, + var stateKey: String? = null, + var originServerTs: Long? = null, + var sender: String? = null, + var age: Long? = 0, + var redacts: String? = null, + var stateIndex: Int = 0 ) : RealmObject() { - companion object + companion object { + const val DEFAULT_STATE_INDEX = Int.MIN_VALUE + } @LinkingObjects("events") val chunk: RealmResults? = 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 66c00d4b33..9ca8267d09 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -26,18 +26,13 @@ internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, t return query } -internal fun EventEntity.Companion.stateEvents(realm: Realm, roomId: String): RealmQuery { - return realm.where() - .equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId) - .isNotNull(EventEntityFields.STATE_KEY) -} -internal fun RealmQuery.last(from: Long? = null): EventEntity? { +internal fun RealmQuery.last(from: Int? = null): EventEntity? { if (from != null) { - this.lessThan(EventEntityFields.ORIGIN_SERVER_TS, from) + this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, from) } return this - .sort(EventEntityFields.ORIGIN_SERVER_TS, Sort.DESCENDING) + .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .findFirst() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt index 5eafc53496..a3010dcaa4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt @@ -19,10 +19,18 @@ internal class MessageEventInterceptor(val monarchy: Monarchy) : EnrichedEventIn override fun enrich(roomId: String, event: EnrichedEvent) { monarchy.doWithRealm { realm -> + + if (event.root.eventId == null) { + return@doWithRealm + } + + val rootEntity = EventEntity.where(realm, eventId = event.root.eventId).findFirst() + ?: return@doWithRealm + val roomMember = EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) .equalTo(EventEntityFields.STATE_KEY, event.root.sender) - .last(from = event.root.originServerTs) + .last(from = rootEntity.stateIndex) ?.asDomain() event.enrichWith(roomMember) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMembers.kt index 5e2c52cd7d..4d9e407126 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMembers.kt @@ -21,7 +21,7 @@ internal class RoomMembers(private val realm: Realm, fun getLoaded(): Map { return EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .sort(EventEntityFields.ORIGIN_SERVER_TS) + .sort(EventEntityFields.STATE_INDEX) .findAll() .map { it.asDomain() } .associateBy { it.stateKey!! } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt index 9a317861ec..b83c18454e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt @@ -8,12 +8,9 @@ import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInte import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.ChunkEntityFields -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.session.events.interceptor.MessageEventInterceptor -import io.realm.Sort private const val PAGE_SIZE = 30 @@ -31,9 +28,9 @@ internal class DefaultTimelineHolder(private val roomId: String, override fun liveTimeline(): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> - EventEntity.where(realm, roomId = roomId) - .equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST}", true) - .sort(EventEntityFields.ORIGIN_SERVER_TS, Sort.DESCENDING) + ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) + ?.events + ?.where() } val domainSourceFactory = realmDataSourceFactory .map { it.asDomain() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationDirection.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationDirection.kt index 86084d5d65..0319a3db11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationDirection.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationDirection.kt @@ -11,5 +11,13 @@ internal enum class PaginationDirection(val value: String) { * Backwards when the event is added to the start of the timeline. * These events come from a back pagination. */ - BACKWARDS("b") + BACKWARDS("b"); + + val incrementStateIndex: Int by lazy { + when (this) { + FORWARDS -> 1 + BACKWARDS -> -1 + } + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt index c4ee9669ad..f6e5278916 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt @@ -4,8 +4,9 @@ import arrow.core.Try import arrow.core.failure import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.database.helper.addManagedToChunk +import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.fastContains @@ -26,9 +27,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class PaginationRequest(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val stateEventsChunkHandler: StateEventsChunkHandler) { + private val monarchy: Monarchy, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val stateEventsChunkHandler: StateEventsChunkHandler) { fun execute(roomId: String, from: String?, @@ -56,11 +57,11 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, executeRequest { apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter) }.flatMap { chunk -> - insertInDb(chunk, roomId) + insertInDb(chunk, roomId, direction) } } - private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String): Try { + private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String, direction: PaginationDirection): Try { return monarchy .tryTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() @@ -74,15 +75,18 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken) val eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! } - val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds) + val chunksOverlapped = realm.copyFromRealm(ChunkEntity.findAllIncludingEvents(realm, eventIds)) val hasOverlapped = chunksOverlapped.isNotEmpty() - val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents) - if (!roomEntity.chunks.contains(stateEventsChunk)) { - roomEntity.chunks.add(stateEventsChunk) - } + var currentStateIndex = currentChunk.stateIndex(direction) + val incrementStateIndex = direction.incrementStateIndex - receivedChunk.events.addManagedToChunk(currentChunk) + receivedChunk.events.forEach { event -> + currentChunk.add(event, currentStateIndex, direction) + if (EventType.isStateEvent(event.type)) { + currentStateIndex += incrementStateIndex + } + } if (prevChunk != null) { currentChunk.events.addAll(prevChunk.events) @@ -94,14 +98,26 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, if (!currentChunk.events.fastContains(event)) { currentChunk.events.add(event) } + if (EventType.isStateEvent(event.type)) { + currentStateIndex += incrementStateIndex + } } currentChunk.prevToken = overlapped.prevToken roomEntity.chunks.remove(overlapped) } } + if (!roomEntity.chunks.contains(currentChunk)) { roomEntity.chunks.add(currentChunk) } + + currentChunk.updateStateIndex(currentStateIndex, direction) + + + val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents) + if (!roomEntity.chunks.contains(stateEventsChunk)) { + roomEntity.chunks.add(stateEventsChunk) + } } .map { receivedChunk } } 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 e0826ba98a..7c6b0a5183 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 @@ -4,13 +4,14 @@ 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.model.MyMembership -import im.vector.matrix.android.internal.database.helper.addManagedToChunk +import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral @@ -68,6 +69,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, roomEntity.chunks.add(chunkEntity) } } + if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited) if (!roomEntity.chunks.contains(chunkEntity)) { @@ -148,7 +150,15 @@ internal class RoomSyncHandler(private val monarchy: Monarchy, lastChunk?.isLast = false chunkEntity.isLast = true chunkEntity.nextToken = nextToken - eventList.addManagedToChunk(chunkEntity) + + var currentStateIndex = chunkEntity.nextStateIndex + eventList.forEach { event -> + if (event.isStateEvent() && currentStateIndex != 0) { + currentStateIndex += 1 + } + chunkEntity.add(event, currentStateIndex, PaginationDirection.FORWARDS) + } + chunkEntity.nextStateIndex = currentStateIndex return chunkEntity } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt index 1e355dfc2d..bc98000bf2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/StateEventsChunkHandler.kt @@ -2,9 +2,11 @@ package im.vector.matrix.android.internal.session.sync import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.DBConstants -import im.vector.matrix.android.internal.database.helper.addManagedToChunk +import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.findWithNextToken +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Realm import io.realm.kotlin.createObject @@ -18,7 +20,10 @@ internal class StateEventsChunkHandler { nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN } - stateEvents.addManagedToChunk(chunkEntity) + + stateEvents.forEach { event -> + chunkEntity.add(event, EventEntity.DEFAULT_STATE_INDEX, PaginationDirection.FORWARDS) + } return chunkEntity }