State index : rework the algorithm to manage limited chunks and avoid using wrong state events (Int.Min overriding)

This commit is contained in:
ganfra 2018-11-22 11:20:50 +01:00
parent d250d2bd27
commit bc1462486d
17 changed files with 120 additions and 144 deletions

View File

@ -6,6 +6,7 @@
<w>merlins</w> <w>merlins</w>
<w>moshi</w> <w>moshi</w>
<w>synchronizer</w> <w>synchronizer</w>
<w>untimelined</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View File

@ -1,12 +1,15 @@
package im.vector.matrix.android.internal.database.helper 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.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.fastContains import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Sort
internal fun ChunkEntity.merge(chunkEntity: ChunkEntity, internal fun ChunkEntity.merge(chunkEntity: ChunkEntity,
@ -25,16 +28,16 @@ internal fun ChunkEntity.merge(chunkEntity: ChunkEntity,
internal fun ChunkEntity.addAll(events: List<Event>, internal fun ChunkEntity.addAll(events: List<Event>,
direction: PaginationDirection, direction: PaginationDirection,
updateStateIndex: Boolean = true) { stateIndexOffset: Int = 0) {
events.forEach { event -> events.forEach { event ->
addOrUpdate(event, direction, updateStateIndex) addOrUpdate(event, direction, stateIndexOffset)
} }
} }
internal fun ChunkEntity.addOrUpdate(event: Event, internal fun ChunkEntity.addOrUpdate(event: Event,
direction: PaginationDirection, direction: PaginationDirection,
updateStateIndex: Boolean = true) { stateIndexOffset: Int = 0) {
if (!isManaged) { if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains") throw IllegalStateException("Chunk entity should be managed to use fast contains")
} }
@ -43,11 +46,16 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
return return
} }
if (updateStateIndex && event.isStateEvent()) { var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
updateStateIndex(direction) if (direction == PaginationDirection.FORWARDS && event.isStateEvent()) {
currentStateIndex += 1
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = events.last()?.type ?: ""
if (EventType.isStateEvent(lastEventType)) {
currentStateIndex -= 1
}
} }
val currentStateIndex = stateIndex(direction)
if (!events.fastContains(event.eventId)) { if (!events.fastContains(event.eventId)) {
val eventEntity = event.asEntity() val eventEntity = event.asEntity()
eventEntity.stateIndex = currentStateIndex eventEntity.stateIndex = currentStateIndex
@ -58,3 +66,10 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
eventEntity?.stateIndex = currentStateIndex eventEntity?.stateIndex = currentStateIndex
} }
} }
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
} ?: defaultValue
}

View File

@ -1,5 +1,7 @@
package im.vector.matrix.android.internal.database.helper package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
@ -16,3 +18,17 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
chunks.add(chunkEntity) chunks.add(chunkEntity)
} }
} }
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>, stateIndex: Int = Int.MIN_VALUE) {
if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains")
}
stateEvents.forEach { event ->
if (event.eventId == null) {
return@forEach
}
val eventEntity = event.asEntity()
eventEntity.stateIndex = stateIndex
untimelinedStateEvents.add(eventEntity)
}
}

View File

