WIP on chunk merging : required to merge chunks wherever they are (permalink)

This commit is contained in:
ganfra 2018-11-28 18:28:35 +01:00
parent b3ba542e09
commit c396c2bec7
8 changed files with 64 additions and 43 deletions

View File

@ -3,15 +3,18 @@ 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.asDomain
import im.vector.matrix.android.internal.database.mapper.fillWith 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.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.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.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.createObject
internal fun ChunkEntity.deleteOnCascade() {
this.events.deleteAllFromRealm()
this.deleteFromRealm()
}
internal fun ChunkEntity.isUnlinked(): Boolean { internal fun ChunkEntity.isUnlinked(): Boolean {
return events.where().equalTo(EventEntityFields.IS_UNLINKED, true).findAll().isNotEmpty() return events.where().equalTo(EventEntityFields.IS_UNLINKED, true).findAll().isNotEmpty()
@ -37,7 +40,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity,
eventsToMerge = chunkToMerge.events eventsToMerge = chunkToMerge.events
} }
eventsToMerge.forEach { eventsToMerge.forEach {
addOrUpdate(it.asDomain(), direction, isUnlinked = isUnlinked) add(it.asDomain(), direction, isUnlinked = isUnlinked)
} }
} }
@ -47,7 +50,7 @@ internal fun ChunkEntity.addAll(events: List<Event>,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
events.forEach { event -> events.forEach { event ->
addOrUpdate(event, direction, stateIndexOffset, isUnlinked) add(event, direction, stateIndexOffset, isUnlinked)
} }
} }
@ -55,15 +58,15 @@ internal fun ChunkEntity.updateDisplayIndexes() {
events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index } events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index }
} }
internal fun ChunkEntity.addOrUpdate(event: Event, internal fun ChunkEntity.add(event: Event,
direction: PaginationDirection, direction: PaginationDirection,
stateIndexOffset: Int = 0, stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
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")
} }
if (event.eventId == null) { if (event.eventId == null || events.fastContains(event.eventId)) {
return return
} }
@ -77,22 +80,16 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
} }
} }
val eventEntity: EventEntity? val eventEntity = event.asEntity()
if (!events.fastContains(event.eventId)) { eventEntity.stateIndex = currentStateIndex
eventEntity = realm.createObject() eventEntity.isUnlinked = isUnlinked
eventEntity.fillWith(event) 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)
} else {
eventEntity = events.find(event.eventId)
}
eventEntity?.stateIndex = currentStateIndex
eventEntity?.isUnlinked = isUnlinked
} }
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

@ -8,8 +8,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity) chunks.remove(chunkEntity)
chunkEntity.events.deleteAllFromRealm() chunkEntity.deleteOnCascade()
chunkEntity.deleteFromRealm()
} }
internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) { internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {

View File

@ -38,7 +38,7 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module
RealmConfiguration.Builder() RealmConfiguration.Builder()
.directory(directory) .directory(directory)
.name("disk_store.realm") .name("disk_store.realm")
.deleteRealmIfMigrationNeeded() .inMemory()
.build() .build()
} }

View File

