From cd1f41594dc760548a6b9521aa8b2be3d2f63da5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:43:21 +0100 Subject: [PATCH] Sync polls until now when landing on screen --- .../sdk/internal/session/room/RoomModule.kt | 5 + .../room/poll/DefaultPollHistoryService.kt | 36 ++++-- .../session/room/poll/SyncPollsTask.kt | 121 ++++++++++++++++++ 3 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 56925d55d5..673a979633 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -93,8 +93,10 @@ import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.poll.DefaultGetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.DefaultLoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.DefaultSyncPollsTask import org.matrix.android.sdk.internal.session.room.poll.GetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.LoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.SyncPollsTask import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask @@ -374,4 +376,7 @@ internal abstract class RoomModule { @Binds abstract fun bindFilterAndStoreEventsTask(task: DefaultFilterAndStoreEventsTask): FilterAndStoreEventsTask + + @Binds + abstract fun bindSyncPollsTask(task: DefaultSyncPollsTask): SyncPollsTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 2fb63ef90e..52ea76b168 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -23,7 +23,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.kotlin.where -import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService @@ -50,6 +51,7 @@ internal class DefaultPollHistoryService @AssistedInject constructor( private val clock: Clock, private val loadMorePollsTask: LoadMorePollsTask, private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask, + private val syncPollsTask: SyncPollsTask, private val timelineEventMapper: TimelineEventMapper, ) : PollHistoryService { @@ -71,20 +73,23 @@ internal class DefaultPollHistoryService @AssistedInject constructor( ) timelineService.createTimeline(eventId = null, settings = settings).also { it.start() } } + private val timelineMutex = Mutex() override fun dispose() { timeline.dispose() } override suspend fun loadMore(): LoadedPollsStatus { - val params = LoadMorePollsTask.Params( - timeline = timeline, - roomId = roomId, - currentTimestampMs = clock.epochMillis(), - loadingPeriodInDays = loadingPeriodInDays, - eventsPageSize = EVENTS_PAGE_SIZE, - ) - return loadMorePollsTask.execute(params) + return timelineMutex.withLock { + val params = LoadMorePollsTask.Params( + timeline = timeline, + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + loadingPeriodInDays = loadingPeriodInDays, + eventsPageSize = EVENTS_PAGE_SIZE, + ) + loadMorePollsTask.execute(params) + } } override suspend fun getLoadedPollsStatus(): LoadedPollsStatus { @@ -96,10 +101,15 @@ internal class DefaultPollHistoryService @AssistedInject constructor( } override suspend fun syncPolls() { - // TODO unmock - // TODO when sync forward, jump to most recent event Id + paginate forward + jump to oldest eventId after - // TODO avoid possibility to call sync and loadMore at the same time from the service API, how? - delay(1000) + timelineMutex.withLock { + val params = SyncPollsTask.Params( + timeline = timeline, + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + eventsPageSize = EVENTS_PAGE_SIZE, + ) + syncPollsTask.execute(params) + } } override fun getPollEvents(): LiveData> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt new file mode 100644 index 0000000000..e968095408 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.room.poll + +import com.zhuinden.monarchy.Monarchy +import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.session.events.model.isPoll +import org.matrix.android.sdk.api.session.events.model.isPollResponse +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask +import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface SyncPollsTask : Task { + data class Params( + val timeline: Timeline, + val roomId: String, + val currentTimestampMs: Long, + val eventsPageSize: Int, + ) +} + +internal class DefaultSyncPollsTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : SyncPollsTask { + + override suspend fun execute(params: SyncPollsTask.Params) { + val currentPollHistoryStatus = getCurrentPollHistoryStatus(params.roomId) + + params.timeline.restartWithEventId(currentPollHistoryStatus.mostRecentEventIdReached) + + var loadStatus = LoadStatus(shouldLoadMore = true) + while (loadStatus.shouldLoadMore){ + loadStatus = fetchMorePollEventsForward(params) + } + + params.timeline.restartWithEventId(currentPollHistoryStatus.oldestEventIdReached) + } + + private suspend fun getCurrentPollHistoryStatus(roomId: String): PollHistoryStatusEntity { + return monarchy.awaitTransaction { realm -> + PollHistoryStatusEntity + .getOrCreate(realm, roomId) + .copy() + } + } + + private suspend fun fetchMorePollEventsForward(params: SyncPollsTask.Params): LoadStatus { + val events = params.timeline.awaitPaginate( + direction = Timeline.Direction.FORWARDS, + count = params.eventsPageSize, + ) + + val paginationState = params.timeline.getPaginationState(direction = Timeline.Direction.FORWARDS) + + return updatePollHistoryStatus( + roomId = params.roomId, + currentTimestampMs = params.currentTimestampMs, + events = events, + paginationState = paginationState, + ) + } + + private suspend fun updatePollHistoryStatus( + roomId: String, + currentTimestampMs: Long, + events: List, + paginationState: Timeline.PaginationState, + ): LoadStatus { + return monarchy.awaitTransaction { realm -> + val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) + val mostRecentEventIdReached = status.mostRecentEventIdReached + + val mostRecentEvent = events + .maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE } + ?.root + + if (mostRecentEventIdReached == null) { + // save it for next forward pagination + status.mostRecentEventIdReached = mostRecentEvent?.eventId + } + + val mostRecentTimestamp = mostRecentEvent?.ageLocalTs + + val shouldLoadMore = paginationState.hasMoreToLoad && + (mostRecentTimestamp == null || mostRecentTimestamp < currentTimestampMs) + + LoadStatus(shouldLoadMore = shouldLoadMore) + } + } + + private class LoadStatus( + val shouldLoadMore: Boolean, + ) +}