diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 28c9776f..7c17f49f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -2,18 +2,13 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse -import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent +import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.PUT -import retrofit2.http.Path -import retrofit2.http.Query +import retrofit2.http.* internal interface RoomAPI { @@ -32,7 +27,7 @@ internal interface RoomAPI { @Query("dir") dir: String, @Query("limit") limit: Int, @Query("filter") filter: String? - ): Call + ): Call /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 7bed7286..9a7e897e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -7,10 +7,7 @@ import im.vector.matrix.android.api.session.room.send.EventFactory import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest import im.vector.matrix.android.internal.session.room.send.DefaultSendService -import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder -import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventRequest -import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest -import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback +import im.vector.matrix.android.internal.session.room.timeline.* import im.vector.matrix.android.internal.util.PagingRequestHelper import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.module.Module @@ -32,6 +29,10 @@ class RoomModule : Module { LoadRoomMembersRequest(get(), get(), get()) } + scope(DefaultSession.SCOPE) { + TokenChunkEventPersistor(get()) + } + scope(DefaultSession.SCOPE) { PaginationRequest(get(), get(), get()) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/EventContextResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/EventContextResponse.kt index 76ea2a8a..0579b44c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/EventContextResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/EventContextResponse.kt @@ -7,9 +7,14 @@ import im.vector.matrix.android.api.session.events.model.Event @JsonClass(generateAdapter = true) data class EventContextResponse( @Json(name = "event") val event: Event, - @Json(name = "start") val prevToken: String? = null, + @Json(name = "start") override val prevToken: String? = null, @Json(name = "events_before") val eventsBefore: List = emptyList(), @Json(name = "events_after") val eventsAfter: List = emptyList(), - @Json(name = "end") val nextToken: String? = null, - @Json(name = "state") val stateEvents: List = emptyList() -) + @Json(name = "end") override val nextToken: String? = null, + @Json(name = "state") override val stateEvents: List = emptyList() +) : TokenChunkEvent { + + override val events: List + get() = listOf(event) + +} 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 9ffad74d..868d6953 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 @@ -1,31 +1,18 @@ package im.vector.matrix.android.internal.session.room.timeline -import arrow.core.Try -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 -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 -import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.legacy.util.FilterUtil import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers -import im.vector.matrix.android.internal.util.tryTransactionSync -import io.realm.kotlin.createObject import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class GetContextOfEventRequest(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, + private val tokenChunkEventPersistor: TokenChunkEventPersistor, private val coroutineDispatchers: MatrixCoroutineDispatchers ) { @@ -48,39 +35,9 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI, executeRequest { apiCall = roomAPI.getContextOfEvent(roomId, eventId, 0, filter) }.flatMap { response -> - insertInDb(response, roomId) + tokenChunkEventPersistor.insertInDb(response, roomId, PaginationDirection.BACKWARDS).map { response } } } - private fun insertInDb(response: EventContextResponse, roomId: String): Try { - return monarchy - .tryTransactionSync { realm -> - val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") - - val currentChunk = realm.createObject().apply { - prevToken = response.prevToken - nextToken = response.nextToken - } - - 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) - - if (prevChunk != null) { - currentChunk.merge(prevChunk, PaginationDirection.BACKWARDS) - roomEntity.deleteOnCascade(prevChunk) - } - if (nextChunk != null) { - currentChunk.merge(nextChunk, PaginationDirection.FORWARDS) - roomEntity.deleteOnCascade(nextChunk) - } - roomEntity.addOrUpdate(currentChunk) - roomEntity.addStateEvents(response.stateEvents, stateIndex = Int.MIN_VALUE, isUnlinked = true) - } - .map { response } - } - } \ 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 795ce5c2..4f603b9b 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 @@ -1,34 +1,19 @@ package im.vector.matrix.android.internal.session.room.timeline -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.util.Cancelable -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 -import im.vector.matrix.android.internal.database.query.findAllIncludingEvents -import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.legacy.util.FilterUtil import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers -import im.vector.matrix.android.internal.util.tryTransactionSync -import io.realm.kotlin.createObject import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class PaginationRequest(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, + private val tokenChunkEventPersistor: TokenChunkEventPersistor, private val coroutineDispatchers: MatrixCoroutineDispatchers ) { @@ -55,66 +40,13 @@ internal class PaginationRequest(private val roomAPI: RoomAPI, if (from == null) { return@withContext RuntimeException("From token shouldn't be null").failure() } - executeRequest { + executeRequest { apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter) }.flatMap { chunk -> - insertInDb(chunk, roomId, direction) + tokenChunkEventPersistor + .insertInDb(chunk, roomId, direction) + .map { chunk } } } - private fun insertInDb(receivedChunk: TokenChunkEvent, - roomId: String, - direction: PaginationDirection): Try { - return monarchy - .tryTransactionSync { realm -> - val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: throw IllegalStateException("You shouldn't use this method without a room") - - // 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().apply { - prevToken = receivedChunk.prevToken - nextToken = receivedChunk.nextToken - } - newChunk.addAll(receivedChunk.events, direction, isUnlinked = true) - - // 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) { - newChunk.merge(prevChunk, PaginationDirection.BACKWARDS) - roomEntity.deleteOnCascade(prevChunk) - } - if (nextChunk != null) { - nextChunk.merge(newChunk, PaginationDirection.BACKWARDS) - newChunk.deleteOnCascade() - currentChunk = nextChunk - } - val newEventIds = receivedChunk.events.mapNotNull { it.eventId } - ChunkEntity - .findAllIncludingEvents(realm, newEventIds) - .filter { it != currentChunk } - .forEach { 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) - } - .map { receivedChunk } - } - - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationResponse.kt new file mode 100644 index 00000000..edf56c28 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationResponse.kt @@ -0,0 +1,13 @@ +package im.vector.matrix.android.internal.session.room.timeline + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Event + +@JsonClass(generateAdapter = true) +internal data class PaginationResponse( + @Json(name = "start") override val nextToken: String? = null, + @Json(name = "end") override val prevToken: String? = null, + @Json(name = "chunk") override val events: List = emptyList(), + @Json(name = "state") override val stateEvents: List = emptyList() +) : TokenChunkEvent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt index 9e25da14..bf5fdd99 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt @@ -1,13 +1,10 @@ package im.vector.matrix.android.internal.session.room.timeline -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.events.model.Event -@JsonClass(generateAdapter = true) -internal data class TokenChunkEvent( - @Json(name = "start") val nextToken: String? = null, - @Json(name = "end") val prevToken: String? = null, - @Json(name = "chunk") val events: List = emptyList(), - @Json(name = "state") val stateEvents: List = emptyList() -) \ No newline at end of file +internal interface TokenChunkEvent { + val nextToken: String? + val prevToken: String? + val events: List + val stateEvents: List +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt new file mode 100644 index 00000000..71148157 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -0,0 +1,73 @@ +package im.vector.matrix.android.internal.session.room.timeline + +import arrow.core.Try +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.helper.* +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 +import im.vector.matrix.android.internal.database.query.findAllIncludingEvents +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.util.tryTransactionSync +import io.realm.kotlin.createObject + + +internal class TokenChunkEventPersistor(private val monarchy: Monarchy) { + + fun insertInDb(receivedChunk: TokenChunkEvent, + roomId: String, + direction: PaginationDirection): Try { + + return monarchy + .tryTransactionSync { realm -> + val roomEntity = RoomEntity.where(realm, roomId).findFirst() + ?: throw IllegalStateException("You shouldn't use this method without a room") + + // 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 + // By default, it's an unlinked chunk + val newChunk = realm.createObject().apply { + prevToken = receivedChunk.prevToken + nextToken = receivedChunk.nextToken + } + newChunk.addAll(receivedChunk.events, direction, isUnlinked = true) + + // 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) { + newChunk.merge(prevChunk, PaginationDirection.BACKWARDS) + roomEntity.deleteOnCascade(prevChunk) + } + if (nextChunk != null) { + nextChunk.merge(newChunk, PaginationDirection.BACKWARDS) + roomEntity.deleteOnCascade(newChunk) + currentChunk = nextChunk + } + val newEventIds = receivedChunk.events.mapNotNull { it.eventId } + ChunkEntity + .findAllIncludingEvents(realm, newEventIds) + .filter { it != currentChunk } + .forEach { overlapped -> + if (direction == PaginationDirection.BACKWARDS) { + currentChunk.merge(overlapped, PaginationDirection.BACKWARDS) + roomEntity.deleteOnCascade(overlapped) + } else { + overlapped.merge(currentChunk, PaginationDirection.BACKWARDS) + roomEntity.deleteOnCascade(currentChunk) + 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) + } + } + + +} \ No newline at end of file