From c396c2bec72a9682b861f68b5f39d8be5a924b16 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Wed, 28 Nov 2018 18:28:35 +0100
Subject: [PATCH] WIP on chunk merging : required to merge chunks wherever they
 are (permalink)

---
 .../database/helper/ChunkEntityHelper.kt      | 45 +++++++++----------
 .../database/helper/RoomEntityHelper.kt       |  3 +-
 .../android/internal/session/SessionModule.kt |  2 +-
 .../room/members/RoomMemberExtractor.kt       |  3 +-
 .../session/room/send/DefaultSendService.kt   |  4 +-
 .../room/timeline/GetContextOfEventRequest.kt |  3 +-
 .../room/timeline/PaginationDirection.kt      |  7 +++
 .../room/timeline/PaginationRequest.kt        | 40 ++++++++++++-----
 8 files changed, 64 insertions(+), 43 deletions(-)

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 4c3abab0fd..351ae7aec3 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
@@ -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.EventType
 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.EventEntity
 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.find
 import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
 import io.realm.Sort
-import io.realm.kotlin.createObject
+
+internal fun ChunkEntity.deleteOnCascade() {
+    this.events.deleteAllFromRealm()
+    this.deleteFromRealm()
+}
 
 internal fun ChunkEntity.isUnlinked(): Boolean {
     return events.where().equalTo(EventEntityFields.IS_UNLINKED, true).findAll().isNotEmpty()
@@ -37,7 +40,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity,
         eventsToMerge = chunkToMerge.events
     }
     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) {
 
     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 }
 }
 
-internal fun ChunkEntity.addOrUpdate(event: Event,
-                                     direction: PaginationDirection,
-                                     stateIndexOffset: Int = 0,
-                                     isUnlinked: Boolean = false) {
+internal fun ChunkEntity.add(event: Event,
+                             direction: PaginationDirection,
+                             stateIndexOffset: Int = 0,
+                             isUnlinked: Boolean = false) {
     if (!isManaged) {
         throw IllegalStateException("Chunk entity should be managed to use fast contains")
     }
 
-    if (event.eventId == null) {
+    if (event.eventId == null || events.fastContains(event.eventId)) {
         return
     }
 
@@ -77,22 +80,16 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
         }
     }
 
-    val eventEntity: EventEntity?
-    if (!events.fastContains(event.eventId)) {
-        eventEntity = realm.createObject()
-        eventEntity.fillWith(event)
-        val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
-        events.add(position, eventEntity)
-    } else {
-        eventEntity = events.find(event.eventId)
-    }
-    eventEntity?.stateIndex = currentStateIndex
-    eventEntity?.isUnlinked = isUnlinked
+    val eventEntity = event.asEntity()
+    eventEntity.stateIndex = currentStateIndex
+    eventEntity.isUnlinked = isUnlinked
+    val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
+    events.add(position, eventEntity)
 }
 
 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
+               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
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
index 7c9c223af8..4df958b467 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
@@ -8,8 +8,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
 
 internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
     chunks.remove(chunkEntity)
-    chunkEntity.events.deleteAllFromRealm()
-    chunkEntity.deleteFromRealm()
+    chunkEntity.deleteOnCascade()
 }
 
 internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
index 8c88122c55..4dbc8a8cc9 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
@@ -38,7 +38,7 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module
             RealmConfiguration.Builder()
                     .directory(directory)
                     .name("disk_store.realm")
-                    .deleteRealmIfMigrationNeeded()
+                    .inMemory()
                     .build()
         }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt
index 08012cb5fd..67763f2660 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/RoomMemberExtractor.kt
@@ -18,12 +18,13 @@ internal class RoomMemberExtractor(private val realm: Realm,
         val sender = event.sender ?: return null
         // 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()
-        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).last(since = event.stateIndex)?.asDomain()?.content()
         } else {
             baseQuery(realm, roomId, sender).last(since = event.stateIndex)?.asDomain()?.content()
         }
