Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5330.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Continue improving realm usage.
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class FlowRoom(private val room: Room) {
}

fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asFlow()
return room.getTimelineEventLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getTimeLineEvent(eventId).toOptional()
room.getTimelineEvent(eventId).toOptional()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class PreShareKeysTest : InstrumentedTest {
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class KeyShareTests : InstrumentedTest {

val roomSecondSessionPOV = aliceSession2.getRoom(roomId)

val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId)
val receivedEvent = roomSecondSessionPOV?.getTimelineEvent(sentEventId)
assertNotNull(receivedEvent)
assert(receivedEvent!!.isEncrypted())

Expand Down Expand Up @@ -382,7 +382,7 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)

val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId)
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)

var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ class WithHeldTests : InstrumentedTest {
// await for bob unverified session to get the message
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null
bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null
}
}

val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId)!!
val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId)!!

// =============================
// ASSERT
Expand All @@ -109,7 +109,7 @@ class WithHeldTests : InstrumentedTest {

testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId)
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId)
// wait until it's decrypted
ev?.root?.getClearType() == EventType.MESSAGE
}
Expand Down Expand Up @@ -157,12 +157,12 @@ class WithHeldTests : InstrumentedTest {
// await for bob session to get the message
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null
bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) != null
}
}

// Previous message should still be undecryptable (partially withheld session)
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try {
// .. might need to wait a bit for stability?
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
Expand Down Expand Up @@ -190,7 +190,7 @@ class WithHeldTests : InstrumentedTest {
// await for bob SecondSession session to get the message
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
}
}

Expand Down Expand Up @@ -231,7 +231,7 @@ class WithHeldTests : InstrumentedTest {
// await for bob SecondSession session to get the message
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
// try to decrypt and force key request
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ interface TimelineService {
* At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
* @param eventId the eventId to get the TimelineEvent
*/
fun getTimeLineEvent(eventId: String): TimelineEvent?
fun getTimelineEvent(eventId: String): TimelineEvent?

/**
* Creates a LiveData of Optional TimelineEvent event with eventId.
* If the eventId is a local echo eventId, it will make the LiveData be updated with the synced TimelineEvent when coming through the sync.
* In this case, makes sure to use the new synced eventId from the TimelineEvent class if you want to interact, as the local echo is removed from the SDK.
* @param eventId the eventId to listen for TimelineEvent
*/
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
fun getTimelineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a file 5330.sdk to announce the 2 API name changes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

package org.matrix.android.sdk.internal.database.mapper

import io.realm.Realm
import io.realm.RealmList
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.query.where
Expand All @@ -32,14 +35,22 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(
return emptyList()
}
val readReceipts = readReceiptsSummaryEntity.readReceipts

return realmSessionProvider.withRealm { realm ->
readReceipts
.mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
}
// Avoid opening a new realm if we already have one opened
return if (readReceiptsSummaryEntity.isManaged) {
map(readReceipts, readReceiptsSummaryEntity.realm)
} else {
realmSessionProvider.withRealm { realm ->
map(readReceipts, realm)
}
}
}

private fun map(readReceipts: RealmList<ReadReceiptEntity>, realm: Realm): List<ReadReceipt> {
return readReceipts
.mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(

private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
val session = sessionManager.getSessionComponent(sessionId)?.session()
return session?.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return null.also {
return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
Timber.v("## POLL target poll event $eventId not found in room $roomId")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@ import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDataSource
import org.matrix.android.sdk.internal.util.fetchCopyMap
import timber.log.Timber

Expand All @@ -50,7 +49,7 @@ internal class DefaultRelationService @AssistedInject constructor(
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask,
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
private val timelineEventMapper: TimelineEventMapper,
private val timelineEventDataSource: TimelineEventDataSource,
@SessionDatabase private val monarchy: Monarchy
) : RelationService {

Expand All @@ -60,14 +59,8 @@ internal class DefaultRelationService @AssistedInject constructor(
}

override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
return if (monarchy
.fetchCopyMap(
{ realm ->
TimelineEventEntity.where(realm, roomId, targetEventId).findFirst()
},
{ entity, _ ->
timelineEventMapper.map(entity)
})
val targetTimelineEvent = timelineEventDataSource.getTimelineEvent(roomId, targetEventId)
return if (targetTimelineEvent
?.annotations
?.reactionsSummary
.orEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,23 @@ import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.TaskExecutor

internal class DefaultTimelineService @AssistedInject constructor(
@Assisted private val roomId: String,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val timelineInput: TimelineInput,
private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask,
private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask,
Expand All @@ -60,7 +47,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val lightweightSettingsStorage: LightweightSettingsStorage,
private val readReceiptHandler: ReadReceiptHandler,
private val coroutineDispatchers: MatrixCoroutineDispatchers
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val timelineEventDataSource: TimelineEventDataSource
) : TimelineService {

@AssistedFactory
Expand Down Expand Up @@ -88,27 +76,15 @@ internal class DefaultTimelineService @AssistedInject constructor(
)
}

override fun getTimeLineEvent(eventId: String): TimelineEvent? {
return realmSessionProvider.withRealm { realm ->
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let {
timelineEventMapper.map(it)
}
}
override fun getTimelineEvent(eventId: String): TimelineEvent? {
return timelineEventDataSource.getTimelineEvent(roomId, eventId)
}

override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId)
override fun getTimelineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
return timelineEventDataSource.getTimelineEventLive(roomId, eventId)
}

override fun getAttachmentMessages(): List<TimelineEvent> {
// TODO pretty bad query.. maybe we should denormalize clear type in base?
return realmSessionProvider.withRealm { realm ->
realm.where<TimelineEventEntity>()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll()
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
.orEmpty()
}
return timelineEventDataSource.getAttachmentMessages(roomId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal class RealmSendingEventsDataSource(

private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
frozenSendingTimelineEvents = sendingTimelineEvents?.freeze()
updateFrozenResults(events)
onEventsUpdated(false)
}

Expand All @@ -59,10 +59,17 @@ internal class RealmSendingEventsDataSource(

override fun stop() {
sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener)
updateFrozenResults(null)
sendingTimelineEvents = null
roomEntity = null
}

private fun updateFrozenResults(sendingEvents: RealmList<TimelineEventEntity>?) {
// Makes sure to close the previous frozen realm
frozenSendingTimelineEvents?.realm?.close()
frozenSendingTimelineEvents = sendingEvents?.freeze()
}

override fun buildSendingEvents(): List<TimelineEvent> {
val builtSendingEvents = mutableListOf<TimelineEvent>()
uiEchoManager.getInMemorySendingEvents()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.timeline

import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.task.TaskExecutor
import javax.inject.Inject

internal class TimelineEventDataSource @Inject constructor(private val realmSessionProvider: RealmSessionProvider,
private val timelineEventMapper: TimelineEventMapper,
private val taskExecutor: TaskExecutor,
@SessionDatabase private val monarchy: Monarchy) {

fun getTimelineEvent(roomId: String, eventId: String): TimelineEvent? {
return realmSessionProvider.withRealm { realm ->
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let {
timelineEventMapper.map(it)
}
}
}

fun getTimelineEventLive(roomId: String, eventId: String): LiveData<Optional<TimelineEvent>> {
return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId)
}

fun getAttachmentMessages(roomId: String): List<TimelineEvent> {
// TODO pretty bad query.. maybe we should denormalize clear type in base?
return realmSessionProvider.withRealm { realm ->
realm.where<TimelineEventEntity>()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll()
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
.orEmpty()
}
}
}
Loading