Rebranch timeline + continue clean up strategy
This commit is contained in:
parent
3648d6292a
commit
283f32479d
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.helper.nextDisplayIndex
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
import im.vector.matrix.android.internal.session.SessionLifecycleObserver
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val MAX_NUMBER_OF_EVENTS = 35_000L
|
||||||
|
private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class makes sure to stay under a maximum number of events as it makes Realm to be unusable when listening to events
|
||||||
|
* when the database is getting too big.
|
||||||
|
*/
|
||||||
|
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
||||||
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
||||||
|
Timber.v("There are ${allRooms.size} rooms in this session")
|
||||||
|
cleanUp(realm, MAX_NUMBER_OF_EVENTS / 2L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun cleanUp(realm: Realm, threshold: Long) {
|
||||||
|
val numberOfEvents = realm.where(EventEntity::class.java).findAll().size
|
||||||
|
val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size
|
||||||
|
Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents")
|
||||||
|
if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS) {
|
||||||
|
Timber.v("Db is low enough")
|
||||||
|
} else {
|
||||||
|
val thresholdChunks = realm.where(ChunkEntity::class.java)
|
||||||
|
.greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, threshold)
|
||||||
|
.findAll()
|
||||||
|
|
||||||
|
Timber.v("There are ${thresholdChunks.size} chunks to clean with more than $threshold events")
|
||||||
|
for (chunk in thresholdChunks) {
|
||||||
|
val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS)
|
||||||
|
val thresholdDisplayIndex = maxDisplayIndex - threshold
|
||||||
|
val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll()
|
||||||
|
Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}")
|
||||||
|
chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size
|
||||||
|
eventsToRemove.forEach {
|
||||||
|
val canDeleteRoot = it.root?.stateKey == null
|
||||||
|
if (canDeleteRoot) {
|
||||||
|
it.root?.deleteFromRealm()
|
||||||
|
}
|
||||||
|
it.readReceipts?.readReceipts?.deleteAllFromRealm()
|
||||||
|
it.readReceipts?.deleteFromRealm()
|
||||||
|
it.annotations?.apply {
|
||||||
|
editSummary?.deleteFromRealm()
|
||||||
|
pollResponseSummary?.deleteFromRealm()
|
||||||
|
referencesSummaryEntity?.deleteFromRealm()
|
||||||
|
reactionsSummary.deleteAllFromRealm()
|
||||||
|
}
|
||||||
|
it.annotations?.deleteFromRealm()
|
||||||
|
it.readReceipts?.deleteFromRealm()
|
||||||
|
it.deleteFromRealm()
|
||||||
|
}
|
||||||
|
// We reset the prevToken so we will need to fetch again.
|
||||||
|
chunk.prevToken = null
|
||||||
|
}
|
||||||
|
cleanUp(realm, (threshold / 1.5).toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -164,49 +164,6 @@ internal class DefaultSession @Inject constructor(
|
|||||||
}
|
}
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
timelineEventDecryptor.start()
|
timelineEventDecryptor.start()
|
||||||
|
|
||||||
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
|
||||||
awaitTransaction(realmConfiguration) { realm ->
|
|
||||||
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
|
||||||
val numberOfEvents = realm.where(EventEntity::class.java).findAll().size
|
|
||||||
val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size
|
|
||||||
Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents")
|
|
||||||
Timber.v("Number of rooms in db: ${allRooms.size}")
|
|
||||||
if (numberOfTimelineEvents < 30_000L) {
|
|
||||||
Timber.v("Db is low enough")
|
|
||||||
} else {
|
|
||||||
val hugeChunks = realm.where(ChunkEntity::class.java).greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, 250).findAll()
|
|
||||||
Timber.v("There are ${hugeChunks.size} chunks to clean")
|
|
||||||
/*
|
|
||||||
for (chunk in hugeChunks) {
|
|
||||||
val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS)
|
|
||||||
val thresholdDisplayIndex = maxDisplayIndex - 250
|
|
||||||
val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll()
|
|
||||||
Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}")
|
|
||||||
chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size
|
|
||||||
eventsToRemove.forEach {
|
|
||||||
val canDeleteRoot = it.root?.stateKey == null
|
|
||||||
if (canDeleteRoot) {
|
|
||||||
it.root?.deleteFromRealm()
|
|
||||||
}
|
|
||||||
it.readReceipts?.readReceipts?.deleteAllFromRealm()
|
|
||||||
it.readReceipts?.deleteFromRealm()
|
|
||||||
it.annotations?.apply {
|
|
||||||
editSummary?.deleteFromRealm()
|
|
||||||
pollResponseSummary?.deleteFromRealm()
|
|
||||||
referencesSummaryEntity?.deleteFromRealm()
|
|
||||||
reactionsSummary.deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
it.annotations?.deleteFromRealm()
|
|
||||||
it.readReceipts?.deleteFromRealm()
|
|
||||||
it.deleteFromRealm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
|
@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.typing.TypingUsersTracker
|
|||||||
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||||
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
|
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||||
import im.vector.matrix.android.internal.crypto.verification.VerificationMessageProcessor
|
import im.vector.matrix.android.internal.crypto.verification.VerificationMessageProcessor
|
||||||
|
import im.vector.matrix.android.internal.database.DatabaseCleaner
|
||||||
import im.vector.matrix.android.internal.database.EventInsertLiveObserver
|
import im.vector.matrix.android.internal.database.EventInsertLiveObserver
|
||||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
||||||
import im.vector.matrix.android.internal.di.Authenticated
|
import im.vector.matrix.android.internal.di.Authenticated
|
||||||
@ -340,6 +341,10 @@ internal abstract class SessionModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindIdentityService(observer: DefaultIdentityService): SessionLifecycleObserver
|
abstract fun bindIdentityService(observer: DefaultIdentityService): SessionLifecycleObserver
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindDatabaseCleaner(observer: DatabaseCleaner): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
|
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||||
|
|
||||||
|
@ -62,10 +62,10 @@ import im.vector.matrix.android.internal.session.room.tags.AddTagToRoomTask
|
|||||||
import im.vector.matrix.android.internal.session.room.tags.DefaultAddTagToRoomTask
|
import im.vector.matrix.android.internal.session.room.tags.DefaultAddTagToRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.tags.DefaultDeleteTagFromRoomTask
|
import im.vector.matrix.android.internal.session.room.tags.DefaultDeleteTagFromRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.tags.DeleteTagFromRoomTask
|
import im.vector.matrix.android.internal.session.room.tags.DeleteTagFromRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask
|
import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask
|
||||||
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.FetchTokenAndPaginateTask
|
||||||
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
|
||||||
@ -176,7 +176,7 @@ internal abstract class RoomModule {
|
|||||||
abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask
|
abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchNextTokenAndPaginateTask): FetchNextTokenAndPaginateTask
|
abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchTokenAndPaginateTask): FetchTokenAndPaginateTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
||||||
|
@ -29,17 +29,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
|
||||||
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.ChunkEntityFields
|
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
|
||||||
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.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.TimelineEventFilter
|
import im.vector.matrix.android.internal.database.query.TimelineEventFilter
|
||||||
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.database.query.whereInRoom
|
|
||||||
import im.vector.matrix.android.internal.database.query.whereRoomId
|
import im.vector.matrix.android.internal.database.query.whereRoomId
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
@ -72,7 +69,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 fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val settings: TimelineSettings,
|
private val settings: TimelineSettings,
|
||||||
@ -98,9 +95,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
|
private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
|
||||||
private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
|
private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
|
||||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
private lateinit var sendingEvents: RealmResults<TimelineEventEntity>
|
||||||
|
|
||||||
private var roomEntity: RoomEntity? = null
|
|
||||||
|
|
||||||
private var prevDisplayIndex: Int? = null
|
private var prevDisplayIndex: Int? = null
|
||||||
private var nextDisplayIndex: Int? = null
|
private var nextDisplayIndex: Int? = null
|
||||||
@ -119,23 +114,10 @@ internal class DefaultTimeline(
|
|||||||
if (!results.isLoaded || !results.isValid) {
|
if (!results.isLoaded || !results.isValid) {
|
||||||
return@OrderedRealmCollectionChangeListener
|
return@OrderedRealmCollectionChangeListener
|
||||||
}
|
}
|
||||||
|
results.createSnapshot()
|
||||||
handleUpdates(results, changeSet)
|
handleUpdates(results, changeSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
|
|
||||||
var hasChange = false
|
|
||||||
|
|
||||||
(changeSet.insertions + changeSet.changes).forEach {
|
|
||||||
val eventRelations = collection[it]
|
|
||||||
if (eventRelations != null) {
|
|
||||||
hasChange = rebuildEvent(eventRelations.eventId) { te ->
|
|
||||||
te.copy(annotations = eventRelations.asDomain())
|
|
||||||
} || hasChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasChange) postSnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods ******************************************************************************
|
// Public methods ******************************************************************************
|
||||||
|
|
||||||
override fun paginate(direction: Timeline.Direction, count: Int) {
|
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||||
@ -173,15 +155,23 @@ internal class DefaultTimeline(
|
|||||||
val realm = Realm.getInstance(realmConfiguration)
|
val realm = Realm.getInstance(realmConfiguration)
|
||||||
backgroundRealm.set(realm)
|
backgroundRealm.set(realm)
|
||||||
|
|
||||||
roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
|
||||||
|
?: throw IllegalStateException("Can't open a timeline without a room")
|
||||||
|
|
||||||
|
sendingEvents = roomEntity.sendingTimelineEvents.where().filterEventsWithSettings().findAll()
|
||||||
|
sendingEvents.addChangeListener { events ->
|
||||||
|
// Remove in memory as soon as they are known by database
|
||||||
|
events.forEach { te ->
|
||||||
|
inMemorySendingEvents.removeAll { te.eventId == it.eventId }
|
||||||
|
}
|
||||||
|
postSnapshot()
|
||||||
|
}
|
||||||
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||||
filteredEvents = nonFilteredEvents.where()
|
filteredEvents = nonFilteredEvents.where()
|
||||||
.filterEventsWithSettings()
|
.filterEventsWithSettings()
|
||||||
.findAll()
|
.findAll()
|
||||||
|
filteredEvents.addChangeListener(eventsChangeListener)
|
||||||
handleInitialLoad()
|
handleInitialLoad()
|
||||||
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
|
||||||
.findAllAsync()
|
|
||||||
|
|
||||||
if (settings.shouldHandleHiddenReadReceipts()) {
|
if (settings.shouldHandleHiddenReadReceipts()) {
|
||||||
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
||||||
}
|
}
|
||||||
@ -202,9 +192,8 @@ internal class DefaultTimeline(
|
|||||||
cancelableBag.cancel()
|
cancelableBag.cancel()
|
||||||
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
|
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
if (this::sendingEvents.isInitialized) {
|
||||||
if (this::eventRelations.isInitialized) {
|
sendingEvents.removeAllChangeListeners()
|
||||||
eventRelations.removeAllChangeListeners()
|
|
||||||
}
|
}
|
||||||
if (this::nonFilteredEvents.isInitialized) {
|
if (this::nonFilteredEvents.isInitialized) {
|
||||||
nonFilteredEvents.removeAllChangeListeners()
|
nonFilteredEvents.removeAllChangeListeners()
|
||||||
@ -303,7 +292,7 @@ internal class DefaultTimeline(
|
|||||||
listeners.clear()
|
listeners.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimelineHiddenReadReceipts.Delegate
|
// TimelineHiddenReadReceipts.Delegate
|
||||||
|
|
||||||
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
||||||
return rebuildEvent(eventId) { te ->
|
return rebuildEvent(eventId) { te ->
|
||||||
@ -336,7 +325,7 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
||||||
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
@ -400,20 +389,16 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSendingEvents(): List<TimelineEvent> {
|
private fun buildSendingEvents(): List<TimelineEvent> {
|
||||||
val sendingEvents = ArrayList<TimelineEvent>()
|
val builtSendingEvents = ArrayList<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
||||||
sendingEvents.addAll(inMemorySendingEvents.filterEventsWithSettings())
|
builtSendingEvents.addAll(inMemorySendingEvents.filterEventsWithSettings())
|
||||||
roomEntity?.sendingTimelineEvents
|
sendingEvents.forEach { timelineEventEntity ->
|
||||||
?.where()
|
if (builtSendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) {
|
||||||
?.filterEventsWithSettings()
|
builtSendingEvents.add(timelineEventMapper.map(timelineEventEntity))
|
||||||
?.findAll()
|
}
|
||||||
?.forEach { timelineEventEntity ->
|
}
|
||||||
if (sendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) {
|
|
||||||
sendingEvents.add(timelineEventMapper.map(timelineEventEntity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sendingEvents
|
return builtSendingEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canPaginate(direction: Timeline.Direction): Boolean {
|
private fun canPaginate(direction: Timeline.Direction): Boolean {
|
||||||
@ -514,19 +499,25 @@ internal class DefaultTimeline(
|
|||||||
val currentChunk = getLiveChunk()
|
val currentChunk = getLiveChunk()
|
||||||
val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken
|
val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse()) {
|
if (direction == Timeline.Direction.BACKWARDS ||
|
||||||
// We are in the case that next event exists, but we do not know the next token.
|
(direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse())) {
|
||||||
// Fetch (again) the last event to get a nextToken
|
// We are in the case where event exists, but we do not know the token.
|
||||||
val lastKnownEventId = nonFilteredEvents.firstOrNull()?.eventId
|
// Fetch (again) the last event to get a token
|
||||||
|
val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) {
|
||||||
|
nonFilteredEvents.firstOrNull()?.eventId
|
||||||
|
} else {
|
||||||
|
nonFilteredEvents.lastOrNull()?.eventId
|
||||||
|
}
|
||||||
if (lastKnownEventId == null) {
|
if (lastKnownEventId == null) {
|
||||||
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
} else {
|
} else {
|
||||||
val params = FetchNextTokenAndPaginateTask.Params(
|
val params = FetchTokenAndPaginateTask.Params(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
|
direction = direction.toPaginationDirection(),
|
||||||
lastKnownEventId = lastKnownEventId
|
lastKnownEventId = lastKnownEventId
|
||||||
)
|
)
|
||||||
cancelableBag += fetchNextTokenAndPaginateTask
|
cancelableBag += fetchTokenAndPaginateTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = createPaginationCallback(limit, direction)
|
this.callback = createPaginationCallback(limit, direction)
|
||||||
}
|
}
|
||||||
@ -755,7 +746,7 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension methods ***************************************************************************
|
// Extension methods ***************************************************************************
|
||||||
|
|
||||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||||
|
@ -43,7 +43,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 fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
@ -66,7 +66,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||||||
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
|
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
|
||||||
eventBus = eventBus,
|
eventBus = eventBus,
|
||||||
eventDecryptor = eventDecryptor,
|
eventDecryptor = eventDecryptor,
|
||||||
fetchNextTokenAndPaginateTask = fetchNextTokenAndPaginateTask
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,38 +28,48 @@ import im.vector.matrix.android.internal.util.awaitTransaction
|
|||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface FetchNextTokenAndPaginateTask : Task<FetchNextTokenAndPaginateTask.Params, TokenChunkEventPersistor.Result> {
|
internal interface FetchTokenAndPaginateTask : Task<FetchTokenAndPaginateTask.Params, TokenChunkEventPersistor.Result> {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val lastKnownEventId: String,
|
val lastKnownEventId: String,
|
||||||
|
val direction: PaginationDirection,
|
||||||
val limit: Int
|
val limit: Int
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultFetchNextTokenAndPaginateTask @Inject constructor(
|
internal class DefaultFetchTokenAndPaginateTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val filterRepository: FilterRepository,
|
private val filterRepository: FilterRepository,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val eventBus: EventBus
|
private val eventBus: EventBus
|
||||||
) : FetchNextTokenAndPaginateTask {
|
) : FetchTokenAndPaginateTask {
|
||||||
|
|
||||||
override suspend fun execute(params: FetchNextTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result {
|
override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result {
|
||||||
val filter = filterRepository.getRoomFilter()
|
val filter = filterRepository.getRoomFilter()
|
||||||
val response = executeRequest<EventContextResponse>(eventBus) {
|
val response = executeRequest<EventContextResponse>(eventBus) {
|
||||||
apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
|
apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
|
||||||
}
|
}
|
||||||
if (response.end == null) {
|
val fromToken = if (params.direction == PaginationDirection.FORWARDS) {
|
||||||
throw IllegalStateException("No next token found")
|
response.end
|
||||||
|
} else {
|
||||||
|
response.start
|
||||||
}
|
}
|
||||||
monarchy.awaitTransaction {
|
?: throw IllegalStateException("No token found")
|
||||||
ChunkEntity.findIncludingEvent(it, params.lastKnownEventId)?.nextToken = response.end
|
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
val chunkToUpdate = ChunkEntity.findIncludingEvent(realm, params.lastKnownEventId)
|
||||||
|
if (params.direction == PaginationDirection.FORWARDS) {
|
||||||
|
chunkToUpdate?.nextToken = fromToken
|
||||||
|
} else {
|
||||||
|
chunkToUpdate?.prevToken = fromToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val paginationParams = PaginationTask.Params(
|
val paginationParams = PaginationTask.Params(
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
from = response.end,
|
from = fromToken,
|
||||||
direction = PaginationDirection.FORWARDS,
|
direction = params.direction,
|
||||||
limit = params.limit
|
limit = params.limit
|
||||||
)
|
)
|
||||||
return paginationTask.execute(paginationParams)
|
return paginationTask.execute(paginationParams)
|
@ -125,7 +125,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
|
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
|
||||||
.filterReceiptsWithSettings()
|
.filterReceiptsWithSettings()
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
//.also { it.addChangeListener(hiddenReadReceiptsListener) }
|
.also { it.addChangeListener(hiddenReadReceiptsListener) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user