Sync polls until now when landing on screen

This commit is contained in:
Maxime NATUREL 2023-01-23 14:43:21 +01:00
parent 073eda75a2
commit cd1f41594d
3 changed files with 149 additions and 13 deletions

View file

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

View file

@ -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<List<TimelineEvent>> {

View file

@ -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<SyncPollsTask.Params, Unit> {
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<TimelineEvent>,
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,
)
}