Pagination/Permalink : extract persistence logic in a dedicated class

This commit is contained in:
ganfra 2018-11-29 11:52:36 +01:00
parent c396c2bec7
commit 9f79a5132d
8 changed files with 116 additions and 143 deletions

View File

@ -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<TokenChunkEvent>
): Call<PaginationResponse>


/**

View File

@ -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())
}

View File

@ -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<Event> = emptyList(),
@Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
@Json(name = "end") val nextToken: String? = null,
@Json(name = "state") val stateEvents: List<Event> = emptyList()
)
@Json(name = "end") override val nextToken: String? = null,
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
) : TokenChunkEvent {

override val events: List<Event>
get() = listOf(event)

}

View File

@ -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<EventContextResponse> {
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<EventContextResponse> {
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<ChunkEntity>().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 }
}


}

View File

@ -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<TokenChunkEvent>()
}
executeRequest<TokenChunkEvent> {
executeRequest<PaginationResponse> {
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<TokenChunkEvent> {
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<ChunkEntity>().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 }
}


}

View File

@ -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<Event> = emptyList(),
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
) : TokenChunkEvent

View File

@ -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<Event> = emptyList(),
@Json(name = "state") val stateEvents: List<Event> = emptyList()
)
internal interface TokenChunkEvent {
val nextToken: String?
val prevToken: String?
val events: List<Event>
val stateEvents: List<Event>
}

View File

@ -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<Unit> {

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<ChunkEntity>().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)
}
}


}