Start to add a flag (isUnlinked) to work with permalink. Still in progress (break state index at the moment)

This commit is contained in:
ganfra 2018-11-27 22:42:46 +01:00
parent b6728ce9be
commit b3ba542e09
13 changed files with 142 additions and 89 deletions

View File

@ -17,6 +17,10 @@ class TimelineEventController(private val roomId: String,
EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyAsyncUtil.getAsyncBackgroundHandler()
) { ) {


init {
setFilterDuplicates(true)
}

private val pagedListCallback = object : PagedList.Callback() { private val pagedListCallback = object : PagedList.Callback() {
override fun onChanged(position: Int, count: Int) { override fun onChanged(position: Int, count: Int) {
buildSnapshotList() buildSnapshotList()

View File

@ -3,35 +3,51 @@ 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.asEntity import im.vector.matrix.android.internal.database.mapper.fillWith
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.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.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.isUnlinked(): Boolean {
return events.where().equalTo(EventEntityFields.IS_UNLINKED, true).findAll().isNotEmpty()
}


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


val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
val isCurrentChunkUnlinked = this.isUnlinked()
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked


chunkEntity.events.forEach { if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
addOrUpdate(it.asDomain(), direction) this.events.forEach { it.isUnlinked = false }
} }
val eventsToMerge: List<EventEntity>
if (direction == PaginationDirection.FORWARDS) { if (direction == PaginationDirection.FORWARDS) {
nextToken = chunkEntity.nextToken this.nextToken = chunkToMerge.nextToken
this.isLast = chunkToMerge.isLast
eventsToMerge = chunkToMerge.events.reversed()
} else { } else {
prevToken = chunkEntity.prevToken this.prevToken = chunkToMerge.prevToken
eventsToMerge = chunkToMerge.events
}
eventsToMerge.forEach {
addOrUpdate(it.asDomain(), direction, isUnlinked = isUnlinked)
} }
} }


internal fun ChunkEntity.addAll(events: List<Event>, internal fun ChunkEntity.addAll(events: List<Event>,
direction: PaginationDirection, direction: PaginationDirection,
stateIndexOffset: Int = 0) { stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) {


events.forEach { event -> events.forEach { event ->
addOrUpdate(event, direction, stateIndexOffset) addOrUpdate(event, direction, stateIndexOffset, isUnlinked)
} }
} }


@ -41,7 +57,8 @@ internal fun ChunkEntity.updateDisplayIndexes() {


internal fun ChunkEntity.addOrUpdate(event: Event, internal fun ChunkEntity.addOrUpdate(event: Event,
direction: PaginationDirection, direction: PaginationDirection,
stateIndexOffset: Int = 0) { stateIndexOffset: Int = 0,
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")
} }
@ -60,15 +77,17 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
} }
} }


val eventEntity: EventEntity?
if (!events.fastContains(event.eventId)) { if (!events.fastContains(event.eventId)) {
val eventEntity = event.asEntity() eventEntity = realm.createObject()
eventEntity.stateIndex = currentStateIndex 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 { } else {
val eventEntity = events.find(event.eventId) eventEntity = events.find(event.eventId)
eventEntity?.stateIndex = currentStateIndex
} }
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 {

View File

@ -19,7 +19,9 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
} }
} }


internal fun RoomEntity.addStateEvents(stateEvents: List<Event>, stateIndex: Int = Int.MIN_VALUE) { internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
stateIndex: Int = Int.MIN_VALUE,
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")
} }
@ -29,6 +31,7 @@ internal fun RoomEntity.addStateEvents(stateEvents: List<Event>, stateIndex: Int
} }
val eventEntity = event.asEntity() val eventEntity = event.asEntity()
eventEntity.stateIndex = stateIndex eventEntity.stateIndex = stateIndex
eventEntity.isUnlinked = isUnlinked
untimelinedStateEvents.add(eventEntity) untimelinedStateEvents.add(eventEntity)
} }
} }

View File

