From 3c7495bd605da1893f9a2055e511373ac3fdfb0a Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 24 Mar 2022 13:50:07 +0200 Subject: [PATCH 1/3] Thread redaction will now update the thread summary counter Root threads with 0 threads replies will become normal messages and removed from thread summaries --- .../api/session/events/model/UnsignedData.kt | 2 + .../database/helper/ThreadEventsHelper.kt | 51 +++++++++++++++---- .../room/prune/RedactionEventProcessor.kt | 38 ++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt index dfe1db7b1c..630a2fb91a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt @@ -46,3 +46,5 @@ data class UnsignedData( @Json(name = "replaces_state") val replacesState: String? = null ) + +fun UnsignedData?.isRedacted() = this?.redactedEvent != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index ee3008d40b..0e85057f23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -16,9 +16,12 @@ package org.matrix.android.sdk.internal.database.helper +import com.squareup.moshi.JsonDataException import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.isRedacted import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -33,6 +36,8 @@ import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.MoshiProvider +import timber.log.Timber private typealias Summary = Pair? @@ -90,19 +95,18 @@ internal fun EventEntity.markEventAsRoot( /** * Count the number of threads for the provided root thread eventId, and finds the latest event message + * note: Redactions are handled by RedactionEventProcessor * @param rootThreadEventId The root eventId that will find the number of threads * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - // Number of messages - val messages = TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) - .count() - .toInt() + val numberOfThread = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return null - if (messages <= 0) return null + if (numberOfThread <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -124,9 +128,38 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(messages, result) + return Summary(numberOfThread, result) } +/** + * Counts the number of threads in the main timeline thread summary, + * with respect to redactions. + */ +internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) + .findAll() + ?.filterNot { timelineEvent -> + timelineEvent.root + ?.unsignedData + ?.takeIf { it.isNotBlank() } + ?.toUnsignedData() + .isRedacted() + }?.size + +/** + * Mapping string to UnsignedData using Moshi + */ +private fun String.toUnsignedData(): UnsignedData? = + try { + MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(this) + } catch (ex: JsonDataException) { + Timber.e(ex, "Failed to parse UnsignedData") + null + } + /** * Lets compare them in case user is moving forward in the timeline and we cannot know the * exact chunk sequence while currentChunk is not yet committed in the DB diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4753e12157..4fcc47a8d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,11 +21,14 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.query.findWithSenderMembershipEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider @@ -89,6 +92,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) eventToPrune.decryptionResultJson = null eventToPrune.decryptionErrorCode = null + + handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho) } // EventType.REACTION -> { // eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId) @@ -104,6 +109,39 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr } } + /** + * Invalidates the number of threads in the main timeline thread summary, + * with respect to redactions. + */ + private fun handleTimelineThreadSummaryIfNeeded( + realm: Realm, + eventToPrune: EventEntity, + isLocalEcho: Boolean, + ) { + if (eventToPrune.isThread() && !isLocalEcho) { + val roomId = eventToPrune.roomId + val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return + val rootThreadEventId = eventToPrune.rootThreadEventId ?: return + + val numberOfThreads = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return + + rootThreadEvent.numberOfThreads = numberOfThreads + if (numberOfThreads == 0) { + // We should also clear the thread summary list + rootThreadEvent.isRootThread = false + rootThreadEvent.threadSummaryLatestMessage = null + ThreadSummaryEntity + .where(realm, roomId = roomId, rootThreadEventId) + .findFirst() + ?.deleteFromRealm() + } + } + } + private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { From 2938fa92c0a6359c94e738d1842d6bbcf7271056 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 11:58:11 +0300 Subject: [PATCH 2/3] Rename countThreads method --- .../sdk/internal/database/helper/ThreadEventsHelper.kt | 4 ++-- .../internal/session/room/prune/RedactionEventProcessor.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 0e85057f23..811c520b8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -100,7 +100,7 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreads( + val numberOfThread = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId @@ -135,7 +135,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: * Counts the number of threads in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4fcc47a8d4..6558a138b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.countThreadReplies import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,7 +123,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreads( + val numberOfThreads = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId From f58f3ad6d9c563322940ee3352273a204b2df603 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 14:28:57 +0300 Subject: [PATCH 3/3] Refactoring --- .../database/helper/ThreadEventsHelper.kt | 24 +++++++++---------- .../internal/database/model/EventEntity.kt | 1 + .../room/prune/RedactionEventProcessor.kt | 10 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 811c520b8b..04cf5b78af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -53,14 +53,14 @@ internal fun Map.updateThreadSummaryIfNeeded( for ((rootThreadEventId, eventEntity) in this) { eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary -> - val numberOfMessages = threadSummary.first + val inThreadMessages = threadSummary.first val latestEventInThread = threadSummary.second // If this is a thread message, find its root event if exists val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - threadsCounted = numberOfMessages, + inThreadMessages = inThreadMessages, latestMessageTimelineEventEntity = latestEventInThread ) } @@ -86,10 +86,10 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = * Mark or update the current event a root thread event */ internal fun EventEntity.markEventAsRoot( - threadsCounted: Int, + inThreadMessages: Int, latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true - numberOfThreads = threadsCounted + numberOfThreads = inThreadMessages threadSummaryLatestMessage = latestMessageTimelineEventEntity } @@ -100,13 +100,13 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return null + ) - if (numberOfThread <= 0) return null + if (inThreadMessages <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -128,26 +128,26 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(numberOfThread, result) + return Summary(inThreadMessages, result) } /** - * Counts the number of threads in the main timeline thread summary, + * Counts the number of thread replies in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEventId: String): Int = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) .findAll() - ?.filterNot { timelineEvent -> + .filterNot { timelineEvent -> timelineEvent.root ?.unsignedData ?.takeIf { it.isNotBlank() } ?.toUnsignedData() .isRedacted() - }?.size + }.size /** * Mapping string to UnsignedData using Moshi diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index b7158ba9cd..09be98aa96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -44,6 +44,7 @@ internal open class EventEntity(@Index var eventId: String = "", // Thread related, no need to create a new Entity for performance @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, + // Number messages within the thread var numberOfThreads: Int = 0, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 6558a138b0..b19b8d4a6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreadReplies +import org.matrix.android.sdk.internal.database.helper.countInThreadMessages import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,14 +123,14 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return + ) - rootThreadEvent.numberOfThreads = numberOfThreads - if (numberOfThreads == 0) { + rootThreadEvent.numberOfThreads = inThreadMessages + if (inThreadMessages == 0) { // We should also clear the thread summary list rootThreadEvent.isRootThread = false rootThreadEvent.threadSummaryLatestMessage = null