@ -1,6 +1,5 @@
package im.vector.matrix.android.internal.database.model 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.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
@ -8,8 +7,6 @@ import io.realm.annotations.LinkingObjects
internal open class ChunkEntity(var prevToken: String? = null, internal open class ChunkEntity(var prevToken: String? = null,
var nextToken: String? = null, var nextToken: String? = null,
var prevStateIndex: Int = -1,
var nextStateIndex: Int = 1,
var isLast: Boolean = false, var isLast: Boolean = false,
var events: RealmList<EventEntity> = RealmList() var events: RealmList<EventEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {
@ -18,19 +15,4 @@ internal open class ChunkEntity(var prevToken: String? = null,
val room: RealmResults<RoomEntity>? = null val room: RealmResults<RoomEntity>? = null
companion object companion object
fun stateIndex(direction: PaginationDirection): Int {
return when (direction) {
PaginationDirection.FORWARDS -> nextStateIndex
PaginationDirection.BACKWARDS -> prevStateIndex
}
}
fun updateStateIndex(direction: PaginationDirection) {
when (direction) {
PaginationDirection.FORWARDS -> nextStateIndex += 1
PaginationDirection.BACKWARDS -> prevStateIndex -= 1
}
}
} }

View File

@ -24,4 +24,7 @@ internal open class EventEntity(var eventId: String = "",
@LinkingObjects("events") @LinkingObjects("events")
val chunk: RealmResults<ChunkEntity>? = null val chunk: RealmResults<ChunkEntity>? = null
@LinkingObjects("untimelinedStateEvents")
val room: RealmResults<RoomEntity>? = null
} }

View File

@ -8,8 +8,9 @@ import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates import kotlin.properties.Delegates
internal open class RoomEntity(@PrimaryKey var roomId: String = "", internal open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList(), var chunks: RealmList<ChunkEntity> = RealmList(),
var areAllMembersLoaded: Boolean = false var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
var areAllMembersLoaded: Boolean = false
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = MyMembership.NONE.name private var membershipStr: String = MyMembership.NONE.name

View File

@ -1,6 +1,5 @@
package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.DBConstants
import im.vector.matrix.android.internal.database.model.ChunkEntity 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.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntityFields import im.vector.matrix.android.internal.database.model.RoomEntityFields
@ -34,7 +33,5 @@ internal fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomI
internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> { internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
return realm.where<ChunkEntity>() return realm.where<ChunkEntity>()
.`in`(ChunkEntityFields.EVENTS.EVENT_ID, eventIds.toTypedArray()) .`in`(ChunkEntityFields.EVENTS.EVENT_ID, eventIds.toTypedArray())
.notEqualTo(ChunkEntityFields.PREV_TOKEN, DBConstants.STATE_EVENTS_CHUNK_TOKEN)
.notEqualTo(ChunkEntityFields.NEXT_TOKEN, DBConstants.STATE_EVENTS_CHUNK_TOKEN)
.findAll() .findAll()
} }

View File

@ -18,7 +18,11 @@ internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQu
internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, type: String? = null): RealmQuery<EventEntity> { internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, type: String? = null): RealmQuery<EventEntity> {
val query = realm.where<EventEntity>() val query = realm.where<EventEntity>()
if (roomId != null) { if (roomId != null) {
query.equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId) query.beginGroup()
.equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId)
.or()
.equalTo("${EventEntityFields.ROOM}.${RoomEntityFields.ROOM_ID}", roomId)
.endGroup()
} }
if (type != null) { if (type != null) {
query.equalTo(EventEntityFields.TYPE, type) query.equalTo(EventEntityFields.TYPE, type)
@ -26,21 +30,28 @@ internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, t
return query return query
} }
internal fun RealmQuery<EventEntity>.findMostSuitableStateEvent(stateIndex: Int): EventEntity? { internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
val sort: Sort = if (stateIndex < 0) { if (from != null) {
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, stateIndex) if (strict) {
Sort.ASCENDING this.greaterThan(EventEntityFields.STATE_INDEX, from)
} else { } else {
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, stateIndex) this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
Sort.DESCENDING }
} }
return this return this
.sort(EventEntityFields.STATE_INDEX, sort) .sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
.findFirst() .findFirst()
} }
internal fun RealmQuery<EventEntity>.last(): EventEntity? { internal fun RealmQuery<EventEntity>.last(since: Int? = null, strict: Boolean = false): EventEntity? {
if (since != null) {
if (strict) {
this.lessThan(EventEntityFields.STATE_INDEX, since)
} else {
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since)
}
}
return this return this
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.findFirst() .findFirst()

View File

@ -25,11 +25,11 @@ class RoomModule : Module {
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
LoadRoomMembersRequest(get(), get(), get(), get()) LoadRoomMembersRequest(get(), get(), get())
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
PaginationRequest(get(), get(), get(), get()) PaginationRequest(get(), get(), get())
} }

View File

@ -5,12 +5,11 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
@ -20,8 +19,7 @@ import kotlinx.coroutines.withContext
internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI, internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers) {
private val stateEventsChunkHandler: StateEventsChunkHandler) {
fun execute(roomId: String, fun execute(roomId: String,
streamToken: String?, streamToken: String?,
@ -57,8 +55,7 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
val roomMembers = RoomMembers(realm, roomId).getLoaded() val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) roomEntity.addStateEvents(eventsToInsert)
roomEntity.addOrUpdate(chunk)
roomEntity.areAllMembersLoaded = true roomEntity.areAllMembersLoaded = true
} }
.map { response } .map { response }