@ -14,7 +14,8 @@ internal open class EventEntity(var eventId: String = "",
var age: Long? = 0, var age: Long? = 0,
var redacts: String? = null, var redacts: String? = null,
var stateIndex: Int = 0, var stateIndex: Int = 0,
var displayIndex: Int = 0 var displayIndex: Int = 0,
var isUnlinked: Boolean = false
) : RealmObject() { ) : RealmObject() {


companion object { companion object {

View File

@ -27,6 +27,7 @@ internal fun EventEntity.Companion.where(realm: Realm, roomId: String? = null, t
if (type != null) { if (type != null) {
query.equalTo(EventEntityFields.TYPE, type) query.equalTo(EventEntityFields.TYPE, type)
} }
query.notEqualTo(EventEntityFields.IS_UNLINKED, true)
return query return query
} }



View File

@ -14,6 +14,7 @@ import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module
@ -31,7 +32,8 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val context = get<Context>() val context = get<Context>()
val directory = File(context.filesDir, sessionParams.credentials.userId) val childPath = sessionParams.credentials.userId.md5()
val directory = File(context.filesDir, childPath)


RealmConfiguration.Builder() RealmConfiguration.Builder()
.directory(directory) .directory(directory)
@ -47,7 +49,7 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val retrofitBuilder = get() as Retrofit.Builder val retrofitBuilder = get<Retrofit.Builder>()
retrofitBuilder retrofitBuilder
.baseUrl(sessionParams.homeServerConnectionConfig.homeServerUri.toString()) .baseUrl(sessionParams.homeServerConnectionConfig.homeServerUri.toString())
.build() .build()

View File

@ -8,6 +8,7 @@ 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.members.LoadRoomMembersRequest
import im.vector.matrix.android.internal.session.room.send.DefaultSendService 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.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.PaginationRequest
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.util.PagingRequestHelper import im.vector.matrix.android.internal.util.PagingRequestHelper
@ -35,6 +36,10 @@ class RoomModule : Module {
PaginationRequest(get(), get(), get()) PaginationRequest(get(), get(), get())
} }


scope(DefaultSession.SCOPE) {
GetContextOfEventRequest(get(), get(), get())
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val sessionParams = get<SessionParams>() val sessionParams = get<SessionParams>()
EventFactory(sessionParams.credentials) EventFactory(sessionParams.credentials)
@ -43,10 +48,9 @@ class RoomModule : Module {
factory { (roomId: String) -> factory { (roomId: String) ->
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor()) val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), helper) val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), helper)
DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback) as TimelineHolder DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback, get()) as TimelineHolder
} }



factory { (roomId: String) -> factory { (roomId: String) ->
DefaultSendService(roomId, get(), get()) as SendService DefaultSendService(roomId, get(), get()) as SendService
} }

View File

@ -4,6 +4,7 @@ import android.arch.lifecycle.LiveData
import android.arch.paging.LivePagedListBuilder import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
@ -20,7 +21,8 @@ private const val PAGE_SIZE = 30


