Timeline : fix merging issues

This commit is contained in:
ganfra 2019-01-07 19:38:49 +01:00
parent 1269715b5c
commit de90cbe73e
3 changed files with 43 additions and 33 deletions

View File

@ -4,7 +4,11 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
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.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.lastStateIndex
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
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Realm import io.realm.Realm
@ -102,7 +106,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.events.size shouldEqual 60 chunk1.events.size shouldEqual 60
} }
} }
@ -118,7 +122,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk2.isLast = false chunk2.isLast = false
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS) chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS) chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.events.size shouldEqual 40 chunk1.events.size shouldEqual 40
chunk1.isLast.shouldBeTrue() chunk1.isLast.shouldBeTrue()
} }
@ -131,7 +135,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked().shouldBeFalse() chunk1.isUnlinked().shouldBeFalse()
} }
} }
@ -143,7 +147,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject() val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked().shouldBeTrue() chunk1.isUnlinked().shouldBeTrue()
} }
} }
@ -157,7 +161,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.prevToken = prevToken chunk1.prevToken = prevToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.merge(chunk2, PaginationDirection.FORWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldEqual prevToken chunk1.prevToken shouldEqual prevToken
} }
} }
@ -171,7 +175,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.nextToken = nextToken chunk1.nextToken = nextToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldEqual nextToken chunk1.nextToken shouldEqual nextToken
} }
} }

View File

@ -2,6 +2,7 @@ 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.api.session.events.model.EventType
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.mapper.toEntity
import im.vector.matrix.android.internal.database.mapper.updateWith import im.vector.matrix.android.internal.database.mapper.updateWith
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -12,18 +13,21 @@ import im.vector.matrix.android.internal.session.room.timeline.PaginationDirecti
import io.realm.Sort import io.realm.Sort


internal fun ChunkEntity.deleteOnCascade() { internal fun ChunkEntity.deleteOnCascade() {
assertIsManaged()
this.events.deleteAllFromRealm() this.events.deleteAllFromRealm()
this.deleteFromRealm() this.deleteFromRealm()
} }


// By default if a chunk is empty we consider it unlinked // By default if a chunk is empty we consider it unlinked
internal fun ChunkEntity.isUnlinked(): Boolean { internal fun ChunkEntity.isUnlinked(): Boolean {
assertIsManaged()
return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty() return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty()
} }


internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity, internal fun ChunkEntity.merge(roomId: String,
chunkToMerge: ChunkEntity,
direction: PaginationDirection) { direction: PaginationDirection) {

assertIsManaged()
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked() val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
val isCurrentChunkUnlinked = this.isUnlinked() val isCurrentChunkUnlinked = this.isUnlinked()
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
@ -41,7 +45,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity,
eventsToMerge = chunkToMerge.events eventsToMerge = chunkToMerge.events
} }
eventsToMerge.forEach { eventsToMerge.forEach {
add(it, direction, isUnlinked = isUnlinked) add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked)
} }
} }


@ -50,7 +54,7 @@ internal fun ChunkEntity.addAll(roomId: String,
direction: PaginationDirection, direction: PaginationDirection,
stateIndexOffset: Int = 0, stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {

assertIsManaged()
events.forEach { event -> events.forEach { event ->
add(roomId, event, direction, stateIndexOffset, isUnlinked) add(roomId, event, direction, stateIndexOffset, isUnlinked)
} }
@ -66,22 +70,12 @@ internal fun ChunkEntity.add(roomId: String,
stateIndexOffset: Int = 0, stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {


add(event.toEntity(roomId), direction, stateIndexOffset, isUnlinked) assertIsManaged()
} if (event.eventId.isNullOrEmpty() || events.fastContains(event.eventId)) {

internal fun ChunkEntity.add(eventEntity: EventEntity,
direction: PaginationDirection,
stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) {
if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains")
}

if (eventEntity.eventId.isEmpty() || events.fastContains(eventEntity.eventId)) {
return return
} }
var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset) var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(eventEntity.type)) { if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.type)) {
currentStateIndex += 1 currentStateIndex += 1
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) { } else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = events.last()?.type ?: "" val lastEventType = events.last()?.type ?: ""
@ -89,14 +83,21 @@ internal fun ChunkEntity.add(eventEntity: EventEntity,
currentStateIndex -= 1 currentStateIndex -= 1
} }
} }
val eventEntity = event.toEntity(roomId)
eventEntity.updateWith(currentStateIndex, isUnlinked) eventEntity.updateWith(currentStateIndex, isUnlinked)
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
events.add(position, eventEntity) events.add(position, eventEntity)
} }


private fun ChunkEntity.assertIsManaged() {
if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use this function")
}
}

internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
} ?: defaultValue } ?: defaultValue
} }

View File

@ -2,7 +2,12 @@ package im.vector.matrix.android.internal.session.room.timeline


import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.* 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.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
import im.vector.matrix.android.internal.database.helper.isUnlinked
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
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.create import im.vector.matrix.android.internal.database.query.create
@ -21,7 +26,7 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {
return monarchy return monarchy
.tryTransactionSync { realm -> .tryTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room") ?: throw IllegalStateException("You shouldn't use this method without a room")


val nextToken: String? val nextToken: String?
val prevToken: String? val prevToken: String?
@ -41,10 +46,10 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {


var currentChunk = if (direction == PaginationDirection.FORWARDS) { var currentChunk = if (direction == PaginationDirection.FORWARDS) {
prevChunk?.apply { this.nextToken = nextToken } prevChunk?.apply { this.nextToken = nextToken }
?: ChunkEntity.create(realm, prevToken, nextToken) ?: ChunkEntity.create(realm, prevToken, nextToken)
} else { } else {
nextChunk?.apply { this.prevToken = prevToken } nextChunk?.apply { this.prevToken = prevToken }
?: ChunkEntity.create(realm, prevToken, nextToken) ?: ChunkEntity.create(realm, prevToken, nextToken)
} }


currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked())
@ -75,11 +80,11 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {


// We always merge the bottom chunk into top chunk, so we are always merging backwards // We always merge the bottom chunk into top chunk, so we are always merging backwards
return if (direction == PaginationDirection.BACKWARDS) { return if (direction == PaginationDirection.BACKWARDS) {
currentChunk.merge(otherChunk, PaginationDirection.BACKWARDS) currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(otherChunk) roomEntity.deleteOnCascade(otherChunk)
currentChunk currentChunk
} else { } else {
otherChunk.merge(currentChunk, PaginationDirection.BACKWARDS) otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(currentChunk) roomEntity.deleteOnCascade(currentChunk)
otherChunk otherChunk
} }