+        return roomMember
     }
 
     private fun baseQuery(realm: Realm,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
index 57e1fc6c9b..a0feebc02c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
@@ -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.send.EventFactory
 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.model.ChunkEntity
 import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
@@ -33,7 +33,7 @@ internal class DefaultSendService(private val roomId: String,
         monarchy.tryTransactionAsync { realm ->
             val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
                     ?: return@tryTransactionAsync
-            chunkEntity.addOrUpdate(event, PaginationDirection.FORWARDS)
+            chunkEntity.add(event, PaginationDirection.FORWARDS)
             chunkEntity.updateDisplayIndexes()
         }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt
index 5ea69ba9bd..9ffad74dfa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt
@@ -5,6 +5,7 @@ import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 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.addStateEvents
 import im.vector.matrix.android.internal.database.helper.deleteOnCascade
 import im.vector.matrix.android.internal.database.helper.merge
@@ -62,7 +63,7 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
                         nextToken = response.nextToken
                     }
 
-                    currentChunk.addOrUpdate(response.event, PaginationDirection.FORWARDS, isUnlinked = true)
+                    currentChunk.add(response.event, PaginationDirection.FORWARDS, isUnlinked = true)
                     // Now, handles chunk merge
                     val prevChunk = ChunkEntity.find(realm, roomId, nextToken = response.prevToken)
                     val nextChunk = ChunkEntity.find(realm, roomId, prevToken = response.nextToken)
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 b65a0ca0b5..ffb8dd0be3 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
@@ -13,4 +13,11 @@ internal enum class PaginationDirection(val value: String) {
      */
     BACKWARDS("b");
 
+    fun reversed(): PaginationDirection {
+        return when (this) {
+            FORWARDS  -> BACKWARDS
+            BACKWARDS -> FORWARDS
+        }
+    }
+
 }
\ 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 02f4e8231c..795ce5c29e 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
@@ -5,7 +5,12 @@ import arrow.core.failure
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 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.RoomEntity
 import im.vector.matrix.android.internal.database.query.find
@@ -63,36 +68,47 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
         return monarchy
                 .tryTransactionSync { realm ->
                     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
                         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 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) {
-                        currentChunk.merge(prevChunk, PaginationDirection.BACKWARDS)
+                        newChunk.merge(prevChunk, PaginationDirection.BACKWARDS)
                         roomEntity.deleteOnCascade(prevChunk)
                     }
                     if (nextChunk != null) {
-                        currentChunk.merge(nextChunk, PaginationDirection.FORWARDS)
-                        roomEntity.deleteOnCascade(nextChunk)
+                        nextChunk.merge(newChunk, PaginationDirection.BACKWARDS)
+                        newChunk.deleteOnCascade()
+                        currentChunk = nextChunk
                     }
-                    val eventIds = receivedChunk.events.mapNotNull { it.eventId }
+                    val newEventIds = receivedChunk.events.mapNotNull { it.eventId }
                     ChunkEntity
-                            .findAllIncludingEvents(realm, eventIds)
+                            .findAllIncludingEvents(realm, newEventIds)
                             .filter { it != currentChunk }
                             .forEach { overlapped ->
-                                currentChunk.merge(overlapped, direction)
-                                roomEntity.deleteOnCascade(overlapped)
+                                if (direction == PaginationDirection.BACKWARDS) {
+                                    currentChunk.merge(overlapped, PaginationDirection.BACKWARDS)
+                                    roomEntity.deleteOnCascade(overlapped)
+                                } else {
+                                    overlapped.merge(currentChunk, PaginationDirection.BACKWARDS)
+                                    currentChunk = overlapped
+                                }
                             }
 
                     roomEntity.addOrUpdate(currentChunk)
+
                     // TODO : there is an issue with the pagination sending unwanted room member events
                     val isUnlinked = currentChunk.isUnlinked()
                     roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = isUnlinked)