internal class DefaultTimelineHolder(private val roomId: String, internal class DefaultTimelineHolder(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val boundaryCallback: TimelineBoundaryCallback private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventRequest: GetContextOfEventRequest
) : TimelineHolder { ) : TimelineHolder {


private val eventInterceptors = ArrayList<EnrichedEventInterceptor>() private val eventInterceptors = ArrayList<EnrichedEventInterceptor>()
@ -32,7 +34,7 @@ internal class DefaultTimelineHolder(private val roomId: String,


override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> { override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> {
if (eventId != null) { if (eventId != null) {
fetchEventIfNeeded() fetchEventIfNeeded(eventId)
} }
val realmDataSourceFactory = monarchy.createDataSourceFactory { val realmDataSourceFactory = monarchy.createDataSourceFactory {
buildDataSourceFactoryQuery(it, eventId) buildDataSourceFactoryQuery(it, eventId)
@ -60,8 +62,18 @@ internal class DefaultTimelineHolder(private val roomId: String,
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
} }


private fun fetchEventIfNeeded() { private fun fetchEventIfNeeded(eventId: String) {
if (!isEventPersisted(eventId)) {
contextOfEventRequest.execute(roomId, eventId, object : MatrixCallback<EventContextResponse> {})
}
}


private fun isEventPersisted(eventId: String): Boolean {
var isEventPersisted = false
monarchy.doWithRealm {
isEventPersisted = EventEntity.where(it, eventId = eventId).findFirst() != null
}
return isEventPersisted
} }


private fun buildDataSourceFactoryQuery(realm: Realm, eventId: String?): RealmQuery<EventEntity> { private fun buildDataSourceFactoryQuery(realm: Realm, eventId: String?): RealmQuery<EventEntity> {

View File

@ -12,11 +12,4 @@ data class EventContextResponse(
@Json(name = "events_after") val eventsAfter: List<Event> = emptyList(), @Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
@Json(name = "end") val nextToken: String? = null, @Json(name = "end") val nextToken: String? = null,
@Json(name = "state") val stateEvents: List<Event> = emptyList() @Json(name = "state") val stateEvents: List<Event> = emptyList()
) { )

val timelineEvents: List<Event> by lazy {
eventsBefore + event + eventsAfter
}


}

View File

@ -4,7 +4,6 @@ import arrow.core.Try
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.addAll
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.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
@ -46,7 +45,7 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
filter: String?) = withContext(coroutineDispatchers.io) { filter: String?) = withContext(coroutineDispatchers.io) {


executeRequest<EventContextResponse> { executeRequest<EventContextResponse> {
apiCall = roomAPI.getContextOfEvent(roomId, eventId, 1, filter) apiCall = roomAPI.getContextOfEvent(roomId, eventId, 0, filter)
}.flatMap { response -> }.flatMap { response ->
insertInDb(response, roomId) insertInDb(response, roomId)
} }
@ -56,17 +55,14 @@ internal class GetContextOfEventRequest(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 { val currentChunk = realm.createObject<ChunkEntity>().apply {
prevToken = response.prevToken prevToken = response.prevToken
nextToken = response.nextToken nextToken = response.nextToken
} }


currentChunk.addOrUpdate(response.event, PaginationDirection.FORWARDS) currentChunk.addOrUpdate(response.event, PaginationDirection.FORWARDS, isUnlinked = true)
currentChunk.addAll(response.eventsAfter, PaginationDirection.FORWARDS)
currentChunk.addAll(response.eventsBefore, PaginationDirection.BACKWARDS)

// 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)
@ -79,18 +75,8 @@ internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
currentChunk.merge(nextChunk, PaginationDirection.FORWARDS) currentChunk.merge(nextChunk, PaginationDirection.FORWARDS)
roomEntity.deleteOnCascade(nextChunk) roomEntity.deleteOnCascade(nextChunk)
} }
/*
val eventIds = response.timelineEvents.mapNotNull { it.eventId }
ChunkEntity
.findAllIncludingEvents(realm, eventIds)
.filter { it != currentChunk }
.forEach { overlapped ->
currentChunk.merge(overlapped, direction)
roomEntity.deleteOnCascade(overlapped)
}
*/
roomEntity.addOrUpdate(currentChunk) roomEntity.addOrUpdate(currentChunk)
roomEntity.addStateEvents(response.stateEvents) roomEntity.addStateEvents(response.stateEvents, stateIndex = Int.MIN_VALUE, isUnlinked = true)
} }
.map { response } .map { response }
} }

View File

@ -5,11 +5,7 @@ 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.addAll import im.vector.matrix.android.internal.database.helper.*
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.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
@ -34,7 +30,7 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
fun execute(roomId: String, fun execute(roomId: String,
from: String?, from: String?,
direction: PaginationDirection, direction: PaginationDirection,
limit: Int = 10, limit: Int,
callback: MatrixCallback<TokenChunkEvent> callback: MatrixCallback<TokenChunkEvent>
): Cancelable { ): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
@ -48,7 +44,7 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
private suspend fun execute(roomId: String, private suspend fun execute(roomId: String,
from: String?, from: String?,
direction: PaginationDirection, direction: PaginationDirection,
limit: Int = 10, limit: Int,
filter: String?) = withContext(coroutineDispatchers.io) { filter: String?) = withContext(coroutineDispatchers.io) {


if (from == null) { if (from == null) {
@ -61,38 +57,45 @@ internal class PaginationRequest(private val roomAPI: RoomAPI,
} }
} }


private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String, direction: PaginationDirection): Try<TokenChunkEvent> { private fun insertInDb(receivedChunk: TokenChunkEvent,
roomId: String,
direction: PaginationDirection): Try<TokenChunkEvent> {
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 = ChunkEntity.find(realm, roomId, prevToken = receivedChunk.nextToken) val currentChunk = realm.createObject<ChunkEntity>().apply {
?: realm.createObject() prevToken = receivedChunk.prevToken

nextToken = receivedChunk.nextToken
currentChunk.prevToken = receivedChunk.prevToken }
currentChunk.addAll(receivedChunk.events, direction) currentChunk.addAll(receivedChunk.events, direction, isUnlinked = true)


// Now, handles chunk merge // Now, handles chunk merge

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)

if (prevChunk != null) { if (prevChunk != null) {
currentChunk.merge(prevChunk, direction) currentChunk.merge(prevChunk, PaginationDirection.BACKWARDS)
roomEntity.deleteOnCascade(prevChunk) roomEntity.deleteOnCascade(prevChunk)
} else {
val eventIds = receivedChunk.events.mapNotNull { it.eventId }
ChunkEntity
.findAllIncludingEvents(realm, eventIds)
.filter { it != currentChunk }
.forEach { overlapped ->
currentChunk.merge(overlapped, direction)
roomEntity.deleteOnCascade(overlapped)
}
} }
if (nextChunk != null) {
currentChunk.merge(nextChunk, PaginationDirection.FORWARDS)
roomEntity.deleteOnCascade(nextChunk)
}
val eventIds = receivedChunk.events.mapNotNull { it.eventId }
ChunkEntity
.findAllIncludingEvents(realm, eventIds)
.filter { it != currentChunk }
.forEach { overlapped ->
currentChunk.merge(overlapped, direction)
roomEntity.deleteOnCascade(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
roomEntity.addStateEvents(receivedChunk.stateEvents) val isUnlinked = currentChunk.isUnlinked()
roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = isUnlinked)
} }
.map { receivedChunk } .map { receivedChunk }
} }

View File

@ -8,7 +8,6 @@ import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.util.PagingRequestHelper import im.vector.matrix.android.internal.util.PagingRequestHelper
import java.util.* import java.util.*
import java.util.concurrent.Executor


internal class TimelineBoundaryCallback(private val roomId: String, internal class TimelineBoundaryCallback(private val roomId: String,
private val paginationRequest: PaginationRequest, private val paginationRequest: PaginationRequest,
@ -24,28 +23,37 @@ internal class TimelineBoundaryCallback(private val roomId: String,


override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) {
helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) {
monarchy.doWithRealm { realm -> runPaginationRequest(it, itemAtEnd, PaginationDirection.BACKWARDS)
if (itemAtEnd.root.eventId == null) {
return@doWithRealm
}
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.root.eventId)).firstOrNull()
paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, limit, callback = createCallback(it))
}
} }
} }