View File

@ -2,14 +2,12 @@ package im.vector.matrix.android.internal.session.room.members
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.DBConstants
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findMostSuitableStateEvent
import im.vector.matrix.android.internal.database.query.last import im.vector.matrix.android.internal.database.query.last
import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
@ -17,35 +15,23 @@ internal class RoomMemberExtractor(private val realm: Realm,
private val roomId: String) { private val roomId: String) {
fun extractFrom(event: EventEntity): RoomMember? { fun extractFrom(event: EventEntity): RoomMember? {
val stateIndex = event.stateIndex val sender = event.sender ?: return null
val chunkEntity = event.chunk?.firstOrNull() // When stateIndex is negative, we try to get the next stateEvent prevContent()
?: throw IllegalStateException("An event should be attached to a chunk") // If prevContent is null we fallback to the Int.MIN state events content()
return if (event.stateIndex <= 0) {
// First of all, try to get the most suitable state event coming from a chunk baseQuery(realm, roomId, sender).next(from = event.stateIndex)?.asDomain()?.prevContent()
val roomMember = buildQuery(chunkEntity, event.sender) ?: baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content()
.findMostSuitableStateEvent(stateIndex = stateIndex) } else {
?.asDomain() baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content()
?.pickContent<RoomMember>(stateIndex)
if (roomMember != null) {
return roomMember
} }
// If the content is null, we try get the last state event coming from a state events chunk
val stateChunkEntity = ChunkEntity.find(realm, roomId, nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN)
?: return null
return buildQuery(stateChunkEntity, event.sender)
.last()
?.asDomain()
?.content()
} }
private fun buildQuery(chunk: ChunkEntity, private fun baseQuery(realm: Realm,
sender: String?): RealmQuery<EventEntity> { roomId: String,
return chunk.events sender: String): RealmQuery<EventEntity> {
.where()
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) return EventEntity
.where(realm, roomId = roomId, type = EventType.STATE_ROOM_MEMBER)
.equalTo(EventEntityFields.STATE_KEY, sender) .equalTo(EventEntityFields.STATE_KEY, sender)
} }

View File

@ -6,6 +6,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade 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.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -15,7 +16,6 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.legacy.util.FilterUtil import im.vector.matrix.android.internal.legacy.util.FilterUtil
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
@ -26,8 +26,7 @@ import kotlinx.coroutines.withContext
internal class GetContextOfEventRequest(private val roomAPI: RoomAPI, internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers
private val stateEventsChunkHandler: StateEventsChunkHandler
) { ) {
fun execute(roomId: String, fun execute(roomId: String,
@ -91,9 +90,7 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
} }
*/ */
roomEntity.addOrUpdate(currentChunk) roomEntity.addOrUpdate(currentChunk)
roomEntity.addStateEvents(response.stateEvents)
val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, response.stateEvents)
roomEntity.addOrUpdate(stateEventsChunk)
} }
.map { response } .map { response }
} }

View File

