From b37877746a522dd92b4a27b125649054b7f86ffc Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jul 2019 14:39:15 +0200 Subject: [PATCH 01/22] Introduce TimelineEventEntity to begin with the rework --- .../internal/database/model/EventEntity.kt | 5 +++ .../database/model/SessionRealmModule.kt | 1 + .../database/model/TimelineEventEntity.kt | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 5b3e8690..0721f684 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.api.session.room.timeline.Timeline import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index @@ -66,4 +67,8 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI @LinkingObjects("untimelinedStateEvents") val room: RealmResults? = null + @LinkingObjects("root") + val timelineEventEntity: RealmResults? = null + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index b926da99..0e4dc1ae 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -25,6 +25,7 @@ import io.realm.annotations.RealmModule classes = [ ChunkEntity::class, EventEntity::class, + TimelineEventEntity::class, FilterEntity::class, GroupEntity::class, GroupSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt new file mode 100644 index 00000000..c1c0b547 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2019 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.model + +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey +import java.util.* + + +internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(), + @Index var eventId: String = "", + var root: EventEntity? = null, + var annotations: EventAnnotationsSummaryEntity? = null, + var senderName: String? = null, + var isUniqueDisplayName: Boolean = false, + var senderAvatar: String? = null +) : RealmObject() { + + companion object + +} \ No newline at end of file From cbfd2af74b863009daa412e06b4b6bff6de9fda2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jul 2019 16:07:12 +0200 Subject: [PATCH 02/22] Start branching TimelineEventEntity --- .../session/room/timeline/TimelineTest.kt | 19 -- .../database/helper/ChunkEntityHelper.kt | 46 ++--- .../database/helper/RoomEntityHelper.kt | 8 +- .../mapper/EventAnnotationsSummaryMapper.kt | 32 ++++ .../database/mapper/RoomSummaryMapper.kt | 12 +- .../database/mapper/TimelineEventMapper.kt | 60 +++++++ .../internal/database/model/ChunkEntity.kt | 2 +- .../internal/database/model/EventEntity.kt | 5 +- .../internal/database/model/RoomEntity.kt | 2 +- .../database/model/RoomSummaryEntity.kt | 2 +- .../database/model/TimelineEventEntity.kt | 6 + .../database/query/ChunkEntityQueries.kt | 2 +- .../database/query/EventEntityQueries.kt | 27 +-- .../query/TimelineEventEntityQueries.kt | 65 +++++++ .../session/room/EventRelationExtractor.kt | 36 ---- .../internal/session/room/RoomFactory.kt | 4 +- .../internal/session/room/RoomModule.kt | 6 - .../session/room/RoomSummaryUpdater.kt | 7 +- .../membership/SenderRoomMemberExtractor.kt | 26 +-- .../session/room/read/DefaultReadService.kt | 18 +- .../session/room/read/SetReadMarkersTask.kt | 7 +- .../session/room/timeline/DefaultTimeline.kt | 14 +- .../room/timeline/DefaultTimelineService.kt | 52 ++---- .../room/timeline/TimelineEventFactory.kt | 164 ------------------ 24 files changed, 251 insertions(+), 371 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt index 18f191de..f09b0376 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt @@ -18,25 +18,6 @@ package im.vector.matrix.android.session.room.timeline import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.api.session.crypto.CryptoService -import im.vector.matrix.android.api.session.room.timeline.Timeline -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.matrix.android.internal.crypto.CryptoManager -import im.vector.matrix.android.internal.database.model.SessionRealmModule -import im.vector.matrix.android.internal.session.room.EventRelationExtractor -import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor -import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline -import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory -import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.testCoroutineDispatchers -import io.realm.Realm -import io.realm.RealmConfiguration -import org.amshove.kluent.shouldEqual -import org.junit.Before -import org.junit.Test -import timber.log.Timber -import java.util.concurrent.CountDownLatch internal class TimelineTest : InstrumentedTest { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index 1741796b..f8ccc5f9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -18,13 +18,14 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.query.fastContains +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +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.query.find +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import io.realm.Sort @@ -32,12 +33,12 @@ import io.realm.Sort // By default if a chunk is empty we consider it unlinked internal fun ChunkEntity.isUnlinked(): Boolean { assertIsManaged() - return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty() + return timelineEvents.where().equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false).findAll().isEmpty() } internal fun ChunkEntity.deleteOnCascade() { assertIsManaged() - this.events.deleteAllFromRealm() + this.timelineEvents.deleteAllFromRealm() this.deleteFromRealm() } @@ -50,20 +51,22 @@ internal fun ChunkEntity.merge(roomId: String, val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) { - this.events.forEach { it.isUnlinked = false } + this.timelineEvents.forEach { it.root?.isUnlinked = false } } - val eventsToMerge: List + val eventsToMerge: List if (direction == PaginationDirection.FORWARDS) { this.nextToken = chunkToMerge.nextToken this.isLastForward = chunkToMerge.isLastForward - eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) } else { this.prevToken = chunkToMerge.prevToken this.isLastBackward = chunkToMerge.isLastBackward - eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } eventsToMerge.forEach { - add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked) + it.root?.let { root -> + add(roomId, root.asDomain(), direction, isUnlinked = isUnlinked) + } } } @@ -86,7 +89,7 @@ internal fun ChunkEntity.add(roomId: String, isUnlinked: Boolean = false) { assertIsManaged() - if (event.eventId != null && events.fastContains(event.eventId)) { + if (event.eventId != null && timelineEvents.find(event.eventId) != null) { return } var currentDisplayIndex = lastDisplayIndex(direction, 0) @@ -101,21 +104,22 @@ internal fun ChunkEntity.add(roomId: String, if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) { currentStateIndex += 1 forwardsStateIndex = currentStateIndex - } else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) { - val lastEventType = events.last()?.type ?: "" + } else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) { + val lastEventType = timelineEvents.last()?.root?.type ?: "" if (EventType.isStateEvent(lastEventType)) { currentStateIndex -= 1 backwardsStateIndex = currentStateIndex } } - val eventEntity = event.toEntity(roomId).apply { - this.stateIndex = currentStateIndex - this.isUnlinked = isUnlinked - this.displayIndex = currentDisplayIndex - this.sendState = SendState.SYNCED + + val eventEntity = TimelineEventEntity().apply { + this.root = event.toEntity(roomId) + this.eventId = event.eventId ?: "" + this.roomId = roomId + this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() } - val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size - events.add(position, eventEntity) + val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size + timelineEvents.add(position, eventEntity) } internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 04921a9a..885b25c7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity 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.query.fastContains import im.vector.matrix.android.internal.extensions.assertIsManaged @@ -60,5 +61,10 @@ internal fun RoomEntity.addSendingEvent(event: Event) { val eventEntity = event.toEntity(roomId).apply { this.sendState = SendState.UNSENT } - sendingTimelineEvents.add(0, eventEntity) + val timelineEventEntity = TimelineEventEntity().also { + it.root = eventEntity + it.eventId = event.eventId ?: "" + it.roomId = roomId + } + sendingTimelineEvents.add(0, timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt index e6b6197c..1b7b7f64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -19,7 +19,10 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary +import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity +import io.realm.RealmList internal object EventAnnotationsSummaryMapper { fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { @@ -45,6 +48,35 @@ internal object EventAnnotationsSummaryMapper { } ) } + + fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity { + val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity() + eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId + eventAnnotationsSummaryEntity.roomId = roomId + eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let { + EditAggregatedSummaryEntity( + ContentMapper.map(it.aggregatedContent), + RealmList().apply { addAll(it.sourceEvents) }, + RealmList().apply { addAll(it.localEchos) }, + it.lastEditTs + ) + } + eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary?.let { + RealmList().apply { + addAll(it.map { + ReactionAggregatedSummaryEntity( + it.key, + it.count, + it.addedByMe, + it.firstTimestamp, + RealmList().apply { addAll(it.sourceEvents) }, + RealmList().apply { addAll(it.localEchoEvents) } + ) + }) + } + } + return eventAnnotationsSummaryEntity + } } internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index b5fdabc7..9991049e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -19,29 +19,21 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import javax.inject.Inject -internal class RoomSummaryMapper @Inject constructor(private val timelineEventFactory: TimelineEventFactory) { +internal class RoomSummaryMapper @Inject constructor() { fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary { val tags = roomSummaryEntity.tags.map { RoomTag(it.tagName, it.tagOrder) } - val latestEvent = if (getLatestEvent) { - roomSummaryEntity.latestEvent?.let { - timelineEventFactory.create(it, it.realm) - } - } else { - null - } return RoomSummary( roomId = roomSummaryEntity.roomId, displayName = roomSummaryEntity.displayName ?: "", topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", isDirect = roomSummaryEntity.isDirect, - latestEvent = latestEvent, + latestEvent = roomSummaryEntity.latestEvent?.asDomain(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt new file mode 100644 index 00000000..27d4a11b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2019 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.mapper + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.database.model.TimelineEventEntity + +internal object TimelineEventMapper { + + fun map(timelineEvent: TimelineEvent, roomId: String): TimelineEventEntity { + val timelineEventEntity = TimelineEventEntity() + timelineEventEntity.root = timelineEvent.root.toEntity(roomId) + timelineEventEntity.eventId = timelineEvent.root.eventId ?: "" + timelineEventEntity.roomId = roomId + timelineEventEntity.annotations = timelineEvent.annotations?.let { EventAnnotationsSummaryMapper.map(it, roomId) } + return timelineEventEntity + } + + fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { + return TimelineEvent( + root = timelineEventEntity.root?.asDomain() + ?: Event("", timelineEventEntity.eventId), + annotations = timelineEventEntity.annotations?.asDomain(), + localId = timelineEventEntity.localId, + displayIndex = timelineEventEntity.root?.displayIndex ?: 0, + senderName = timelineEventEntity.senderName, + isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, + senderAvatar = timelineEventEntity.senderAvatar, + sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, + hasClearEventFlag = false + ) + } + +} + +internal fun TimelineEventEntity.asDomain(): TimelineEvent { + return TimelineEventMapper.map(this) +} + +internal fun TimelineEvent.toEntity(roomId: String): TimelineEventEntity { + return TimelineEventMapper.map(this, roomId) +} + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt index 6f34fb03..77a97f72 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt @@ -24,7 +24,7 @@ import io.realm.annotations.LinkingObjects internal open class ChunkEntity(@Index var prevToken: String? = null, @Index var nextToken: String? = null, - var events: RealmList = RealmList(), + var timelineEvents: RealmList = RealmList(), @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false, var backwardsDisplayIndex: Int? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 0721f684..91375a96 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -27,7 +27,7 @@ import java.util.* internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(), @Index var eventId: String = "", - var roomId: String = "", + @Index var roomId: String = "", @Index var type: String = "", var content: String? = null, var prevContent: String? = null, @@ -61,9 +61,6 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI companion object - @LinkingObjects("events") - val chunk: RealmResults? = null - @LinkingObjects("untimelinedStateEvents") val room: RealmResults? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt index 2341e492..b9c110b5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt @@ -26,7 +26,7 @@ import kotlin.properties.Delegates internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), var untimelinedStateEvents: RealmList = RealmList(), - var sendingTimelineEvents: RealmList = RealmList(), + var sendingTimelineEvents: RealmList = RealmList(), var areAllMembersLoaded: Boolean = false ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index 5e3a9752..c178711c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var displayName: String? = "", var avatarUrl: String? = "", var topic: String? = "", - var latestEvent: EventEntity? = null, + var latestEvent: TimelineEventEntity? = null, var heroes: RealmList = RealmList(), var joinedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index c1c0b547..bb910aec 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -17,13 +17,16 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmObject +import io.realm.RealmResults import io.realm.annotations.Index +import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import java.util.* internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(), @Index var eventId: String = "", + @Index var roomId: String = "", var root: EventEntity? = null, var annotations: EventAnnotationsSummaryEntity? = null, var senderName: String? = null, @@ -31,6 +34,9 @@ internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.r var senderAvatar: String? = null ) : RealmObject() { + @LinkingObjects("timelineEvents") + val chunk: RealmResults? = null + companion object } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt index bfaac609..2f714562 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt @@ -49,7 +49,7 @@ internal fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomI internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where() - .`in`(ChunkEntityFields.EVENTS.EVENT_ID, eventIds.toTypedArray()) + .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray()) .findAll() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index f7a71c47..cf0f845b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -16,12 +16,13 @@ package im.vector.matrix.android.internal.database.query -import im.vector.matrix.android.internal.database.helper.addSendingEvent import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields 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 io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery @@ -62,30 +63,6 @@ internal fun EventEntity.Companion.types(realm: Realm, return query } - -internal fun EventEntity.Companion.latestEvent(realm: Realm, - roomId: String, - includedTypes: List = emptyList(), - excludedTypes: List = emptyList()): EventEntity? { - - val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null - val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) { - roomEntity.sendingTimelineEvents - } else { - ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events - } - val query = eventList?.where() - if (includedTypes.isNotEmpty()) { - query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) - } else if (excludedTypes.isNotEmpty()) { - query?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) - } - return query - ?.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - ?.findFirst() -} - - internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): EventEntity? { if (from != null) { if (strict) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt new file mode 100644 index 00000000..28b2bc01 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2019 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.query + +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* +import im.vector.matrix.android.internal.database.model.EventEntityFields +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 io.realm.Realm +import io.realm.RealmList +import io.realm.RealmQuery +import io.realm.Sort +import io.realm.kotlin.where + +internal fun TimelineEventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { + return realm.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId) +} + +internal fun TimelineEventEntity.Companion.where(realm: Realm, eventIds: List): RealmQuery { + return realm.where().`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray()) +} + + +internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, + roomId: String, + includedTypes: List = emptyList(), + excludedTypes: List = emptyList()): TimelineEventEntity? { + + val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null + val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) { + roomEntity.sendingTimelineEvents + } else { + ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents + } + val query = eventList?.where() + if (includedTypes.isNotEmpty()) { + query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) + } else if (excludedTypes.isNotEmpty()) { + query?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) + } + return query + ?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + ?.findFirst() +} + +internal fun RealmList.find(eventId: String): TimelineEventEntity? { + return this.where().equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, eventId).findFirst() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt deleted file mode 100644 index 147726b1..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationExtractor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 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.session.room - -import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary -import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope -import io.realm.Realm -import javax.inject.Inject - -/** - * Fetches annotations (reactions, edits...) associated to a given eventEntity from the data layer. - */ - -internal class EventRelationExtractor @Inject constructor() { - - fun extractFrom(event: EventEntity, realm: Realm = event.realm): EventAnnotationsSummary? { - return EventAnnotationsSummaryEntity.where(realm, event.eventId).findFirst()?.asDomain() - } -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index d49471fb..13ca4e67 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -39,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.InMemoryTimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.task.TaskExecutor import javax.inject.Inject @@ -63,8 +62,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, private val leaveRoomTask: LeaveRoomTask) { fun create(roomId: String): Room { - val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService) - val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, cryptoService, paginationTask) + val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 1e0794a4..9cf6ac69 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -138,10 +138,4 @@ internal abstract class RoomModule { @Binds abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService - @Binds - abstract fun bindSimpleTimelineEventFactory(timelineEventFactory: SimpleTimelineEventFactory): TimelineEventFactory - - @Binds - abstract fun bindCacheableTimelineEventFactory(inMemoryTimelineEventFactory: InMemoryTimelineEventFactory): CacheableTimelineEventFactory - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 76f2d151..8fdb5fe9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where @@ -77,19 +78,19 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C } } roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0 - roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?:0 + roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 if (membership != null) { roomSummaryEntity.membership = membership } - val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES) + val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.topic = lastTopicEvent?.content.toModel()?.topic - roomSummaryEntity.latestEvent = lastEvent + roomSummaryEntity.latestEvent = latestEvent roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt index 890ecc8e..f2629f4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt @@ -16,29 +16,12 @@ package im.vector.matrix.android.internal.session.room.membership -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.database.query.findIncludingEvent -import im.vector.matrix.android.internal.database.query.next -import im.vector.matrix.android.internal.database.query.prev -import im.vector.matrix.android.internal.database.query.where -import io.realm.Realm -import io.realm.RealmList -import io.realm.RealmQuery -import javax.inject.Inject +internal object SenderRoomMemberExtractor { -internal class SenderRoomMemberExtractor @Inject constructor() { - - fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? { + /* + fun extractFrom(event: Event, realm: Realm): RoomMember? { val roomId = event.roomId - val sender = event.sender ?: return null + val sender = event.senderId ?: return null // If the event is unlinked we want to fetch unlinked state events val unlinked = event.isUnlinked val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return null @@ -69,4 +52,5 @@ internal class SenderRoomMemberExtractor @Inject constructor() { .equalTo(EventEntityFields.IS_UNLINKED, isUnlinked) } +*/ } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index 7b90a8ac..ff899968 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -21,8 +21,8 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.latestEvent @@ -55,21 +55,21 @@ internal class DefaultReadService @Inject constructor(private val roomId: String setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor) } - private fun getLatestEvent(): EventEntity? { - return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) } + private fun getLatestEvent(): TimelineEventEntity? { + return monarchy.fetchCopied { TimelineEventEntity.latestEvent(it, roomId) } } override fun isEventRead(eventId: String): Boolean { var isEventRead = false monarchy.doWithRealm { val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() - ?: return@doWithRealm + ?: return@doWithRealm val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) - ?: return@doWithRealm - val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex - ?: Int.MIN_VALUE - val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex - ?: Int.MAX_VALUE + ?: return@doWithRealm + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex + ?: Int.MIN_VALUE + val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex + ?: Int.MAX_VALUE isEventRead = eventToCheckIndex <= readReceiptIndex } return isEventRead diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index 7ff2d009..2106ab55 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.latestEvent @@ -82,7 +83,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) { monarchy.tryTransactionAsync { realm -> - val isLatestReceived = EventEntity.latestEvent(realm, roomId)?.eventId == eventId + val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId)?.eventId == eventId if (isLatestReceived) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() ?: return@tryTransactionAsync @@ -99,9 +100,9 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI ?: return@doWithRealm val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) ?: return@doWithRealm - val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex ?: Int.MIN_VALUE - val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex + val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex ?: Int.MAX_VALUE isEventRead = eventToCheckIndex <= readReceiptIndex } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 8f5c62cd..44d1808e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -67,7 +67,6 @@ internal class DefaultTimeline( private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, - private val timelineEventFactory: CacheableTimelineEventFactory, private val paginationTask: PaginationTask, private val cryptoService: CryptoService, private val allowedTypes: List? @@ -116,7 +115,6 @@ internal class DefaultTimeline( nextDisplayIndex = DISPLAY_INDEX_UNKNOWN builtEvents.clear() builtEventsIdMap.clear() - timelineEventFactory.clear() } changeSet.insertionRanges.forEach { range -> val (startDisplayIndex, direction) = if (range.startIndex == 0) { @@ -145,7 +143,7 @@ internal class DefaultTimeline( builtEventsIdMap[eventId]?.let { builtIndex -> //Update the relation of existing event builtEvents[builtIndex]?.let { te -> - builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm) + //builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm) hasChanged = true } } @@ -189,7 +187,7 @@ internal class DefaultTimeline( && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { //we need to rebuild this event EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { - builtEvents[index] = timelineEventFactory.create(it, realm) + //builtEvents[index] = timelineEventFactory.create(it, realm) hasChange = true } } @@ -326,10 +324,10 @@ internal class DefaultTimeline( val sendingEvents = ArrayList() if (hasReachedEnd(Timeline.Direction.FORWARDS)) { roomEntity?.sendingTimelineEvents - ?.filter { allowedTypes?.contains(it.type) ?: false } + ?.filter { allowedTypes?.contains(it.root?.type) ?: false } ?.forEach { - val timelineEvent = timelineEventFactory.create(it, it.realm) - sendingEvents.add(timelineEvent) + //val timelineEvent = timelineEventFactory.create(it, it.realm) + //sendingEvents.add(timelineEvent) } } return sendingEvents @@ -432,7 +430,7 @@ internal class DefaultTimeline( * This has to be called on TimelineThread as it access realm live results */ private fun getLiveChunk(): ChunkEntity? { - return liveEvents.firstOrNull()?.chunk?.firstOrNull() + return null //return liveEvents.firstOrNull()?.chunk?.firstOrNull() } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 1d3462e2..d70f1b92 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -17,15 +17,15 @@ package im.vector.matrix.android.internal.session.room.timeline import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.database.RealmLiveData -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity -import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.fetchCopyMap @@ -34,7 +34,6 @@ import javax.inject.Inject internal class DefaultTimelineService @Inject constructor(private val roomId: String, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor, - private val timelineEventFactory: CacheableTimelineEventFactory, private val contextOfEventTask: GetContextOfEventTask, private val cryptoService: CryptoService, private val paginationTask: PaginationTask @@ -42,46 +41,31 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St override fun createTimeline(eventId: String?, allowedTypes: List?): Timeline { return DefaultTimeline(roomId, - eventId, - monarchy.realmConfiguration, - taskExecutor, - contextOfEventTask, - timelineEventFactory, - paginationTask, - cryptoService, - allowedTypes) + eventId, + monarchy.realmConfiguration, + taskExecutor, + contextOfEventTask, + paginationTask, + cryptoService, + allowedTypes) } override fun getTimeLineEvent(eventId: String): TimelineEvent? { return monarchy .fetchCopyMap({ - EventEntity.where(it, eventId = eventId).findFirst() - }, { entity, realm -> - timelineEventFactory.create(entity, realm) - }) + TimelineEventEntity.where(it, eventId = eventId).findFirst() + }, { entity, realm -> + entity.asDomain() + }) } override fun liveTimeLineEvent(eventId: String): LiveData { - val liveEventEntity = RealmLiveData(monarchy.realmConfiguration) { realm -> - EventEntity.where(realm, eventId = eventId) + val liveData = RealmLiveData(monarchy.realmConfiguration) { + TimelineEventEntity.where(it, eventId = eventId) } - val liveAnnotationsEntity = RealmLiveData(monarchy.realmConfiguration) { realm -> - EventAnnotationsSummaryEntity.where(realm, eventId = eventId) + return Transformations.map(liveData) { + it.firstOrNull()?.asDomain() } - val result = MediatorLiveData() - result.addSource(liveEventEntity) { realmResults -> - result.value = realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) } - } - - result.addSource(liveAnnotationsEntity) { - liveEventEntity.value?.let { - result.value = liveEventEntity.value?.let { realmResults -> - //recreate the timeline event - realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) } - } - } - } - return result } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt deleted file mode 100644 index aeb35e10..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2019 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.session.room.timeline - -import im.vector.matrix.android.api.session.crypto.CryptoService -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.matrix.android.api.session.room.timeline.TimelineService -import im.vector.matrix.android.internal.crypto.MXDecryptionException -import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult -import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.session.room.EventRelationExtractor -import im.vector.matrix.android.internal.session.room.membership.RoomMembers -import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor -import io.realm.Realm -import timber.log.Timber -import java.util.* -import javax.inject.Inject - -internal interface TimelineEventFactory { - fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent -} - -internal interface CacheableTimelineEventFactory : TimelineEventFactory { - fun clear() -} - -/** - * This class is responsible for building [TimelineEvent] returned by a [Timeline] through [TimelineService] - * It handles decryption, extracting additional data around an event as sender data and relation. - */ -internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor, - private val relationExtractor: EventRelationExtractor, - private val cryptoService: CryptoService -) : TimelineEventFactory { - - override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent { - val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) - val relations = relationExtractor.extractFrom(eventEntity, realm) - - val event = eventEntity.asDomain() - if (event.getClearType() == EventType.ENCRYPTED) { - handleEncryptedEvent(event) - } - - val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName) - - return TimelineEvent( - event, - eventEntity.localId, - eventEntity.displayIndex, - senderRoomMember?.displayName, - isUniqueDisplayName, - senderRoomMember?.avatarUrl, - eventEntity.sendState, - event.mClearEvent != null, - relations - ) - } - - private fun handleEncryptedEvent(event: Event) { - Timber.v("Encrypted event: try to decrypt ${event.eventId}") - try { - val result = cryptoService.decryptEvent(event, UUID.randomUUID().toString()) - event.setClearData(result) - } catch (failure: Throwable) { - Timber.e("Encrypted event: decryption failed") - if (failure is MXDecryptionException) { - event.setCryptoError(failure.cryptoError) - } - } - } -} - -internal class InMemoryTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor, - private val relationExtractor: EventRelationExtractor, - private val cryptoService: CryptoService) : CacheableTimelineEventFactory { - - private val timelineId = UUID.randomUUID().toString() - private val senderCache = mutableMapOf() - private val decryptionCache = mutableMapOf() - - override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent { - val sender = eventEntity.sender - val cacheKey = sender + eventEntity.localId - val senderData = senderCache.getOrPut(cacheKey) { - val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) - val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName) - - SenderData(senderRoomMember?.displayName, - isUniqueDisplayName, - senderRoomMember?.avatarUrl) - } - val event = eventEntity.asDomain() - if (event.getClearType() == EventType.ENCRYPTED && !event.isRedacted()) { - handleEncryptedEvent(event, eventEntity.localId) - } - - val relations = relationExtractor.extractFrom(eventEntity, realm) - return TimelineEvent( - event, - eventEntity.localId, - eventEntity.displayIndex, - senderData.senderName, - senderData.isUniqueDisplayName, - senderData.senderAvatar, - eventEntity.sendState, - event.mClearEvent != null, - relations - ) - } - - private fun handleEncryptedEvent(event: Event, cacheKey: String) { - Timber.v("Encrypted event: try to decrypt ${event.eventId}") - val cachedDecryption = decryptionCache[cacheKey] - if (cachedDecryption != null) { - Timber.v("Encrypted event ${event.eventId} cached") - event.setClearData(cachedDecryption) - } else { - try { - val result = cryptoService.decryptEvent(event, timelineId) - if (result != null) { - decryptionCache[cacheKey] = result - } - event.setClearData(result) - } catch (failure: Throwable) { - Timber.e("Encrypted event: decryption failed ${failure.localizedMessage}") - if (failure is MXDecryptionException) { - event.setCryptoError(failure.cryptoError) - } else { - // Other error - Timber.e("Other error, should be handled") - } - } - } - } - - override fun clear() { - senderCache.clear() - decryptionCache.clear() - } - - private data class SenderData( - val senderName: String?, - val isUniqueDisplayName: Boolean, - val senderAvatar: String? - ) -} \ No newline at end of file From f01e7962711986d17efe8ae835f9be52c7d6c294 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 5 Jul 2019 17:00:13 +0200 Subject: [PATCH 03/22] Timeline is back --- .../database/helper/ChunkEntityHelper.kt | 16 +++-- .../query/TimelineEventEntityQueries.kt | 22 ++++++- .../session/room/prune/EventsPruner.kt | 3 +- .../session/room/timeline/DefaultTimeline.kt | 64 +++++++++---------- .../session/user/UserEntityUpdater.kt | 3 +- 5 files changed, 67 insertions(+), 41 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index f8ccc5f9..b74fcdcf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity @@ -112,11 +113,16 @@ internal fun ChunkEntity.add(roomId: String, } } - val eventEntity = TimelineEventEntity().apply { - this.root = event.toEntity(roomId) - this.eventId = event.eventId ?: "" - this.roomId = roomId - this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + val eventEntity = TimelineEventEntity().also { + it.root = event.toEntity(roomId).apply { + this.stateIndex = currentStateIndex + this.isUnlinked = isUnlinked + this.displayIndex = currentDisplayIndex + this.sendState = SendState.SYNCED + } + it.eventId = event.eventId ?: "" + it.roomId = roomId + it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst() } val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size timelineEvents.add(position, eventEntity) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 28b2bc01..5367603c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -37,6 +37,24 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, eventIds: List().`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray()) } +internal fun TimelineEventEntity.Companion.where(realm: Realm, + roomId: String? = null, + type: String? = null, + linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + } + if (type != null) { + query.equalTo(TimelineEventEntityFields.ROOT.TYPE, type) + } + return when (linkFilterMode) { + LINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false) + UNLINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, true) + BOTH -> query + } +} + internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, roomId: String, @@ -51,9 +69,9 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, } val query = eventList?.where() if (includedTypes.isNotEmpty()) { - query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) + query?.`in`(TimelineEventEntityFields.ROOT.TYPE, includedTypes.toTypedArray()) } else if (excludedTypes.isNotEmpty()) { - query?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray()) + query?.not()?.`in`(TimelineEventEntityFields.ROOT.TYPE, excludedTypes.toTypedArray()) } return query ?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index 53b72fa4..63033287 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.session.SessionScope @@ -41,7 +42,7 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat private val taskExecutor: TaskExecutor) : RealmLiveEntityObserver(realmConfiguration) { - override val query = Monarchy.Query { EventEntity.where(it, type = EventType.REDACTION) } + override val query = Monarchy.Query { EventEntity.types(it, listOf(EventType.REDACTION)) } override fun processChanges(inserted: List, updated: List, deleted: List) { Timber.v("Event pruner called with ${inserted.size} insertions") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 44d1808e..fc0eb2bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -27,12 +27,12 @@ import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.* 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.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields 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.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where @@ -91,7 +91,7 @@ internal class DefaultTimeline( private val cancelableBag = CancelableBag() private val debouncer = Debouncer(mainHandler) - private lateinit var liveEvents: RealmResults + private lateinit var liveEvents: RealmResults private var roomEntity: RoomEntity? = null private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN @@ -105,7 +105,7 @@ internal class DefaultTimeline( private lateinit var eventRelations: RealmResults - private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> + private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { handleInitialLoad() } else { @@ -118,9 +118,9 @@ internal class DefaultTimeline( } changeSet.insertionRanges.forEach { range -> val (startDisplayIndex, direction) = if (range.startIndex == 0) { - Pair(liveEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS) + Pair(liveEvents[range.length - 1]!!.root!!.displayIndex, Timeline.Direction.FORWARDS) } else { - Pair(liveEvents[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS) + Pair(liveEvents[range.startIndex]!!.root!!.displayIndex, Timeline.Direction.BACKWARDS) } val state = getPaginationState(direction) if (state.isPaginating) { @@ -233,7 +233,7 @@ internal class DefaultTimeline( } liveEvents = buildEventQuery(realm) - .sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) .findAllAsync() .also { it.addChangeListener(eventsChangeListener) } @@ -268,13 +268,13 @@ internal class DefaultTimeline( private fun hasMoreInCache(direction: Timeline.Direction): Boolean { val localRealm = Realm.getInstance(realmConfiguration) - val eventEntity = buildEventQuery(localRealm).findFirst(direction) ?: return false + val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction) ?: return false val hasMoreInCache = if (direction == Timeline.Direction.FORWARDS) { val firstEvent = builtEvents.firstOrNull() ?: return true - firstEvent.displayIndex < eventEntity.displayIndex + firstEvent.displayIndex < timelineEventEntity.root!!.displayIndex } else { val lastEvent = builtEvents.lastOrNull() ?: return true - lastEvent.displayIndex > eventEntity.displayIndex + lastEvent.displayIndex > timelineEventEntity.root!!.displayIndex } localRealm.close() return hasMoreInCache @@ -287,7 +287,7 @@ internal class DefaultTimeline( currentChunk.isLastForward } else { val eventEntity = buildEventQuery(localRealm).findFirst(direction) - currentChunk.isLastBackward || eventEntity?.type == EventType.STATE_ROOM_CREATE + currentChunk.isLastBackward || eventEntity?.root?.type == EventType.STATE_ROOM_CREATE } localRealm.close() return hasReachedEnd @@ -360,11 +360,11 @@ internal class DefaultTimeline( private fun handleInitialLoad() { var shouldFetchInitialEvent = false val initialDisplayIndex = if (isLive) { - liveEvents.firstOrNull()?.displayIndex + liveEvents.firstOrNull()?.root?.displayIndex } else { - val initialEvent = liveEvents.where().equalTo(EventEntityFields.EVENT_ID, initialEventId).findFirst() + val initialEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst() shouldFetchInitialEvent = initialEvent == null - initialEvent?.displayIndex + initialEvent?.root?.displayIndex } ?: DISPLAY_INDEX_UNKNOWN prevDisplayIndex = initialDisplayIndex @@ -448,14 +448,14 @@ internal class DefaultTimeline( if (offsetResults.isEmpty()) { return 0 } - val offsetIndex = offsetResults.last()!!.displayIndex + val offsetIndex = offsetResults.last()!!.root!!.displayIndex if (direction == Timeline.Direction.BACKWARDS) { prevDisplayIndex = offsetIndex - 1 } else { nextDisplayIndex = offsetIndex + 1 } offsetResults.forEach { eventEntity -> - val timelineEvent = timelineEventFactory.create(eventEntity, eventEntity.realm) + val timelineEvent = eventEntity.asDomain() val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size builtEvents.add(position, timelineEvent) //Need to shift :/ @@ -472,16 +472,16 @@ internal class DefaultTimeline( */ private fun getOffsetResults(startDisplayIndex: Int, direction: Timeline.Direction, - count: Long): RealmResults { + count: Long): RealmResults { val offsetQuery = liveEvents.where() if (direction == Timeline.Direction.BACKWARDS) { offsetQuery - .sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .lessThanOrEqualTo(EventEntityFields.DISPLAY_INDEX, startDisplayIndex) + .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + .lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) } else { offsetQuery - .sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) - .greaterThanOrEqualTo(EventEntityFields.DISPLAY_INDEX, startDisplayIndex) + .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) + .greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) } return offsetQuery .filterAllowedTypes() @@ -490,15 +490,15 @@ internal class DefaultTimeline( } - private fun buildEventQuery(realm: Realm): RealmQuery { + private fun buildEventQuery(realm: Realm): RealmQuery { return if (initialEventId == null) { - EventEntity + TimelineEventEntity .where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY) - .equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true) + .equalTo("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true) } else { - EventEntity + TimelineEventEntity .where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH) - .`in`("${EventEntityFields.CHUNK}.${ChunkEntityFields.EVENTS.EVENT_ID}", arrayOf(initialEventId)) + .`in`("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID}", arrayOf(initialEventId)) } } @@ -514,7 +514,7 @@ internal class DefaultTimeline( realm.executeTransaction { val unlinkedChunks = ChunkEntity .where(it, roomId = roomId) - .equalTo(ChunkEntityFields.EVENTS.IS_UNLINKED, true) + .equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true) .findAll() unlinkedChunks.deleteAllFromRealm() } @@ -537,19 +537,19 @@ internal class DefaultTimeline( return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS } - private fun RealmQuery.findFirst(direction: Timeline.Direction): EventEntity? { + private fun RealmQuery.findFirst(direction: Timeline.Direction): TimelineEventEntity? { return if (direction == Timeline.Direction.FORWARDS) { - sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } else { - sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) } .filterAllowedTypes() .findFirst() } - private fun RealmQuery.filterAllowedTypes(): RealmQuery { + private fun RealmQuery.filterAllowedTypes(): RealmQuery { if (allowedTypes != null) { - `in`(EventEntityFields.TYPE, allowedTypes.toTypedArray()) + `in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray()) } return this } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt index bfb9786d..254381f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.session.SessionScope @@ -38,7 +39,7 @@ internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfi override val query = Monarchy.Query { EventEntity - .where(it, type = EventType.STATE_ROOM_MEMBER) + .types(it, listOf(EventType.STATE_ROOM_MEMBER)) .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) .distinct(EventEntityFields.STATE_KEY) From 79a704d2406c04756962b6139ae30a13dbe9b7e7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jul 2019 17:27:24 +0200 Subject: [PATCH 04/22] Timeline : Uncomment liveChunk to make pagination working --- .../android/internal/session/room/timeline/DefaultTimeline.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index fc0eb2bc..ef23cec0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -430,7 +430,7 @@ internal class DefaultTimeline( * This has to be called on TimelineThread as it access realm live results */ private fun getLiveChunk(): ChunkEntity? { - return null //return liveEvents.firstOrNull()?.chunk?.firstOrNull() + return liveEvents.firstOrNull()?.chunk?.firstOrNull() } /** From c503445092e0e452fd6d18b8d5c44461934b2aef Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 5 Jul 2019 18:38:20 +0200 Subject: [PATCH 05/22] Branch back relation summaries --- .../database/query/EventAnnotationsSummaryEntityQuery.kt | 5 +++++ .../internal/session/room/EventRelationsAggregationTask.kt | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index b8fd3c89..f1179ebe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntityFields +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where @@ -39,5 +40,9 @@ internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, r internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, eventId: String): EventAnnotationsSummaryEntity { val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId) + //Denormalization + TimelineEventEntity.where(realm, eventId = eventId).findFirst()?.let { + it.annotations = obj + } return obj } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index dd4b59fc..b55a7e14 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -86,6 +86,13 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(private } } + EventAnnotationsSummaryEntity.where(realm, event.eventId ?: "").findFirst()?.let { + TimelineEventEntity.where(realm,eventId = event.eventId ?: "").findFirst()?.let { tet -> + tet.annotations = it + } + } + + } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } From 78951b91554ff1a9e7b4b8a6a6a2c465e3050653 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jul 2019 19:07:33 +0200 Subject: [PATCH 06/22] Timeline event: handle displayName/avatar [WIP] --- .../database/helper/ChunkEntityHelper.kt | 64 ++++++++++++++++--- .../database/helper/RoomEntityHelper.kt | 7 ++ .../database/query/EventEntityQueries.kt | 4 -- .../query/TimelineEventEntityQueries.kt | 34 ++++++++-- .../internal/session/room/RoomFactory.kt | 1 - .../membership/SenderRoomMemberExtractor.kt | 56 ---------------- 6 files changed, 92 insertions(+), 74 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index b74fcdcf..d5979723 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -18,17 +18,28 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields +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.database.query.find +import im.vector.matrix.android.internal.database.query.next +import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection +import io.realm.RealmList +import io.realm.RealmQuery import io.realm.Sort // By default if a chunk is empty we consider it unlinked @@ -64,11 +75,11 @@ internal fun ChunkEntity.merge(roomId: String, this.isLastBackward = chunkToMerge.isLastBackward eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } - eventsToMerge.forEach { - it.root?.let { root -> - add(roomId, root.asDomain(), direction, isUnlinked = isUnlinked) - } + val events = eventsToMerge.mapNotNull { it.root?.asDomain() } + events.forEach { event -> + add(roomId, event, direction, isUnlinked = isUnlinked) } + updateSenderDataFor(roomId, isUnlinked, events) } internal fun ChunkEntity.addAll(roomId: String, @@ -81,13 +92,35 @@ internal fun ChunkEntity.addAll(roomId: String, events.forEach { event -> add(roomId, event, direction, stateIndexOffset, isUnlinked) } + updateSenderDataFor(roomId, isUnlinked, events) } -internal fun ChunkEntity.add(roomId: String, - event: Event, - direction: PaginationDirection, - stateIndexOffset: Int = 0, - isUnlinked: Boolean = false) { +private fun ChunkEntity.updateSenderDataFor(roomId: String, isUnlinked: Boolean, events: List) { + for (event in events) { + val eventId = event.eventId ?: continue + val timelineEventEntity = timelineEvents.find(eventId) ?: continue + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: continue + val stateIndex = timelineEventEntity.root?.stateIndex ?: continue + val senderId = timelineEventEntity.root?.sender ?: continue + + val senderRoomMemberContent = when { + stateIndex <= 0 -> timelineEvents.build(senderId, isUnlinked).next(from = stateIndex)?.root?.prevContent + else -> timelineEvents.build(senderId, isUnlinked).prev(since = stateIndex)?.root?.content + } + val fallbackContent = senderRoomMemberContent + ?: roomEntity.untimelinedStateEvents.build(senderId).prev(since = stateIndex)?.content + val senderRoomMember: RoomMember? = ContentMapper.map(fallbackContent).toModel() + timelineEventEntity.senderAvatar = senderRoomMember?.avatarUrl + timelineEventEntity.senderName = senderRoomMember?.displayName + timelineEventEntity.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName) + } +} + +private fun ChunkEntity.add(roomId: String, + event: Event, + direction: PaginationDirection, + stateIndexOffset: Int = 0, + isUnlinked: Boolean = false) { assertIsManaged() if (event.eventId != null && timelineEvents.find(event.eventId) != null) { @@ -128,6 +161,19 @@ internal fun ChunkEntity.add(roomId: String, timelineEvents.add(position, eventEntity) } +private fun RealmList.build(sender: String, isUnlinked: Boolean): RealmQuery { + return where() + .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) + .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) + .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) +} + +private fun RealmList.build(sender: String): RealmQuery { + return where() + .equalTo(EventEntityFields.STATE_KEY, sender) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) +} + internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { PaginationDirection.FORWARDS -> forwardsDisplayIndex diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 885b25c7..f694953e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -24,6 +24,7 @@ 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.query.fastContains import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.room.membership.RoomMembers internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { @@ -58,13 +59,19 @@ internal fun RoomEntity.addStateEvents(stateEvents: List, internal fun RoomEntity.addSendingEvent(event: Event) { assertIsManaged() + val senderId = event.senderId ?: return val eventEntity = event.toEntity(roomId).apply { this.sendState = SendState.UNSENT } + val roomMembers = RoomMembers(realm, roomId) + val myUser = roomMembers.get(senderId) val timelineEventEntity = TimelineEventEntity().also { it.root = eventEntity it.eventId = event.eventId ?: "" it.roomId = roomId + it.senderName = myUser?.displayName + it.senderAvatar = myUser?.avatarUrl + it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName) } sendingTimelineEvents.add(0, timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index cf0f845b..4327b6ee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -16,13 +16,9 @@ package im.vector.matrix.android.internal.database.query -import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields -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 io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 5367603c..620f47be 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* -import im.vector.matrix.android.internal.database.model.EventEntityFields 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 @@ -38,9 +37,9 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, eventIds: List { + roomId: String? = null, + type: String? = null, + linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery { val query = realm.where() if (roomId != null) { query.equalTo(TimelineEventEntityFields.ROOM_ID, roomId) @@ -78,6 +77,33 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, ?.findFirst() } +internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): TimelineEventEntity? { + if (from != null) { + if (strict) { + this.greaterThan(TimelineEventEntityFields.ROOT.STATE_INDEX, from) + } else { + this.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, from) + } + } + return this + .sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.ASCENDING) + .findFirst() +} + +internal fun RealmQuery.prev(since: Int? = null, strict: Boolean = false): TimelineEventEntity? { + if (since != null) { + if (strict) { + this.lessThan(TimelineEventEntityFields.ROOT.STATE_INDEX, since) + } else { + this.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, since) + } + } + return this + .sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.DESCENDING) + .findFirst() +} + + internal fun RealmList.find(eventId: String): TimelineEventEntity? { return this.where().equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, eventId).findFirst() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 13ca4e67..dddcd37f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -24,7 +24,6 @@ import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask -import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt deleted file mode 100644 index f2629f4d..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2019 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.session.room.membership - -internal object SenderRoomMemberExtractor { - - /* - fun extractFrom(event: Event, realm: Realm): RoomMember? { - val roomId = event.roomId - val sender = event.senderId ?: return null - // If the event is unlinked we want to fetch unlinked state events - val unlinked = event.isUnlinked - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return null - - // When not synced, we should grab the live RoomMember event - return if (event.sendState != SendState.SYNCED) { - RoomMembers(realm, roomId).get(sender) - } else { - val chunkEntity = ChunkEntity.findIncludingEvent(realm, event.eventId) - val content = when { - chunkEntity == null -> null - event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent - else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content - } - val fallbackContent = content - ?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content - ContentMapper.map(fallbackContent).toModel() - } - } - - private fun baseQuery(list: RealmList, - sender: String, - isUnlinked: Boolean): RealmQuery { - return list - .where() - .equalTo(EventEntityFields.STATE_KEY, sender) - .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) - .equalTo(EventEntityFields.IS_UNLINKED, isUnlinked) - } - -*/ -} \ No newline at end of file From 98a7652403763a7d8ed76b8a46413872e12d8645 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 5 Jul 2019 19:13:34 +0200 Subject: [PATCH 07/22] Put back local echo --- .../android/internal/session/room/timeline/DefaultTimeline.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index ef23cec0..a291dfdc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -326,8 +326,7 @@ internal class DefaultTimeline( roomEntity?.sendingTimelineEvents ?.filter { allowedTypes?.contains(it.root?.type) ?: false } ?.forEach { - //val timelineEvent = timelineEventFactory.create(it, it.realm) - //sendingEvents.add(timelineEvent) + sendingEvents.add(it.asDomain()) } } return sendingEvents From e50dd265d4c7205916dc4404a6eb7b82c2039cb5 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 10:58:41 +0200 Subject: [PATCH 08/22] merge develop --- .../android/api/session/events/model/Event.kt | 203 +++++++++--------- .../session/room/timeline/TimelineEvent.kt | 3 +- .../internal/database/mapper/EventMapper.kt | 16 +- .../database/mapper/TimelineEventMapper.kt | 9 +- .../internal/database/model/EventEntity.kt | 18 +- .../database/model/TimelineEventEntity.kt | 4 + .../session/room/timeline/DefaultTimeline.kt | 88 ++++---- .../room/timeline/TimelineEventDecryptor.kt | 133 ++++++++++++ .../session/sync/CryptoSyncHandler.kt | 16 +- .../timeline/action/MessageMenuViewModel.kt | 2 +- 10 files changed, 341 insertions(+), 151 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index f02b7d31..a79b306b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -20,12 +20,11 @@ import android.text.TextUtils import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.util.JsonDict -import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider import timber.log.Timber -import java.util.* -import kotlin.collections.HashMap typealias Content = JsonDict @@ -79,6 +78,10 @@ data class Event( @Json(name = "redacts") val redacts: String? = null ) { + + var mxDecryptionResult: MXDecryptionResult? = null + var mCryptoError: MXCryptoError? = null + /** * Check if event is a state event. * @return true if event is state event. @@ -91,41 +94,41 @@ data class Event( // Crypto //============================================================================================================== - /** - * For encrypted events, the plaintext payload for the event. - * This is a small MXEvent instance with typically value for `type` and 'content' fields. - */ - @Transient - var mClearEvent: Event? = null - private set - - /** - * Curve25519 key which we believe belongs to the sender of the event. - * See `senderKey` property. - */ - @Transient - private var mSenderCurve25519Key: String? = null - - /** - * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own. - * See `claimedEd25519Key` property. - */ - @Transient - private var mClaimedEd25519Key: String? = null - - /** - * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. - * See `forwardingCurve25519KeyChain` property. - */ - @Transient - private var mForwardingCurve25519KeyChain: List = ArrayList() - - /** - * Decryption error - */ - @Transient - var mCryptoError: MXCryptoError? = null - private set +// /** +// * For encrypted events, the plaintext payload for the event. +// * This is a small MXEvent instance with typically value for `type` and 'content' fields. +// */ +// @Transient +// var mClearEvent: Event? = null +// private set +// +// /** +// * Curve25519 key which we believe belongs to the sender of the event. +// * See `senderKey` property. +// */ +// @Transient +// private var mSenderCurve25519Key: String? = null +// +// /** +// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own. +// * See `claimedEd25519Key` property. +// */ +// @Transient +// private var mClaimedEd25519Key: String? = null +// +// /** +// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. +// * See `forwardingCurve25519KeyChain` property. +// */ +// @Transient +// private var mForwardingCurve25519KeyChain: List = ArrayList() +// +// /** +// * Decryption error +// */ +// @Transient +// var mCryptoError: MXCryptoError? = null +// private set /** * @return true if this event is encrypted. @@ -140,88 +143,96 @@ data class Event( * * @param decryptionResult the decryption result, including the plaintext and some key info. */ - internal fun setClearData(decryptionResult: MXEventDecryptionResult) { - mClearEvent = null - mCryptoError = null - - val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) - mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) - - if (mClearEvent != null) { - mSenderCurve25519Key = decryptionResult.senderCurve25519Key - mClaimedEd25519Key = decryptionResult.claimedEd25519Key - mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain - - // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back - // in the clear event - try { - content?.get("m.relates_to")?.let { clearRelates -> - mClearEvent = mClearEvent?.copy( - content = HashMap(mClearEvent!!.content).apply { - this["m.relates_to"] = clearRelates - } - ) - } - } catch (e: Exception) { - Timber.e(e, "Unable to restore 'm.relates_to' the clear event") - } - } - } +// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) { +// mClearEvent = null +// if (decryptionResult != null) { +// if (decryptionResult.clearEvent != null) { +// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) +// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) +// +// if (mClearEvent != null) { +// mSenderCurve25519Key = decryptionResult.senderCurve25519Key +// mClaimedEd25519Key = decryptionResult.claimedEd25519Key +// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain +// +// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back +// // in the clear event +// try { +// content?.get("m.relates_to")?.let { clearRelates -> +// mClearEvent = mClearEvent?.copy( +// content = HashMap(mClearEvent!!.content).apply { +// this["m.relates_to"] = clearRelates +// } +// ) +// } +// } catch (e: Exception) { +// Timber.e(e, "Unable to restore 'm.relates_to' the clear event") +// } +// } +// +// +// } +// } +// mCryptoError = null +// } /** * @return The curve25519 key that sent this event. */ fun getSenderKey(): String? { - return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key + return mxDecryptionResult?.senderKey + // return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key } /** * @return The additional keys the sender of this encrypted event claims to possess. */ fun getKeysClaimed(): Map { - val res = HashMap() - - val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key - - if (null != claimedEd25519Key) { - res["ed25519"] = claimedEd25519Key - } - - return res + return mxDecryptionResult?.keysClaimed ?: HashMap() +// val res = HashMap() +// +// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key +// +// if (null != claimedEd25519Key) { +// res["ed25519"] = claimedEd25519Key +// } +// +// return res } - +// /** * @return the event type */ fun getClearType(): String { - return mClearEvent?.type ?: type + return mxDecryptionResult?.payload?.get("type")?.toString() + ?: type//get("type")?.toString() ?: type } /** * @return the event content */ fun getClearContent(): Content? { - return mClearEvent?.content ?: content + return mxDecryptionResult?.payload?.get("content") as? Content ?: content } - /** - * @return the linked crypto error - */ - fun getCryptoError(): MXCryptoError? { - return mCryptoError - } - - /** - * Update the linked crypto error - * - * @param error the new crypto error. - */ - fun setCryptoError(error: MXCryptoError?) { - mCryptoError = error - if (null != error) { - mClearEvent = null - } - } +// /** +// * @return the linked crypto error +// */ +// fun getCryptoError(): MXCryptoError? { +// return mCryptoError +// } +// +// /** +// * Update the linked crypto error +// * +// * @param error the new crypto error. +// */ +// fun setCryptoError(error: MXCryptoError?) { +// mCryptoError = error +// if (null != error) { +// mClearEvent = null +// } +// } /** * Tells if the event is redacted diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 14b9e334..7dc7083d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.room.timeline +import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary @@ -34,7 +35,7 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, - val hasClearEventFlag: Boolean = false, +// val hasClearEventFlag: Boolean = false, val annotations: EventAnnotationsSummary? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index 17df08e5..fcd6a848 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -17,10 +17,13 @@ package im.vector.matrix.android.internal.database.mapper import com.squareup.moshi.JsonDataException +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.di.MoshiProvider +import timber.log.Timber import java.util.* internal object EventMapper { @@ -46,7 +49,6 @@ internal object EventMapper { } fun map(eventEntity: EventEntity): Event { - //TODO proxy the event to only parse unsigned data when accessed? val ud = if (eventEntity.unsignedData.isNullOrBlank()) { null } else { @@ -68,7 +70,17 @@ internal object EventMapper { roomId = eventEntity.roomId, unsignedData = ud, redacts = eventEntity.redacts - ) + ).also { + eventEntity.decryptionResultJson?.let { json -> + try { + it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java).fromJson(json) + } catch (t: JsonDataException) { + Timber.e(t, "Failed to parse decryption result") + } + } + //TODO get the full crypto error object + it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError(it, it) } + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 27d4a11b..72057b39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -16,10 +16,12 @@ package im.vector.matrix.android.internal.database.mapper +import com.squareup.moshi.Types import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.di.MoshiProvider internal object TimelineEventMapper { @@ -33,17 +35,18 @@ internal object TimelineEventMapper { } fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { + val listMapper = MoshiProvider.providesMoshi().adapter>(Types.newParameterizedType(List::class.java, String::class.java)) + return TimelineEvent( root = timelineEventEntity.root?.asDomain() - ?: Event("", timelineEventEntity.eventId), + ?: Event("", timelineEventEntity.eventId), annotations = timelineEventEntity.annotations?.asDomain(), localId = timelineEventEntity.localId, displayIndex = timelineEventEntity.root?.displayIndex ?: 0, senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, - sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, - hasClearEventFlag = false + sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 91375a96..edb6bf61 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -17,7 +17,9 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index @@ -39,7 +41,9 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI var redacts: String? = null, @Index var stateIndex: Int = 0, @Index var displayIndex: Int = 0, - @Index var isUnlinked: Boolean = false + @Index var isUnlinked: Boolean = false, + var decryptionResultJson: String? = null, + var decryptionErrorCode: String? = null ) : RealmObject() { enum class LinkFilterMode { @@ -68,4 +72,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI val timelineEventEntity: RealmResults? = null + fun setDecryptionResult(result: MXEventDecryptionResult) { + val decryptionResult = MXDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + val adapter = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java) + decryptionResultJson = adapter.toJson(decryptionResult) + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index bb910aec..8c42e4c9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -16,6 +16,10 @@ package im.vector.matrix.android.internal.database.model +import com.squareup.moshi.Types +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 542270c3..18664de6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -19,19 +19,15 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.CancelableBag +import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity 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.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where @@ -41,13 +37,7 @@ import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createUIHandler -import io.realm.OrderedCollectionChangeSet -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.RealmQuery -import io.realm.RealmResults -import io.realm.Sort +import io.realm.* import timber.log.Timber import java.util.* import java.util.concurrent.atomic.AtomicBoolean @@ -67,7 +57,7 @@ internal class DefaultTimeline( private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, private val paginationTask: PaginationTask, - private val cryptoService: CryptoService, + cryptoService: CryptoService, private val allowedTypes: List? ) : Timeline { @@ -102,8 +92,12 @@ internal class DefaultTimeline( private val forwardsPaginationState = AtomicReference(PaginationState()) + private val timelineID = UUID.randomUUID().toString() + private lateinit var eventRelations: RealmResults + private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService) + private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { handleInitialLoad() @@ -172,32 +166,32 @@ internal class DefaultTimeline( postSnapshot() } - private val newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { - if (roomId == this@DefaultTimeline.roomId) { - Timber.v("New session id detected for this room") - BACKGROUND_HANDLER.post { - val realm = backgroundRealm.get() - var hasChange = false - builtEvents.forEachIndexed { index, timelineEvent -> - if (timelineEvent.isEncrypted()) { - val eventContent = timelineEvent.root.content.toModel() - if (eventContent?.sessionId == sessionId - && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { - //we need to rebuild this event - EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { - //builtEvents[index] = timelineEventFactory.create(it, realm) - hasChange = true - } - } - } - } - if (hasChange) postSnapshot() - } - } - } - - } +// private val newSessionListener = object : NewSessionListener { +// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { +// if (roomId == this@DefaultTimeline.roomId) { +// Timber.v("New session id detected for this room") +// BACKGROUND_HANDLER.post { +// val realm = backgroundRealm.get() +// var hasChange = false +// builtEvents.forEachIndexed { index, timelineEvent -> +// if (timelineEvent.isEncrypted()) { +// val eventContent = timelineEvent.root.content.toModel() +// if (eventContent?.sessionId == sessionId +// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { +// //we need to rebuild this event +// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { +// //builtEvents[index] = timelineEventFactory.create(it, realm) +// hasChange = true +// } +// } +// } +// } +// if (hasChange) postSnapshot() +// } +// } +// } +// +// } // Public methods ****************************************************************************** @@ -219,7 +213,7 @@ internal class DefaultTimeline( override fun start() { if (isStarted.compareAndSet(false, true)) { Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") - cryptoService.addNewSessionListener(newSessionListener) + eventDecryptor.start() BACKGROUND_HANDLER.post { val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) @@ -247,7 +241,7 @@ internal class DefaultTimeline( override fun dispose() { if (isStarted.compareAndSet(true, false)) { - cryptoService.removeSessionListener(newSessionListener) + eventDecryptor.destroy() Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") BACKGROUND_HANDLER.post { cancelableBag.cancel() @@ -387,9 +381,9 @@ internal class DefaultTimeline( private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) ?: return val params = PaginationTask.Params(roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit) + from = token, + direction = direction.toPaginationDirection(), + limit = limit) Timber.v("Should fetch $limit items $direction") cancelableBag += paginationTask.configureWith(params) @@ -453,6 +447,12 @@ internal class DefaultTimeline( } offsetResults.forEach { eventEntity -> val timelineEvent = eventEntity.asDomain() + + if (timelineEvent.isEncrypted() + && timelineEvent.root.mxDecryptionResult == null) { + timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } + } + val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size builtEvents.add(position, timelineEvent) //Need to shift :/ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt new file mode 100644 index 00000000..e822d37f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2019 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.session.room.timeline + +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.MXDecryptionException +import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.where +import io.realm.Realm +import io.realm.RealmConfiguration +import timber.log.Timber +import java.util.concurrent.Executors + + +internal class TimelineEventDecryptor( + private val realmConfiguration: RealmConfiguration, + private val timelineId: String, + private val cryptoService: CryptoService +) { + + private val newSessionListener = object : NewSessionListener { + override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + synchronized(unknownSessionsFailure) { + unknownSessionsFailure[sessionId]?.let { eventIds -> + eventIds.forEach { + requestDecryption(it) + } + } + unknownSessionsFailure[sessionId]?.clear() + } + } + + } + + private val executor = Executors.newSingleThreadExecutor() + + private val existingRequests = HashSet() + private val unknownSessionsFailure = HashMap>() + + fun start() { + cryptoService.addNewSessionListener(newSessionListener) + } + + fun destroy() { + cryptoService.removeSessionListener(newSessionListener) + executor.shutdownNow() + unknownSessionsFailure.clear() + existingRequests.clear() + } + + fun requestDecryption(eventId: String) { + synchronized(existingRequests) { + if (existingRequests.contains(eventId)) { + return Unit.also { + Timber.d("Skip Decryption request for event ${eventId}, already requested") + } + } + existingRequests.add(eventId) + } + synchronized(unknownSessionsFailure) { + unknownSessionsFailure.values.forEach { + if (it.contains(eventId)) return@synchronized Unit.also { + Timber.d("Skip Decryption request for event ${eventId}, unknown session") + } + } + } + executor.execute { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { + processDecryptRequest(eventId, it) + } + } + } + } + + private fun processDecryptRequest(eventId: String, realm: Realm) { + Timber.v("Decryption request for event ${eventId}") + val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() + ?: return Unit.also { + Timber.d("Decryption request for unknown message") + } + val event = eventEntity.asDomain() + try { + val result = cryptoService.decryptEvent(event, timelineId) + if (result == null) { + Timber.e("Null decryption result for event ${eventId}") + } else { + Timber.v("Successfully decrypted event ${eventId}") + eventEntity.setDecryptionResult(result) + } + + } catch (e: MXDecryptionException) { + if (e.cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { + //Keep track of unknown sessions to automatically try to decrypt on new session + event.content?.toModel()?.let { content -> + content.sessionId?.let { sessionId -> + synchronized(unknownSessionsFailure) { + val list = unknownSessionsFailure[sessionId] + ?: ArrayList().also { + unknownSessionsFailure[sessionId] = it + } + list.add(eventId) + } + } + } + } + } catch (t: Throwable) { + Timber.e(t, "Failed to decrypt event $eventId") + } finally { + synchronized(existingRequests) { + existingRequests.remove(eventId) + } + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index 5ee64b1c..f286ec4b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse @@ -39,7 +40,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: // Decrypt event if necessary decryptEvent(event, null) if (TextUtils.equals(event.getClearType(), EventType.MESSAGE) - && event.mClearEvent?.content?.toModel()?.type == "m.bad.encrypted") { + && event.getClearContent()?.toModel()?.type == "m.bad.encrypted") { Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) } else { sasVerificationService.onToDeviceEvent(event) @@ -70,7 +71,18 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: } if (null != result) { - event.setClearData(result) +// event.mxDecryptionResult = MXDecryptionResult( +// payload = result.clearEvent, +// keysClaimed = map +// ) + //TODO persist that? + event.mxDecryptionResult = MXDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) +// event.setClearData(result) return true } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index c8accc8e..7a9a918a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -176,7 +176,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4))) if (event.isEncrypted()) { - val decryptedContent = event.root.mClearEvent.toContent()?.let { + val decryptedContent = event.root.getClearContent()?.let { JSONObject(it).toString(4) } ?: stringProvider.getString(R.string.encryption_information_decryption_error) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent)) From 94b4351e190ac000de91d730be09eb58570aa499 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 11:18:27 +0200 Subject: [PATCH 09/22] wip async crypto + persist --- .../android/api/session/events/model/Event.kt | 9 ++++----- .../crypto/algorithms/olm/OlmDecryptionResult.kt | 11 +++++++---- .../android/internal/database/mapper/EventMapper.kt | 8 +++++--- .../android/internal/database/model/EventEntity.kt | 6 +++--- .../session/room/timeline/DefaultTimeline.kt | 3 --- .../session/room/timeline/TimelineEventDecryptor.kt | 6 +++--- .../internal/session/sync/CryptoSyncHandler.kt | 6 +++--- .../detail/timeline/factory/EncryptedItemFactory.kt | 13 ++++--------- 8 files changed, 29 insertions(+), 33 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index a79b306b..ce809128 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -20,9 +20,8 @@ import android.text.TextUtils import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.MXCryptoError -import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.util.JsonDict -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider import timber.log.Timber @@ -79,8 +78,8 @@ data class Event( ) { - var mxDecryptionResult: MXDecryptionResult? = null - var mCryptoError: MXCryptoError? = null + var mxDecryptionResult: OlmDecryptionResult? = null + var mCryptoError: MXCryptoError.ErrorType? = null /** * Check if event is a state event. @@ -181,7 +180,7 @@ data class Event( */ fun getSenderKey(): String? { return mxDecryptionResult?.senderKey - // return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key + // return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt index 8a28e492..f882ed5c 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt @@ -16,30 +16,33 @@ package im.vector.matrix.android.internal.crypto.algorithms.olm +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.util.JsonDict /** * This class represents the decryption result. */ +@JsonClass(generateAdapter = true) data class OlmDecryptionResult( /** * The decrypted payload (with properties 'type', 'content') */ - val payload: JsonDict? = null, + @Json(name = "payload") val payload: JsonDict? = null, /** * keys that the sender of the event claims ownership of: * map from key type to base64-encoded key. */ - val keysClaimed: Map? = null, + @Json(name = "keysClaimed") val keysClaimed: Map? = null, /** * The curve25519 key that the sender of the event is known to have ownership of. */ - val senderKey: String? = null, + @Json(name = "senderKey") val senderKey: String? = null, /** * Devices which forwarded this session to us (normally empty). */ - val forwardingCurve25519KeyChain: List? = null + @Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index fcd6a848..2d0aa9e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -20,7 +20,9 @@ import com.squareup.moshi.JsonDataException import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.UnsignedData -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryption +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.di.MoshiProvider import timber.log.Timber @@ -73,13 +75,13 @@ internal object EventMapper { ).also { eventEntity.decryptionResultJson?.let { json -> try { - it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java).fromJson(json) + it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json) } catch (t: JsonDataException) { Timber.e(t, "Failed to parse decryption result") } } //TODO get the full crypto error object - it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError(it, it) } + it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError.ErrorType.valueOf(it) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index edb6bf61..15640189 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.RealmResults @@ -73,13 +73,13 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI fun setDecryptionResult(result: MXEventDecryptionResult) { - val decryptionResult = MXDecryptionResult( + val decryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain ) - val adapter = MoshiProvider.providesMoshi().adapter(MXDecryptionResult::class.java) + val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java) decryptionResultJson = adapter.toJson(decryptionResult) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 18664de6..0e6a0754 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -22,9 +22,6 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.CancelableBag -import im.vector.matrix.android.api.util.addTo -import im.vector.matrix.android.internal.crypto.NewSessionListener -import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index e822d37f..b597aee3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.internal.crypto.MXDecryptionException import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain @@ -107,9 +106,10 @@ internal class TimelineEventDecryptor( eventEntity.setDecryptionResult(result) } - } catch (e: MXDecryptionException) { - if (e.cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { + } catch (e: MXCryptoError) { + if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { //Keep track of unknown sessions to automatically try to decrypt on new session + eventEntity.decryptionErrorCode = e.errorType.name event.content?.toModel()?.let { content -> content.sessionId?.let { sessionId -> synchronized(unknownSessionsFailure) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index f286ec4b..a9416a70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse @@ -67,7 +67,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: try { result = cryptoManager.decryptEvent(event, timelineId ?: "") } catch (exception: MXCryptoError) { - event.setCryptoError(exception) + event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError) } if (null != result) { @@ -76,7 +76,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: // keysClaimed = map // ) //TODO persist that? - event.mxDecryptionResult = MXDecryptionResult( + event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 0fd60157..d3c42cd5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -48,16 +48,11 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat EventType.ENCRYPTED == event.root.getClearType() -> { val cryptoError = event.root.mCryptoError val errorDescription = - if (cryptoError is MXCryptoError.Base) { - if (cryptoError.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { - stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) - } else { - // TODO i18n - cryptoError.technicalMessage - } + if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { + stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) } else { - // Cannot happen (for now) - "Other error" + // TODO i18n + cryptoError?.name } val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) From 7096094224d96515c6ecfae26cb7d8491bef6c41 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 12:05:41 +0200 Subject: [PATCH 10/22] wip crypto --- .../android/api/session/room/timeline/TimelineEvent.kt | 2 +- .../internal/database/mapper/TimelineEventMapper.kt | 3 ++- .../android/internal/database/model/EventEntity.kt | 2 ++ .../session/room/timeline/TimelineEventDecryptor.kt | 10 +++------- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 7dc7083d..563b97b0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -35,7 +35,7 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, -// val hasClearEventFlag: Boolean = false, + val hasClearEventFlag: Boolean = false, //used to refresh val annotations: EventAnnotationsSummary? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 72057b39..d082c12f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -46,7 +46,8 @@ internal object TimelineEventMapper { senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, - sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN + sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, + hasClearEventFlag = timelineEventEntity.root?.decryptionResultJson != null ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 15640189..65ee5aab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -81,5 +81,7 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI ) val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java) decryptionResultJson = adapter.toJson(decryptionResult) + decryptionErrorCode = null + timelineEventEntity?.firstOrNull()?.root = this } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index b597aee3..e67fba38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -99,14 +99,10 @@ internal class TimelineEventDecryptor( val event = eventEntity.asDomain() try { val result = cryptoService.decryptEvent(event, timelineId) - if (result == null) { - Timber.e("Null decryption result for event ${eventId}") - } else { - Timber.v("Successfully decrypted event ${eventId}") - eventEntity.setDecryptionResult(result) - } - + Timber.v("Successfully decrypted event ${eventId}") + eventEntity.setDecryptionResult(result) } catch (e: MXCryptoError) { + Timber.v("Failed to decrypte event ${eventId} ${e}") if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { //Keep track of unknown sessions to automatically try to decrypt on new session eventEntity.decryptionErrorCode = e.errorType.name From ee27d3e047740a10c0d7b287f984ee5525ff5382 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 12:49:22 +0200 Subject: [PATCH 11/22] Fix / clear unknown session map before re-request decrypt --- .../session/room/timeline/TimelineEventDecryptor.kt | 8 ++++++-- .../room/detail/timeline/factory/EncryptedItemFactory.kt | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index e67fba38..483a0a47 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -38,12 +38,16 @@ internal class TimelineEventDecryptor( private val newSessionListener = object : NewSessionListener { override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { synchronized(unknownSessionsFailure) { + val toDecryptAgain = ArrayList() unknownSessionsFailure[sessionId]?.let { eventIds -> - eventIds.forEach { + toDecryptAgain.addAll(eventIds) + } + if (toDecryptAgain.isNotEmpty()) { + unknownSessionsFailure[sessionId]?.clear() + toDecryptAgain.forEach { requestDecryption(it) } } - unknownSessionsFailure[sessionId]?.clear() } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index d3c42cd5..2a4a0c0f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -55,7 +55,8 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat cryptoError?.name } - val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) + val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null } + ?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) val spannableStr = span(message) { textStyle = "italic" textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) From a30da07fd1f5fe0319cb3c702ae64e603a3c6758 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 14:12:46 +0200 Subject: [PATCH 12/22] Fix / timeline auto refresh on new session --- .../android/internal/session/room/timeline/DefaultTimeline.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 0e6a0754..3b652d0b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -133,7 +133,7 @@ internal class DefaultTimeline( builtEventsIdMap[eventId]?.let { builtIndex -> //Update the relation of existing event builtEvents[builtIndex]?.let { te -> - //builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm) + builtEvents[builtIndex] = eventEntity.asDomain() hasChanged = true } } From e4c52484b134877381efe4bfaf40d8ffcc062d06 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 14:57:02 +0200 Subject: [PATCH 13/22] Fix / ensure equals check for encryption result --- .../android/api/session/events/model/Event.kt | 39 +++++++++++++++++++ .../session/room/timeline/TimelineEvent.kt | 1 - 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index ce809128..a50330a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -81,6 +81,7 @@ data class Event( var mxDecryptionResult: OlmDecryptionResult? = null var mCryptoError: MXCryptoError.ErrorType? = null + /** * Check if event is a state event. * @return true if event is state event. @@ -237,4 +238,42 @@ data class Event( * Tells if the event is redacted */ fun isRedacted() = unsignedData?.redactedEvent != null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Event + + if (type != other.type) return false + if (eventId != other.eventId) return false + if (content != other.content) return false + if (prevContent != other.prevContent) return false + if (originServerTs != other.originServerTs) return false + if (senderId != other.senderId) return false + if (stateKey != other.stateKey) return false + if (roomId != other.roomId) return false + if (unsignedData != other.unsignedData) return false + if (redacts != other.redacts) return false + if (mxDecryptionResult != other.mxDecryptionResult) return false + if (mCryptoError != other.mCryptoError) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + (eventId?.hashCode() ?: 0) + result = 31 * result + (content?.hashCode() ?: 0) + result = 31 * result + (prevContent?.hashCode() ?: 0) + result = 31 * result + (originServerTs?.hashCode() ?: 0) + result = 31 * result + (senderId?.hashCode() ?: 0) + result = 31 * result + (stateKey?.hashCode() ?: 0) + result = 31 * result + (roomId?.hashCode() ?: 0) + result = 31 * result + (unsignedData?.hashCode() ?: 0) + result = 31 * result + (redacts?.hashCode() ?: 0) + result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0) + result = 31 * result + (mCryptoError?.hashCode() ?: 0) + return result + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 563b97b0..1e1de79a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -35,7 +35,6 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, - val hasClearEventFlag: Boolean = false, //used to refresh val annotations: EventAnnotationsSummary? = null ) { From 25bc5001f97bec4f9254fde3a04a2e909178f044 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 14:57:37 +0200 Subject: [PATCH 14/22] RoomSummary / Use encrypted message screen --- .../riotx/features/home/room/list/RoomSummaryItemFactory.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index 05b366a6..6af62341 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -93,7 +93,10 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte val date = latestEvent.root.localDateTime() val currentDate = DateProvider.currentLocalDateTime() val isSameDay = date.toLocalDate() == currentDate.toLocalDate() - latestFormattedEvent = if (latestEvent.root.getClearType() == EventType.MESSAGE) { + latestFormattedEvent = if (latestEvent.root.isEncrypted() + && latestEvent.root.mxDecryptionResult == null) { + stringProvider.getString(R.string.encrypted_message) + } else if (latestEvent.root.getClearType() == EventType.MESSAGE) { val senderName = latestEvent.senderName() ?: latestEvent.root.senderId val content = latestEvent.root.getClearContent()?.toModel() val message = content?.body ?: "" From 57bd103de899c321660c32553152b35bebd9cb05 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 14:58:49 +0200 Subject: [PATCH 15/22] Fix / decrypt room summary latest event --- .../database/mapper/RoomSummaryMapper.kt | 27 +++++++++++++++++-- .../database/mapper/TimelineEventMapper.kt | 3 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 9991049e..dba7d502 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -16,24 +16,47 @@ package im.vector.matrix.android.internal.database.mapper +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import java.util.* import javax.inject.Inject -internal class RoomSummaryMapper @Inject constructor() { +internal class RoomSummaryMapper @Inject constructor( + val cryptoService: CryptoService +) { fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary { val tags = roomSummaryEntity.tags.map { RoomTag(it.tagName, it.tagOrder) } + + val latestEvent = roomSummaryEntity.latestEvent?.asDomain() + if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { + //TODO use a global event decryptor? attache to session and that listen to new sessionId? + //for now decrypt sync + try { + val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString()) + latestEvent.root.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + + } + } return RoomSummary( roomId = roomSummaryEntity.roomId, displayName = roomSummaryEntity.displayName ?: "", topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", isDirect = roomSummaryEntity.isDirect, - latestEvent = roomSummaryEntity.latestEvent?.asDomain(), + latestEvent = latestEvent, otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index d082c12f..72057b39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -46,8 +46,7 @@ internal object TimelineEventMapper { senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, - sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN, - hasClearEventFlag = timelineEventEntity.root?.decryptionResultJson != null + sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN ) } From 1d11a163afafabe0591016477bea35a60dbd0716 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 15:08:49 +0200 Subject: [PATCH 16/22] Notification resolver try to decrypt --- .../notifications/NotifiableEventResolver.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index 14d64d44..07891b80 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -18,6 +18,8 @@ package im.vector.riotx.features.notifications import androidx.core.app.NotificationCompat import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.content.ContentUrlResolver +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel @@ -25,11 +27,13 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter import timber.log.Timber +import java.util.* import javax.inject.Inject /** @@ -39,7 +43,8 @@ import javax.inject.Inject * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider, - private val noticeEventFormatter: NoticeEventFormatter) { + private val noticeEventFormatter: NoticeEventFormatter, + private val cryptoService: CryptoService) { //private val eventDisplay = RiotEventDisplay(context) @@ -110,6 +115,22 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St notifiableEvent.matrixID = session.sessionParams.credentials.userId return notifiableEvent } else { + if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) { + //TODO use a global event decryptor? attache to session and that listen to new sessionId? + //for now decrypt sync + try { + val result = cryptoService.decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString()) + event.root.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + + } + } + val body = event.annotations?.editSummary?.aggregatedContent?.toModel()?.body ?: event.root.getClearContent().toModel()?.body ?: stringProvider.getString(R.string.notification_unknown_new_event) From 7e6e09bc19668e1e4f477e48d41373691dca873d Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 8 Jul 2019 15:30:11 +0200 Subject: [PATCH 17/22] fix / compilation --- .../riotx/features/notifications/NotifiableEventResolver.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index 07891b80..e0076589 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -43,8 +43,7 @@ import javax.inject.Inject * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider, - private val noticeEventFormatter: NoticeEventFormatter, - private val cryptoService: CryptoService) { + private val noticeEventFormatter: NoticeEventFormatter) { //private val eventDisplay = RiotEventDisplay(context) @@ -52,7 +51,6 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St val roomID = event.roomId ?: return null val eventId = event.eventId ?: return null val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null - when (event.getClearType()) { EventType.MESSAGE -> { return resolveMessageEvent(timelineEvent, session) @@ -119,7 +117,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St //TODO use a global event decryptor? attache to session and that listen to new sessionId? //for now decrypt sync try { - val result = cryptoService.decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString()) + val result = session.decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString()) event.root.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, From dd07f5c2a6c3241dca91dfd4d29350069166c846 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 8 Jul 2019 15:32:24 +0200 Subject: [PATCH 18/22] TimelineEvent : update sender data when loading room members and prune event (+ remove RoomSummaryMapper param) --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 4 +- .../java/im/vector/matrix/rx/RxSession.kt | 4 +- .../matrix/android/api/session/room/Room.kt | 4 +- .../android/api/session/room/RoomService.kt | 2 +- .../internal/database/RealmLiveData.kt | 3 +- .../database/helper/ChunkEntityHelper.kt | 56 ++++--------- .../database/helper/RoomEntityHelper.kt | 1 + .../helper/TimelineEventEntityHelper.kt | 78 +++++++++++++++++++ .../database/mapper/RoomSummaryMapper.kt | 2 +- .../database/model/TimelineEventEntity.kt | 3 +- .../query/TimelineEventEntityQueries.kt | 6 ++ .../internal/session/room/DefaultRoom.kt | 10 +-- .../session/room/DefaultRoomService.kt | 4 +- .../room/membership/LoadRoomMembersTask.kt | 21 ++--- .../session/room/membership/RoomMembers.kt | 4 + .../session/room/prune/PruneEventTask.kt | 48 +++++++----- .../api/pushrules/PushrulesConditionTest.kt | 6 +- .../features/home/HomeActivityViewModel.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 12 +-- .../roomdirectory/RoomDirectoryViewModel.kt | 2 +- .../roompreview/RoomPreviewViewModel.kt | 2 +- 21 files changed, 165 insertions(+), 109 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index dbb94a91..2c9c7d8b 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -25,8 +25,8 @@ import io.reactivex.schedulers.Schedulers class RxRoom(private val room: Room) { - fun liveRoomSummary(fetchLastEvent: Boolean): Observable { - return room.liveRoomSummary(fetchLastEvent).asObservable().observeOn(Schedulers.computation()) + fun liveRoomSummary(): Observable { + return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation()) } fun liveRoomMemberIds(): Observable> { diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 5fd76f0b..30d31f94 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -26,8 +26,8 @@ import io.reactivex.schedulers.Schedulers class RxSession(private val session: Session) { - fun liveRoomSummaries(fetchLastEvents: Boolean): Observable> { - return session.liveRoomSummaries(fetchLastEvents).asObservable().observeOn(Schedulers.computation()) + fun liveRoomSummaries(): Observable> { + return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation()) } fun liveGroupSummaries(): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 35efa0fb..ec6b382f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -47,8 +47,8 @@ interface Room : * A live [RoomSummary] associated with the room * You can observe this summary to get dynamic data from this room. */ - fun liveRoomSummary(fetchLastEvent: Boolean = false): LiveData + fun liveRoomSummary(): LiveData - fun roomSummary(fetchLastEvent: Boolean = false): RoomSummary? + fun roomSummary(): RoomSummary? } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 8fd6605f..fc0bf499 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -43,6 +43,6 @@ interface RoomService { * Get a live list of room summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of [RoomSummary] */ - fun liveRoomSummaries(fetchLastEvents: Boolean = true): LiveData> + fun liveRoomSummaries(): LiveData> } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveData.kt index 238c0916..46a431a4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveData.kt @@ -31,8 +31,7 @@ class RealmLiveData(private val realmConfiguration: RealmConfigu override fun onActive() { val realm = Realm.getInstance(realmConfiguration) - val results = query.invoke(realm).findAll() - value = results + val results = query.invoke(realm).findAllAsync() results.addChangeListener(listener) this.realm = realm this.results = results diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index d5979723..b779ec08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -18,28 +18,17 @@ package im.vector.matrix.android.internal.database.helper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -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.database.query.find -import im.vector.matrix.android.internal.database.query.next -import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.extensions.assertIsManaged -import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection -import io.realm.RealmList -import io.realm.RealmQuery import io.realm.Sort // By default if a chunk is empty we consider it unlinked @@ -76,10 +65,14 @@ internal fun ChunkEntity.merge(roomId: String, eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) } val events = eventsToMerge.mapNotNull { it.root?.asDomain() } + val eventIds = ArrayList() events.forEach { event -> add(roomId, event, direction, isUnlinked = isUnlinked) + if (event.eventId != null) { + eventIds.add(event.eventId) + } } - updateSenderDataFor(roomId, isUnlinked, events) + updateSenderDataFor(eventIds) } internal fun ChunkEntity.addAll(roomId: String, @@ -89,30 +82,20 @@ internal fun ChunkEntity.addAll(roomId: String, // Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk) isUnlinked: Boolean = false) { assertIsManaged() + val eventIds = ArrayList() events.forEach { event -> add(roomId, event, direction, stateIndexOffset, isUnlinked) + if (event.eventId != null) { + eventIds.add(event.eventId) + } } - updateSenderDataFor(roomId, isUnlinked, events) + updateSenderDataFor(eventIds) } -private fun ChunkEntity.updateSenderDataFor(roomId: String, isUnlinked: Boolean, events: List) { - for (event in events) { - val eventId = event.eventId ?: continue +internal fun ChunkEntity.updateSenderDataFor(eventIds: List) { + for (eventId in eventIds) { val timelineEventEntity = timelineEvents.find(eventId) ?: continue - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: continue - val stateIndex = timelineEventEntity.root?.stateIndex ?: continue - val senderId = timelineEventEntity.root?.sender ?: continue - - val senderRoomMemberContent = when { - stateIndex <= 0 -> timelineEvents.build(senderId, isUnlinked).next(from = stateIndex)?.root?.prevContent - else -> timelineEvents.build(senderId, isUnlinked).prev(since = stateIndex)?.root?.content - } - val fallbackContent = senderRoomMemberContent - ?: roomEntity.untimelinedStateEvents.build(senderId).prev(since = stateIndex)?.content - val senderRoomMember: RoomMember? = ContentMapper.map(fallbackContent).toModel() - timelineEventEntity.senderAvatar = senderRoomMember?.avatarUrl - timelineEventEntity.senderName = senderRoomMember?.displayName - timelineEventEntity.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName) + timelineEventEntity.updateSenderData() } } @@ -161,19 +144,6 @@ private fun ChunkEntity.add(roomId: String, timelineEvents.add(position, eventEntity) } -private fun RealmList.build(sender: String, isUnlinked: Boolean): RealmQuery { - return where() - .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) - .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) - .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) -} - -private fun RealmList.build(sender: String): RealmQuery { - return where() - .equalTo(EventEntityFields.STATE_KEY, sender) - .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) -} - internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { PaginationDirection.FORWARDS -> forwardsDisplayIndex diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index f694953e..c0d01ded 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -72,6 +72,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) { it.senderName = myUser?.displayName it.senderAvatar = myUser?.avatarUrl it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName) + it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst() } sendingTimelineEvents.add(0, timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt new file mode 100644 index 00000000..16a815b4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/TimelineEventEntityHelper.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2019 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.helper + +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields +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.database.query.next +import im.vector.matrix.android.internal.database.query.prev +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.session.room.membership.RoomMembers +import io.realm.RealmList +import io.realm.RealmQuery + +internal fun TimelineEventEntity.updateSenderData() { + assertIsManaged() + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return + val stateIndex = root?.stateIndex ?: return + val senderId = root?.sender ?: return + val chunkEntity = chunk?.firstOrNull() ?: return + val isUnlinked = chunkEntity.isUnlinked() + var senderMembershipEvent: EventEntity? + var senderRoomMemberContent: String? + when { + stateIndex <= 0 -> { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.prevContent + } + else -> { + senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root + senderRoomMemberContent = senderMembershipEvent?.content + } + } + + // We fallback to untimelinedStateEvents if we can't find membership events in timeline + if (senderMembershipEvent == null) { + senderMembershipEvent = roomEntity.untimelinedStateEvents + .where() + .equalTo(EventEntityFields.STATE_KEY, senderId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER) + .prev(since = stateIndex) + senderRoomMemberContent = senderMembershipEvent?.content + } + val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel() + this.senderAvatar = senderRoomMember?.avatarUrl + this.senderName = senderRoomMember?.displayName + this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName) + this.senderMembershipEvent = senderMembershipEvent +} + +private fun RealmList.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery { + return where() + .equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender) + .equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER) + .equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked) +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index dba7d502..5f946e42 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -29,7 +29,7 @@ internal class RoomSummaryMapper @Inject constructor( val cryptoService: CryptoService ) { - fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary { + fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { val tags = roomSummaryEntity.tags.map { RoomTag(it.tagName, it.tagOrder) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index 8c42e4c9..9a467f72 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -35,7 +35,8 @@ internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.r var annotations: EventAnnotationsSummaryEntity? = null, var senderName: String? = null, var isUniqueDisplayName: Boolean = false, - var senderAvatar: String? = null + var senderAvatar: String? = null, + var senderMembershipEvent: EventEntity? = null ) : RealmObject() { @LinkingObjects("timelineEvents") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 620f47be..5942118d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -54,6 +54,12 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, } } +internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List { + return realm.where() + .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT.EVENT_ID, senderMembershipEventId) + .findAll() +} + internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, roomId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index b94ba769..492dd035 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -33,8 +33,6 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.util.fetchCopied -import im.vector.matrix.android.internal.util.fetchCopyMap import javax.inject.Inject internal class DefaultRoom @Inject constructor(override val roomId: String, @@ -55,12 +53,12 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, RelationService by relationService, MembershipService by roomMembersService { - override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData { + override fun liveRoomSummary(): LiveData { val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) } return Transformations.map(liveRealmData) { results -> - val roomSummaries = results.map { roomSummaryMapper.map(it, fetchLastEvent) } + val roomSummaries = results.map { roomSummaryMapper.map(it) } if (roomSummaries.isEmpty()) { // Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache @@ -71,10 +69,10 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, } } - override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? { + override fun roomSummary(): RoomSummary? { return monarchy.fetchAllMappedSync( { realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, - { roomSummaryMapper.map(it, fetchLastEvent) } + { roomSummaryMapper.map(it) } ).firstOrNull() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index d40184a0..2c0f1ce9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -52,10 +52,10 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona return roomFactory.create(roomId) } - override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData> { + override fun liveRoomSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, - { roomSummaryMapper.map(it, fetchLastEvents) } + { roomSummaryMapper.map(it) } ) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 4dbb60b8..d98436a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -20,15 +20,16 @@ import arrow.core.Try import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.helper.addStateEvents +import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync +import io.realm.Realm import io.realm.kotlin.createObject import javax.inject.Inject @@ -41,9 +42,9 @@ internal interface LoadRoomMembersTask : Task { @@ -68,20 +69,20 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP val roomMembers = RoomMembers(realm, roomId).getLoaded() val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } - roomEntity.addStateEvents(eventsToInsert) + roomEntity.chunks.flatMap { it.timelineEvents }.forEach { + it.updateSenderData() + } roomEntity.areAllMembersLoaded = true - roomSummaryUpdater.update(realm, roomId) } .map { response } } private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { - return monarchy - .fetchAllCopiedSync { RoomEntity.where(it, roomId) } - .firstOrNull() - ?.areAllMembersLoaded ?: false + return Realm.getInstance(monarchy.realmConfiguration).use { + RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false + } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt index 41d5879c..72b2695e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt @@ -29,6 +29,10 @@ import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +/** + * This class is an helper around STATE_ROOM_MEMBER events. + * It allows to get the live membership of a user. + */ internal class RoomMembers(private val realm: Realm, private val roomId: String ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index 56b40f2d..8ea8c337 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -21,12 +21,14 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.query.findWithSenderMembershipEvent import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm @@ -59,13 +61,13 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M } val redactionEventEntity = EventEntity.where(realm, eventId = redactionEvent.eventId - ?: "").findFirst() - ?: return + ?: "").findFirst() + ?: return val isLocalEcho = redactionEventEntity.sendState == SendState.UNSENT Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho") val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() - ?: return + ?: return val allowedKeys = computeAllowedKeys(eventToPrune.type) if (allowedKeys.isNotEmpty()) { @@ -76,7 +78,7 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData - ?: UnsignedData(null, null) + ?: UnsignedData(null, null) //was this event a m.replace // val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() @@ -94,28 +96,34 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M // } } } + if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) { + val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId) + for (timelineEvent in timelineEventsToUpdate) { + timelineEvent.updateSenderData() + } + } } private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { - EventType.STATE_ROOM_MEMBER -> listOf("membership") - EventType.STATE_ROOM_CREATE -> listOf("creator") - EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") + EventType.STATE_ROOM_MEMBER -> listOf("membership") + EventType.STATE_ROOM_CREATE -> listOf("creator") + EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", - "users_default", - "events", - "events_default", - "state_default", - "ban", - "kick", - "redact", - "invite") - EventType.STATE_ROOM_ALIASES -> listOf("aliases") - EventType.STATE_CANONICAL_ALIAS -> listOf("alias") - EventType.FEEDBACK -> listOf("type", "target_event_id") - else -> emptyList() + "users_default", + "events", + "events_default", + "state_default", + "ban", + "kick", + "redact", + "invite") + EventType.STATE_ROOM_ALIASES -> listOf("aliases") + EventType.STATE_CANONICAL_ALIAS -> listOf("alias") + EventType.FEEDBACK -> listOf("type", "target_event_id") + else -> emptyList() } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 6c1ef646..17faee35 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -179,7 +179,7 @@ class PushrulesConditionTest { } } - override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData> { + override fun liveRoomSummaries(): LiveData> { return MutableLiveData() } } @@ -194,11 +194,11 @@ class PushrulesConditionTest { return _numberOfJoinedMembers } - override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData { + override fun liveRoomSummary(): LiveData { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? { + override fun roomSummary(): RoomSummary? { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index d3b95f26..36d57255 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -73,7 +73,7 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState: private fun observeRoomAndGroup() { Observable .combineLatest, Option, List>( - session.rx().liveRoomSummaries(fetchLastEvents = true).throttleLast(300, TimeUnit.MILLISECONDS), + session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), selectedGroupStore.observe(), BiFunction { rooms, selectedGroupOption -> val selectedGroup = selectedGroupOption.orNull() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index d5d5ffa3..83dcb415 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -500,17 +500,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun observeRoomSummary() { - room.rx().liveRoomSummary(false) - .observeOn(AndroidSchedulers.mainThread()) - .flatMap { - if (it.membership != Membership.INVITE || it.latestEvent != null) { - // Not an invitation, or already fetching last event - Observable.just(it) - } else { - // We need the last event - room.rx().liveRoomSummary(true) - } - } + room.rx().liveRoomSummary() .execute { async -> copy( asyncRoomSummary = async, diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index f7d6c2fb..b34618e2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -86,7 +86,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: private fun observeJoinedRooms() { session .rx() - .liveRoomSummaries(fetchLastEvents = false) + .liveRoomSummaries() .subscribe { list -> val joinedRoomIds = list // Keep only joined room diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index e91b4b0f..bf5fa743 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -54,7 +54,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R private fun observeJoinedRooms() { session .rx() - .liveRoomSummaries(fetchLastEvents = false) + .liveRoomSummaries() .subscribe { list -> withState { state -> val isRoomJoined = list From d52613d72358ac49931e65f7a1e1e06f5643061f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 9 Jul 2019 11:17:36 +0200 Subject: [PATCH 19/22] Trick / Remove home progress blank paddings --- .../riotx/features/home/HomeDetailFragment.kt | 1 + .../main/res/layout/fragment_home_detail.xml | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index e24ae7ff..db8ae359 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -212,6 +212,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { is SyncState.RUNNING -> if (it.syncState.catchingUp) View.VISIBLE else View.GONE else -> View.GONE } + syncProgressBarWrap.visibility = syncProgressBar.visibility } companion object { diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index cc80bd3a..5ee80314 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -44,18 +44,29 @@ - + + tools:visibility="visible"> + + + + + app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap" /> Date: Tue, 9 Jul 2019 14:22:40 +0200 Subject: [PATCH 20/22] Fix / view source, decrypted source was not correct --- .../android/api/session/events/model/Event.kt | 15 +++++++++++++++ .../timeline/action/MessageMenuViewModel.kt | 8 ++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index a50330a5..547e627f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider +import org.json.JSONObject import timber.log.Timber typealias Content = JsonDict @@ -234,6 +235,20 @@ data class Event( // } // } + + fun toContentStringWithIndent(): String { + val contentMap = this.toContent()?.toMutableMap() ?: HashMap() + contentMap.remove("mxDecryptionResult") + contentMap.remove("mCryptoError") + return JSONObject(contentMap).toString(4) + } + + fun toClearContentStringWithIndent(): String? { + val contentMap = this.mxDecryptionResult?.payload?.toMutableMap() + val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) + return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) } + } + /** * Tells if the event is redacted */ diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 7a9a918a..032d690c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -27,6 +27,8 @@ import im.vector.matrix.android.api.session.room.model.message.MessageImageConte import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.rx.RxRoom import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel @@ -174,11 +176,9 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M //TODO sent by me or sufficient power level } - this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4))) + this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source,event.root.toContentStringWithIndent())) if (event.isEncrypted()) { - val decryptedContent = event.root.getClearContent()?.let { - JSONObject(it).toString(4) - } ?: stringProvider.getString(R.string.encryption_information_decryption_error) + val decryptedContent = event.root.toClearContentStringWithIndent() ?: stringProvider.getString(R.string.encryption_information_decryption_error) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent)) } this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId)) From b5650b2b8f2170da22556381b4bdbfbdec349477 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 9 Jul 2019 14:44:59 +0200 Subject: [PATCH 21/22] Pagination : avoid breaking timeline when paginating twice from same token (race condition) --- .../session/room/timeline/TokenChunkEventPersistor.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 50881854..ae60969b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -128,6 +128,12 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy nextToken = receivedChunk.start prevToken = receivedChunk.end } + + if (ChunkEntity.find(realm, roomId, nextToken = nextToken) != null || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null) { + Timber.v("Already inserted - SKIP") + return@tryTransactionSync + } + val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken) From 61d7f23870dbd4a585fb6fd77be05ec4788ad01b Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 9 Jul 2019 14:43:27 +0200 Subject: [PATCH 22/22] remove dead code --- .../android/internal/database/mapper/TimelineEventMapper.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 72057b39..c890436f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -16,12 +16,10 @@ package im.vector.matrix.android.internal.database.mapper -import com.squareup.moshi.Types import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.di.MoshiProvider internal object TimelineEventMapper { @@ -35,7 +33,6 @@ internal object TimelineEventMapper { } fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { - val listMapper = MoshiProvider.providesMoshi().adapter>(Types.newParameterizedType(List::class.java, String::class.java)) return TimelineEvent( root = timelineEventEntity.root?.asDomain()