@ -18,12 +18,13 @@ internal class RoomMemberExtractor(private val realm: Realm,
val sender = event.sender ?: return null val sender = event.sender ?: return null
// When stateIndex is negative, we try to get the next stateEvent prevContent() // When stateIndex is negative, we try to get the next stateEvent prevContent()
// If prevContent is null we fallback to the Int.MIN state events content() // If prevContent is null we fallback to the Int.MIN state events content()
return if (event.stateIndex <= 0) { val roomMember: RoomMember? = if (event.stateIndex <= 0) {
baseQuery(realm, roomId, sender).next(from = event.stateIndex)?.asDomain()?.prevContent() baseQuery(realm, roomId, sender).next(from = event.stateIndex)?.asDomain()?.prevContent()
?: baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content() ?: baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content()
} else { } else {
baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content() baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content()
} }
return roomMember
} }
private fun baseQuery(realm: Realm, private fun baseQuery(realm: Realm,

View File

@ -7,7 +7,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.send.EventFactory import im.vector.matrix.android.api.session.room.send.EventFactory
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.add
import im.vector.matrix.android.internal.database.helper.updateDisplayIndexes import im.vector.matrix.android.internal.database.helper.updateDisplayIndexes
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.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
@ -33,7 +33,7 @@ internal class DefaultSendService(private val roomId: String,
monarchy.tryTransactionAsync { realm -> monarchy.tryTransactionAsync { realm ->
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
?: return@tryTransactionAsync ?: return@tryTransactionAsync
chunkEntity.addOrUpdate(event, PaginationDirection.FORWARDS) chunkEntity.add(event, PaginationDirection.FORWARDS)
chunkEntity.updateDisplayIndexes() chunkEntity.updateDisplayIndexes()
} }

View File

@ -5,6 +5,7 @@ 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.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.addOrUpdate
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.addStateEvents 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
@ -62,7 +63,7 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
nextToken = response.nextToken nextToken = response.nextToken
} }
currentChunk.addOrUpdate(response.event, PaginationDirection.FORWARDS, isUnlinked = true) currentChunk.add(response.event, PaginationDirection.FORWARDS, isUnlinked = true)
// Now, handles chunk merge // Now, handles chunk merge
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = response.prevToken) val prevChunk = ChunkEntity.find(realm, roomId, nextToken = response.prevToken)
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = response.nextToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = response.nextToken)

View File

@ -13,4 +13,11 @@ internal enum class PaginationDirection(val value: String) {
*/ */
BACKWARDS("b"); BACKWARDS("b");
fun reversed(): PaginationDirection {
return when (this) {
FORWARDS -> BACKWARDS
BACKWARDS -> FORWARDS
}
}
} }

View File

@ -5,7 +5,12 @@ import arrow.core.failure
import com.zhuinden.monarchy.Monarchy 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.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
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.find import im.vector.matrix.android.internal.database.query.find
@ -63,36 +68,47 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
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 currentChunk = realm.createObject<ChunkEntity>().apply { // We create a new chunk with prev and next token as a base
// In case of permalink, we may not encounter other chunks, so it can be added
val newChunk = realm.createObject<ChunkEntity>().apply {
prevToken = receivedChunk.prevToken prevToken = receivedChunk.prevToken
nextToken = receivedChunk.nextToken nextToken = receivedChunk.nextToken
} }
currentChunk.addAll(receivedChunk.events, direction, isUnlinked = true) newChunk.addAll(receivedChunk.events, direction, isUnlinked = true)
// Now, handles chunk merge // The current chunk is the one we will keep all along the merge process.
var currentChunk = newChunk
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = receivedChunk.prevToken) val prevChunk = ChunkEntity.find(realm, roomId, nextToken = receivedChunk.prevToken)
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = receivedChunk.nextToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = receivedChunk.nextToken)
// We always merge the bottom chunk into top chunk, so we are always merging backwards
if (prevChunk != null) { if (prevChunk != null) {
currentChunk.merge(prevChunk, PaginationDirection.BACKWARDS) newChunk.merge(prevChunk, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(prevChunk) roomEntity.deleteOnCascade(prevChunk)
} }
if (nextChunk != null) { if (nextChunk != null) {
currentChunk.merge(nextChunk, PaginationDirection.FORWARDS) nextChunk.merge(newChunk, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(nextChunk) newChunk.deleteOnCascade()
currentChunk = nextChunk
} }
val eventIds = receivedChunk.events.mapNotNull { it.eventId } val newEventIds = receivedChunk.events.mapNotNull { it.eventId }
ChunkEntity ChunkEntity
.findAllIncludingEvents(realm, eventIds) .findAllIncludingEvents(realm, newEventIds)
.filter { it != currentChunk } .filter { it != currentChunk }
.forEach { overlapped -> .forEach { overlapped ->
currentChunk.merge(overlapped, direction) if (direction == PaginationDirection.BACKWARDS) {
roomEntity.deleteOnCascade(overlapped) currentChunk.merge(overlapped, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(overlapped)
} else {
overlapped.merge(currentChunk, PaginationDirection.BACKWARDS)
currentChunk = overlapped
}
} }
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 isUnlinked = currentChunk.isUnlinked() val isUnlinked = currentChunk.isUnlinked()
roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = isUnlinked) roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = isUnlinked)