@ -6,7 +6,6 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -15,8 +14,7 @@ import kotlinx.coroutines.withContext
internal class GetEventRequest(private val roomAPI: RoomAPI, internal class GetEventRequest(private val roomAPI: RoomAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers
private val stateEventsChunkHandler: StateEventsChunkHandler
) { ) {
fun execute(roomId: String, fun execute(roomId: String,

View File

@ -7,6 +7,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade 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.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -17,7 +18,6 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.legacy.util.FilterUtil import im.vector.matrix.android.internal.legacy.util.FilterUtil
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync
@ -28,8 +28,7 @@ import kotlinx.coroutines.withContext
internal class PaginationRequest(private val roomAPI: RoomAPI, internal class PaginationRequest(private val roomAPI: RoomAPI,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers
private val stateEventsChunkHandler: StateEventsChunkHandler
) { ) {
fun execute(roomId: String, fun execute(roomId: String,
@ -92,10 +91,8 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
} }
roomEntity.addOrUpdate(currentChunk) roomEntity.addOrUpdate(currentChunk)
// TODO : there is an issue with the pagination sending unwanted room member events // TODO : there is an issue with the pagination sending unwanted room member events
val stateEventsChunk = stateEventsChunkHandler.handle(realm, roomId, receivedChunk.stateEvents) roomEntity.addStateEvents(receivedChunk.stateEvents)
roomEntity.addOrUpdate(stateEventsChunk)
} }
.map { receivedChunk } .map { receivedChunk }
} }

View File

@ -6,10 +6,11 @@ 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.api.session.room.model.MyMembership
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.internal.database.model.ChunkEntity 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.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity 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.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where 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.room.timeline.PaginationDirection
@ -23,7 +24,6 @@ import io.realm.kotlin.createObject
internal class RoomSyncHandler(private val monarchy: Monarchy, internal class RoomSyncHandler(private val monarchy: Monarchy,
private val stateEventsChunkHandler: StateEventsChunkHandler,
private val readReceiptHandler: ReadReceiptHandler) { private val readReceiptHandler: ReadReceiptHandler) {
sealed class HandlingStrategy { sealed class HandlingStrategy {
@ -56,7 +56,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: RoomEntity(roomId) ?: realm.createObject(roomId)
if (roomEntity.membership == MyMembership.INVITED) { if (roomEntity.membership == MyMembership.INVITED) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -64,13 +64,27 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.membership = MyMembership.JOINED roomEntity.membership = MyMembership.JOINED
val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
val isInitialSync = lastChunk == null
val lastStateIndex = lastChunk?.lastStateIndex(PaginationDirection.FORWARDS) ?: 0
val numberOfStateEvents = roomSync.state?.events?.size ?: 0
val stateIndexOffset = lastStateIndex + numberOfStateEvents
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events) val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset
roomEntity.addOrUpdate(chunkEntity) roomEntity.addStateEvents(roomSync.state.events, stateIndex = untimelinedStateIndex)
} }
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited) val timelineStateOffset = if (isInitialSync || roomSync.timeline.limited.not()) 0 else stateIndexOffset
val chunkEntity = handleTimelineEvents(
realm,
roomId,
roomSync.timeline.events,
roomSync.timeline.prevToken,
roomSync.timeline.limited,
timelineStateOffset
)
roomEntity.addOrUpdate(chunkEntity) roomEntity.addOrUpdate(chunkEntity)
} }
@ -111,22 +125,19 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomId: String, roomId: String,
eventList: List<Event>, eventList: List<Event>,
prevToken: String? = null, prevToken: String? = null,
nextToken: String? = null, isLimited: Boolean = true,
isLimited: Boolean = true): ChunkEntity { stateIndexOffset: Int = 0): ChunkEntity {
val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
val chunkEntity = if (!isLimited) { val chunkEntity = if (!isLimited && lastChunk != null) {
lastChunk lastChunk
} else { } else {
val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! } realm.createObject<ChunkEntity>().apply { this.prevToken = prevToken }
ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull() }
} ?: realm.createObject<ChunkEntity>().apply { this.prevToken = prevToken }
lastChunk?.isLast = false lastChunk?.isLast = false
chunkEntity.isLast = true chunkEntity.isLast = true
chunkEntity.nextToken = nextToken chunkEntity.addAll(eventList, PaginationDirection.FORWARDS, stateIndexOffset)
chunkEntity.addAll(eventList, PaginationDirection.FORWARDS)
return chunkEntity return chunkEntity
} }

View File

@ -1,32 +0,0 @@
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.addAll
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Realm
import io.realm.kotlin.createObject
internal class StateEventsChunkHandler {
fun handle(realm: Realm, roomId: String, stateEvents: List<Event>): ChunkEntity {
val chunkEntity = ChunkEntity.find(realm, roomId, nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN)
?: realm.createObject<ChunkEntity>()
.apply {
prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
nextStateIndex = Int.MIN_VALUE
prevStateIndex = Int.MIN_VALUE
}
// We always consider going forwards as data from server are the most recent
chunkEntity.addAll(stateEvents, direction = PaginationDirection.FORWARDS, updateStateIndex = false)
return chunkEntity
}
}

View File

@ -17,16 +17,12 @@ internal class SyncModule : Module {
retrofit.create(SyncAPI::class.java) retrofit.create(SyncAPI::class.java)
} }
scope(DefaultSession.SCOPE) {
StateEventsChunkHandler()
}
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
ReadReceiptHandler() ReadReceiptHandler()
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RoomSyncHandler(get(), get(), get()) RoomSyncHandler(get(), get())
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {