From a95e41056d26a3764dd82d540063eef9738cd28a Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Sun, 22 May 2022 19:01:23 +0200 Subject: [PATCH] Improve room preview generation - Also look into past chunks for previewable events - Better timestamp guesses if not enough history loaded yet - Update missing previews when more chunks loaded Change-Id: I27a420b9564e091db0e322751c798e205e26fd47 --- .../query/TimelineEventEntityQueries.kt | 57 ++++++++++++ .../room/summary/RoomSummaryEventsHelper.kt | 20 ++-- .../room/summary/RoomSummaryUpdater.kt | 93 +++++++++++-------- .../room/timeline/TokenChunkEventPersistor.kt | 4 + 4 files changed, 124 insertions(+), 50 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 1654a33806..9721a0a4ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -80,6 +80,63 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, ?.findFirst() } +/** + * Return , with: + * - Event: best event we could find to generate room timestamps + * - Boolean: true if Event is previewable, else false + */ +internal fun TimelineEventEntity.Companion.bestTimestampPreviewEvent(realm: Realm, + roomId: String, + filters: TimelineEventFilters = TimelineEventFilters(), + chunk: ChunkEntity? = null, + maxChunksToVisit: Int = 10): Pair? { + val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null + + // First try currently sending events, later recurse and try chunks + val query = if (chunk == null) { + roomEntity.sendingTimelineEvents.where().filterEvents(filters) + } else { + chunk.timelineEvents.where()?.filterEvents(filters) + } + val filteredResult = query + ?.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + ?.findFirst() + if (filteredResult != null) { + return Pair(filteredResult, true) + } + var recursiveResult: Pair? = null + // One recursion step more than maxChunksToVisit, since in first step, we don't visit any chunk, but only sendingTimelineEvents + if (maxChunksToVisit > 0 || chunk == null) { + if (chunk == null) { + // Initial chunk recursion + val latestChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) + if (latestChunk != null) { + recursiveResult = bestTimestampPreviewEvent(realm, roomId, filters, latestChunk, maxChunksToVisit - 1) + } + } else { + val prevChunk = chunk.prevChunk + if (prevChunk != null) { + recursiveResult = bestTimestampPreviewEvent(realm, roomId, filters, prevChunk, maxChunksToVisit - 1) + } + } + } + if (recursiveResult != null) { + return recursiveResult + } + // If we haven't found any previewable event by now, fall back to the oldest non-previewable event we found + return chunk?.timelineEvents?.where()?.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)?.findFirst()?.let { + Pair(it, false) + } +} + +internal fun Pair?.previewable(): TimelineEventEntity? { + return if (this == null || !second) { + null + } else { + first + } +} + internal fun RealmQuery.filterEvents(filters: TimelineEventFilters): RealmQuery { if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) { beginGroup() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt index d52f1c5b45..503f0aad4e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.query.bestTimestampPreviewEvent import org.matrix.android.sdk.internal.database.query.latestEvent internal object RoomSummaryEventsHelper { @@ -51,31 +52,32 @@ internal object RoomSummaryEventsHelper { filterEdits = true ) - fun getLatestPreviewableEvent(realm: Realm, roomId: String): TimelineEventEntity? { - return TimelineEventEntity.latestEvent( + // SC-modified + fun getLatestPreviewableEvent(realm: Realm, roomId: String): Pair? { + return TimelineEventEntity.bestTimestampPreviewEvent( realm = realm, roomId = roomId, - includesSending = true, + //includesSending = true, filters = previewFilters ) } // SC addition - fun getLatestPreviewableEventScAll(realm: Realm, roomId: String): TimelineEventEntity? { - return TimelineEventEntity.latestEvent( + fun getLatestPreviewableEventScAll(realm: Realm, roomId: String): Pair? { + return TimelineEventEntity.bestTimestampPreviewEvent( realm = realm, roomId = roomId, - includesSending = true, + //includesSending = true, filters = previewFiltersScAll ) } // SC addition - fun getLatestPreviewableEventScOriginalContent(realm: Realm, roomId: String): TimelineEventEntity? { - return TimelineEventEntity.latestEvent( + fun getLatestPreviewableEventScOriginalContent(realm: Realm, roomId: String): Pair? { + return TimelineEventEntity.bestTimestampPreviewEvent( realm = realm, roomId = roomId, - includesSending = true, + //includesSending = true, filters = previewFiltersScOriginalContent ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index d6a8eec145..01e23791be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary import de.spiritcroc.matrixsdk.StaticScSdkHelper import io.realm.Realm +import io.realm.Sort import io.realm.kotlin.createObject import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.extensions.orFalse @@ -52,11 +53,13 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntity import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.isEventRead import org.matrix.android.sdk.internal.database.query.latestEvent +import org.matrix.android.sdk.internal.database.query.previewable import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith @@ -80,13 +83,49 @@ internal class RoomSummaryUpdater @Inject constructor( private val roomAccountDataDataSource: RoomAccountDataDataSource ) { - fun refreshLatestPreviewContent(realm: Realm, roomId: String) { + fun refreshLatestPreviewContent(realm: Realm, roomId: String, attemptDecrypt: Boolean = true) { val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId) if (roomSummaryEntity != null) { - val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId) - val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) - attemptToDecryptLatestPreviewables(latestPreviewableEvent, latestPreviewableContentEvent, latestPreviewableOriginalContentEvent) + //roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId)?.first + //roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)?.first + //val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)?.first + //attemptToDecryptLatestPreviewables(latestPreviewableEvent, latestPreviewableContentEvent, latestPreviewableOriginalContentEvent) + refreshLatestPreviewContent(roomSummaryEntity, realm, roomId, attemptDecrypt) + } + } + + fun refreshLatestPreviewContentIfNull(realm: Realm, roomId: String) { + val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId) ?: return + if (roomSummaryEntity.latestPreviewableOriginalContentEvent == null) { + refreshLatestPreviewContent(roomSummaryEntity, realm, roomId) + } + } + + private fun refreshLatestPreviewContent(roomSummaryEntity: RoomSummaryEntity, realm: Realm, roomId: String, attemptDecrypt: Boolean = true) { + val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId) + val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) + val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) + + roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent.previewable() + roomSummaryEntity.latestPreviewableContentEvent = latestPreviewableContentEvent.previewable() + roomSummaryEntity.latestPreviewableOriginalContentEvent = latestPreviewableOriginalContentEvent.previewable() + val scLatestPreviewableEvent = roomSummaryEntity.scLatestPreviewableEvent() + + val lastActivityFromEvent = scLatestPreviewableEvent?.root?.originServerTs + if (lastActivityFromEvent != null) { + roomSummaryEntity.lastActivityTime = lastActivityFromEvent + } + // If we still did not find a timestamp for the last activity: + // Any (non-previewable) event is still better for sorting than just dropping the room to the bottom in the list + if (roomSummaryEntity.lastActivityTime == null) { + roomSummaryEntity.lastActivityTime = latestPreviewableOriginalContentEvent?.first?.root?.originServerTs + } + if (attemptDecrypt) { + attemptToDecryptLatestPreviewables( + roomSummaryEntity.latestPreviewableEvent, + roomSummaryEntity.latestPreviewableContentEvent, + roomSummaryEntity.latestPreviewableOriginalContentEvent + ) } } @@ -142,36 +181,7 @@ internal class RoomSummaryUpdater @Inject constructor( val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent") - val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId) - val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) - - roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent - roomSummaryEntity.latestPreviewableContentEvent = latestPreviewableContentEvent - roomSummaryEntity.latestPreviewableOriginalContentEvent = latestPreviewableOriginalContentEvent - val scLatestPreviewableEvent = roomSummaryEntity.scLatestPreviewableEvent() - - val lastActivityFromEvent = scLatestPreviewableEvent?.root?.originServerTs - if (lastActivityFromEvent != null) { - roomSummaryEntity.lastActivityTime = lastActivityFromEvent - } else if (latestPreviewableEvent != scLatestPreviewableEvent) { - // Try using a less aggressive previewable filter for last activity, so we avoid null timestamps, which would just drop the room to the bottom - roomSummaryEntity.lastActivityTime = latestPreviewableEvent?.root?.originServerTs - } - // If we still did not find a timestamp for the last activity: - // Any (non-previewable) event is still better for sorting than just dropping the room to the bottom in the list - if (roomSummaryEntity.lastActivityTime == null) { - roomSummaryEntity.lastActivityTime = TimelineEventEntity.latestEvent( - realm = realm, - roomId = roomId, - includesSending = true - )?.root?.originServerTs - } - attemptToDecryptLatestPreviewables( - roomSummaryEntity.latestPreviewableEvent, - roomSummaryEntity.latestPreviewableContentEvent, - roomSummaryEntity.latestPreviewableOriginalContentEvent - ) + refreshLatestPreviewContent(roomSummaryEntity, realm, roomId) val roomSummaryUnreadCount = roomSummaryEntity.unreadCount if (roomSummaryUnreadCount != null /* && preferences.prioritizeUnreadCountsOverRoomPreviewsForUnreadCalculation() */) { @@ -188,15 +198,15 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || // avoid this call if we are sure there are unread events - latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false + roomSummaryEntity.latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false roomSummaryEntity.hasUnreadContentMessages = roomSummaryEntity.notificationCount > 0 || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || // avoid this call if we are sure there are unread events - latestPreviewableContentEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false + roomSummaryEntity.latestPreviewableContentEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false roomSummaryEntity.hasUnreadOriginalContentMessages = roomSummaryEntity.notificationCount > 0 || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || // avoid this call if we are sure there are unread events - latestPreviewableOriginalContentEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false + roomSummaryEntity.latestPreviewableOriginalContentEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false } roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) @@ -274,9 +284,10 @@ internal class RoomSummaryUpdater @Inject constructor( fun updateSendingInformation(realm: Realm, roomId: String) { val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) roomSummaryEntity.updateHasFailedSending() - roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) + refreshLatestPreviewContent(realm, roomId, attemptDecrypt = false) + //roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId).previewable() + //roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId).previewable() + //roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId).previewable() } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 6a6dac51a2..9e9e1c5dc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.StreamEventsManager +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -56,6 +57,7 @@ import javax.inject.Inject internal class TokenChunkEventPersistor @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @UserId private val userId: String, + private val roomSummaryUpdater: RoomSummaryUpdater, private val lightweightSettingsStorage: LightweightSettingsStorage, private val liveEventManager: Lazy, private val clock: Clock, @@ -318,6 +320,8 @@ internal class TokenChunkEventPersistor @Inject constructor( } if (currentChunk.isValid) { RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) + // After linking chunks, we may have a new room summary preview + roomSummaryUpdater.refreshLatestPreviewContentIfNull(realm, roomId) } if (lightweightSettingsStorage.areThreadMessagesEnabled()) {