override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) {
helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) {
monarchy.doWithRealm { realm -> runPaginationRequest(it, itemAtFront, PaginationDirection.FORWARDS)
if (itemAtFront.root.eventId == null) {
return@doWithRealm
}
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.root.eventId)).firstOrNull()
paginationRequest.execute(roomId, chunkEntity?.nextToken, PaginationDirection.FORWARDS, limit, callback = createCallback(it))
}
} }
} }


private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback,
item: EnrichedEvent,
direction: PaginationDirection) {
var token: String? = null
monarchy.doWithRealm { realm ->
if (item.root.eventId == null) {
return@doWithRealm
}
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(item.root.eventId)).firstOrNull()
token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken
}
paginationRequest.execute(
roomId = roomId,
from = token,
direction = direction,
limit = limit,
callback = createCallback(requestCallback)
)
}


private fun createCallback(pagingRequestCallback: PagingRequestHelper.Request.Callback) = object : MatrixCallback<TokenChunkEvent> { private fun createCallback(pagingRequestCallback: PagingRequestHelper.Request.Callback) = object : MatrixCallback<TokenChunkEvent> {
override fun onSuccess(data: TokenChunkEvent) { override fun onSuccess(data: TokenChunkEvent) {
pagingRequestCallback.recordSuccess() pagingRequestCallback.recordSuccess()

View File

@ -0,0 +1,17 @@
package im.vector.matrix.android.internal.util

import java.security.MessageDigest

fun String.md5() = try {
val digest = MessageDigest.getInstance("md5")
digest.update(toByteArray())
val bytes = digest.digest()
val sb = StringBuilder()
for (i in bytes.indices) {
sb.append(String.format("%02X", bytes[i]))
}
sb.toString().toLowerCase()
} catch (exc: Exception) {
// Should not happen, but just in case
hashCode().toString()
}