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
This commit is contained in:
SpiritCroc 2022-05-22 19:01:23 +02:00
parent bc368070c9
commit a95e41056d
4 changed files with 124 additions and 50 deletions

View file

@ -80,6 +80,63 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
?.findFirst() ?.findFirst()
} }
/**
* Return <Event, Boolean>, 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<TimelineEventEntity, Boolean>? {
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<TimelineEventEntity, Boolean>? = 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<TimelineEventEntity, Boolean>?.previewable(): TimelineEventEntity? {
return if (this == null || !second) {
null
} else {
first
}
}
internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> { internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> {
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) { if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
beginGroup() beginGroup()

View file

@ -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.EventTypeFilter
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters 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.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.bestTimestampPreviewEvent
import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.latestEvent
internal object RoomSummaryEventsHelper { internal object RoomSummaryEventsHelper {
@ -51,31 +52,32 @@ internal object RoomSummaryEventsHelper {
filterEdits = true filterEdits = true
) )
fun getLatestPreviewableEvent(realm: Realm, roomId: String): TimelineEventEntity? { // SC-modified
return TimelineEventEntity.latestEvent( fun getLatestPreviewableEvent(realm: Realm, roomId: String): Pair<TimelineEventEntity, Boolean>? {
return TimelineEventEntity.bestTimestampPreviewEvent(
realm = realm, realm = realm,
roomId = roomId, roomId = roomId,
includesSending = true, //includesSending = true,
filters = previewFilters filters = previewFilters
) )
} }
// SC addition // SC addition
fun getLatestPreviewableEventScAll(realm: Realm, roomId: String): TimelineEventEntity? { fun getLatestPreviewableEventScAll(realm: Realm, roomId: String): Pair<TimelineEventEntity, Boolean>? {
return TimelineEventEntity.latestEvent( return TimelineEventEntity.bestTimestampPreviewEvent(
realm = realm, realm = realm,
roomId = roomId, roomId = roomId,
includesSending = true, //includesSending = true,
filters = previewFiltersScAll filters = previewFiltersScAll
) )
} }
// SC addition // SC addition
fun getLatestPreviewableEventScOriginalContent(realm: Realm, roomId: String): TimelineEventEntity? { fun getLatestPreviewableEventScOriginalContent(realm: Realm, roomId: String): Pair<TimelineEventEntity, Boolean>? {
return TimelineEventEntity.latestEvent( return TimelineEventEntity.bestTimestampPreviewEvent(
realm = realm, realm = realm,
roomId = roomId, roomId = roomId,
includesSending = true, //includesSending = true,
filters = previewFiltersScOriginalContent filters = previewFiltersScOriginalContent
) )
} }

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary
import de.spiritcroc.matrixsdk.StaticScSdkHelper import de.spiritcroc.matrixsdk.StaticScSdkHelper
import io.realm.Realm import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.orFalse 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.SpaceChildSummaryEntity
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity 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.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.findAllInRoomWithSendStates
import org.matrix.android.sdk.internal.database.query.getOrCreate 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.getOrNull
import org.matrix.android.sdk.internal.database.query.isEventRead 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.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.database.query.where
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.clearWith import org.matrix.android.sdk.internal.extensions.clearWith
@ -80,13 +83,49 @@ internal class RoomSummaryUpdater @Inject constructor(
private val roomAccountDataDataSource: RoomAccountDataDataSource 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) val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
if (roomSummaryEntity != null) { if (roomSummaryEntity != null) {
//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 latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId)
val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) val latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) val latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)
attemptToDecryptLatestPreviewables(latestPreviewableEvent, latestPreviewableContentEvent, latestPreviewableOriginalContentEvent)
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 val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent") Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScAll(realm, roomId) refreshLatestPreviewContent(roomSummaryEntity, 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
)
val roomSummaryUnreadCount = roomSummaryEntity.unreadCount val roomSummaryUnreadCount = roomSummaryEntity.unreadCount
if (roomSummaryUnreadCount != null /* && preferences.prioritizeUnreadCountsOverRoomPreviewsForUnreadCalculation() */) { if (roomSummaryUnreadCount != null /* && preferences.prioritizeUnreadCountsOverRoomPreviewsForUnreadCalculation() */) {
@ -188,15 +198,15 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 || roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
//(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) ||
// avoid this call if we are sure there are unread events // 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.hasUnreadContentMessages = roomSummaryEntity.notificationCount > 0 ||
//(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) ||
// avoid this call if we are sure there are unread events // 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.hasUnreadOriginalContentMessages = roomSummaryEntity.notificationCount > 0 ||
//(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) || //(roomSummaryEntity.unreadCount?.let { it > 0 } ?: false) ||
// avoid this call if we are sure there are unread events // 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)) roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
@ -274,9 +284,10 @@ internal class RoomSummaryUpdater @Inject constructor(
fun updateSendingInformation(realm: Realm, roomId: String) { fun updateSendingInformation(realm: Realm, roomId: String) {
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
roomSummaryEntity.updateHasFailedSending() roomSummaryEntity.updateHasFailedSending()
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) refreshLatestPreviewContent(realm, roomId, attemptDecrypt = false)
roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) //roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId).previewable()
roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId) //roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId).previewable()
//roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId).previewable()
} }
/** /**

View file

@ -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.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.StreamEventsManager 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.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
@ -56,6 +57,7 @@ import javax.inject.Inject
internal class TokenChunkEventPersistor @Inject constructor( internal class TokenChunkEventPersistor @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
@UserId private val userId: String, @UserId private val userId: String,
private val roomSummaryUpdater: RoomSummaryUpdater,
private val lightweightSettingsStorage: LightweightSettingsStorage, private val lightweightSettingsStorage: LightweightSettingsStorage,
private val liveEventManager: Lazy<StreamEventsManager>, private val liveEventManager: Lazy<StreamEventsManager>,
private val clock: Clock, private val clock: Clock,
@ -318,6 +320,8 @@ internal class TokenChunkEventPersistor @Inject constructor(
} }
if (currentChunk.isValid) { if (currentChunk.isValid) {
RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) 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()) { if (lightweightSettingsStorage.areThreadMessagesEnabled()) {