Timeline: fetch next token with the help of getContext when required

This commit is contained in:
ganfra 2020-05-15 20:18:07 +02:00
parent ffeae7ec83
commit 458e3ee5e8
5 changed files with 138 additions and 55 deletions

View file

@ -56,8 +56,10 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReportCon
import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
import im.vector.matrix.android.internal.session.room.timeline.FetchNextTokenAndPaginateTask
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask
@ -143,6 +145,9 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask
@Binds
abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchNextTokenAndPaginateTask): FetchNextTokenAndPaginateTask
@Binds @Binds
abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.extensions.orFalse
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.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -71,6 +72,7 @@ internal class DefaultTimeline(
private val realmConfiguration: RealmConfiguration, private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val settings: TimelineSettings, private val settings: TimelineSettings,
@ -519,50 +521,44 @@ internal class DefaultTimeline(
* This has to be called on TimelineThread as it accesses realm live results * This has to be called on TimelineThread as it accesses realm live results
*/ */
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) val currentChunk = getLiveChunk()
val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken
if (token == null) { if (token == null) {
val currentChunk = getLiveChunk() if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse()) {
if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk() == true) {
// We are in the case that next event exists, but we do not know the next token. // We are in the case that next event exists, but we do not know the next token.
// Fetch (again) the last event to get a nextToken // Fetch (again) the last event to get a nextToken
currentChunk.timelineEvents.lastOrNull()?.eventId?.let { fetchEvent(it) } val lastKnownEventId = nonFilteredEvents.firstOrNull()?.eventId
} if (lastKnownEventId == null) {
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
return } else {
} val params = FetchNextTokenAndPaginateTask.Params(
val params = PaginationTask.Params(roomId = roomId, roomId = roomId,
from = token, limit = limit,
direction = direction.toPaginationDirection(), lastKnownEventId = lastKnownEventId
limit = limit) )
cancelableBag += fetchNextTokenAndPaginateTask
Timber.v("Should fetch $limit items $direction") .configureWith(params) {
cancelableBag += paginationTask this.callback = createPaginationCallback(limit, direction)
.configureWith(params) {
this.callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
when (data) {
TokenChunkEventPersistor.Result.SUCCESS -> {
Timber.v("Success fetching $limit items $direction from pagination request")
}
TokenChunkEventPersistor.Result.REACHED_END -> {
postSnapshot()
}
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
// Database won't be updated, so we force pagination request
BACKGROUND_HANDLER.post {
executePaginationTask(direction, limit)
}
} }
} .executeBy(taskExecutor)
override fun onFailure(failure: Throwable) {
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
postSnapshot()
Timber.v("Failure fetching $limit items $direction from pagination request")
}
}
} }
.executeBy(taskExecutor) } else {
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
}
} else {
val params = PaginationTask.Params(
roomId = roomId,
from = token,
direction = direction.toPaginationDirection(),
limit = limit
)
Timber.v("Should fetch $limit items $direction")
cancelableBag += paginationTask
.configureWith(params) {
this.callback = createPaginationCallback(limit, direction)
}
.executeBy(taskExecutor)
}
} }
// For debug purpose only // For debug purpose only
@ -743,6 +739,32 @@ internal class DefaultTimeline(
forwardsState.set(State()) forwardsState.set(State())
} }
private fun createPaginationCallback(limit: Int, direction: Timeline.Direction): MatrixCallback<TokenChunkEventPersistor.Result> {
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
when (data) {
TokenChunkEventPersistor.Result.SUCCESS -> {
Timber.v("Success fetching $limit items $direction from pagination request")
}
TokenChunkEventPersistor.Result.REACHED_END -> {
postSnapshot()
}
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
// Database won't be updated, so we force pagination request
BACKGROUND_HANDLER.post {
executePaginationTask(direction, limit)
}
}
}
override fun onFailure(failure: Throwable) {
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
postSnapshot()
Timber.v("Failure fetching $limit items $direction from pagination request")
}
}
}
// Extension methods *************************************************************************** // Extension methods ***************************************************************************
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {

View file

@ -42,6 +42,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val eventDecryptor: TimelineEventDecryptor, private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
) : TimelineService { ) : TimelineService {
@ -63,7 +64,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
settings = settings, settings = settings,
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
eventBus = eventBus, eventBus = eventBus,
eventDecryptor = eventDecryptor eventDecryptor = eventDecryptor,
fetchNextTokenAndPaginateTask = fetchNextTokenAndPaginateTask
) )
} }

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.filter.FilterRepository
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface FetchNextTokenAndPaginateTask : Task<FetchNextTokenAndPaginateTask.Params, TokenChunkEventPersistor.Result> {
data class Params(
val roomId: String,
val lastKnownEventId: String,
val limit: Int
)
}
internal class DefaultFetchNextTokenAndPaginateTask @Inject constructor(
private val roomAPI: RoomAPI,
private val monarchy: Monarchy,
private val filterRepository: FilterRepository,
private val paginationTask: PaginationTask,
private val eventBus: EventBus
) : FetchNextTokenAndPaginateTask {
override suspend fun execute(params: FetchNextTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result {
val filter = filterRepository.getRoomFilter()
val response = executeRequest<EventContextResponse>(eventBus) {
apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
}
if (response.end == null) {
throw IllegalStateException("No next token found")
}
monarchy.awaitTransaction {
ChunkEntity.findIncludingEvent(it, params.lastKnownEventId)?.nextToken = response.end
}
val paginationParams = PaginationTask.Params(
roomId = params.roomId,
from = response.end,
direction = PaginationDirection.FORWARDS,
limit = params.limit
)
return paginationTask.execute(paginationParams)
}
}

View file

@ -230,21 +230,9 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
val chunksToDelete = ArrayList<ChunkEntity>() val chunksToDelete = ArrayList<ChunkEntity>()
chunks.forEach { chunks.forEach {
if (it != currentChunk) { if (it != currentChunk) {
if (direction == PaginationDirection.FORWARDS && it.hasBeenALastForwardChunk()) { Timber.d("Merge $it")
// Maybe it was a trick to get a nextToken currentChunk.merge(roomId, it, direction)
if (receivedChunk.events.size == 1) { chunksToDelete.add(it)
Timber.d("Receiving a new nextToken")
it.nextToken = receivedChunk.end
chunksToDelete.add(currentChunk)
} else {
Timber.d("Do not merge $it")
chunksToDelete.add(it)
}
} else {
Timber.d("Merge $it")
currentChunk.merge(roomId, it, direction)
chunksToDelete.add(it)
}
} }
} }
val shouldUpdateSummary = chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS val shouldUpdateSummary = chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS