From f0dbb92d76dbdb2b52489e3abc0e58cd23351658 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 30 Jun 2020 19:45:17 +0200 Subject: [PATCH 1/8] Attempt to clean db [WIP] --- .../database/helper/ChunkEntityHelper.kt | 1 + .../internal/database/model/ChunkEntity.kt | 1 + .../internal/session/DefaultSession.kt | 55 +++++++++++++++++++ .../internal/session/filter/FilterUtil.kt | 3 +- 4 files changed, 59 insertions(+), 1 deletion(-) 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 d86151e694..bc681e4eb8 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 @@ -115,6 +115,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, true } } + numberOfTimelineEvents++ timelineEvents.add(timelineEventEntity) } 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 19bf72970c..b1e08fdeea 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 @@ -27,6 +27,7 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, @Index var nextToken: String? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), + var numberOfTimelineEvents: Long= 0, // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index e32ba7e63c..f8ddd3d345 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -51,9 +51,21 @@ import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.widgets.WidgetService import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.crypto.DefaultCryptoService +import im.vector.matrix.android.internal.database.awaitTransaction +import im.vector.matrix.android.internal.database.helper.nextDisplayIndex +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.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields +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.model.TimelineEventEntityFields +import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.identity.DefaultIdentityService +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread @@ -61,6 +73,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.createUIHandler +import io.realm.RealmConfiguration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus @@ -77,6 +90,7 @@ internal class DefaultSession @Inject constructor( private val eventBus: EventBus, @SessionId override val sessionId: String, + @SessionDatabase private val realmConfiguration: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, private val roomService: Lazy, @@ -151,6 +165,47 @@ internal class DefaultSession @Inject constructor( } eventBus.register(this) timelineEventDecryptor.start() + taskExecutor.executorScope.launch(Dispatchers.Default) { + awaitTransaction(realmConfiguration) { realm -> + val allRooms = realm.where(RoomEntity::class.java).findAll() + val numberOfEvents = realm.where(EventEntity::class.java).findAll().size + val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size + Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") + Timber.v("Number of rooms in db: ${allRooms.size}") + if (numberOfTimelineEvents < 30_000L) { + Timber.v("Db is low enough") + } else { + + val hugeChunks = realm.where(ChunkEntity::class.java).greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, 250).findAll() + Timber.v("There are ${hugeChunks.size} chunks to clean") + for (chunk in hugeChunks) { + val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS) + val thresholdDisplayIndex = maxDisplayIndex - 250 + val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll() + Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}") + chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size + eventsToRemove.forEach { + val canDeleteRoot = it.root?.stateKey == null + if (canDeleteRoot) { + it.root?.deleteFromRealm() + } + it.readReceipts?.readReceipts?.deleteAllFromRealm() + it.readReceipts?.deleteFromRealm() + it.annotations?.apply { + editSummary?.deleteFromRealm() + pollResponseSummary?.deleteFromRealm() + referencesSummaryEntity?.deleteFromRealm() + reactionsSummary.deleteAllFromRealm() + } + it.annotations?.deleteFromRealm() + it.readReceipts?.deleteFromRealm() + it.deleteFromRealm() + } + } + } + + } + } } override fun requireBackgroundSync() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt index 53ede5ad45..d95a53cb07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt @@ -98,7 +98,8 @@ internal object FilterUtil { state = filter.room.state?.copy(lazyLoadMembers = true) ?: RoomEventFilter(lazyLoadMembers = true) ) - ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true)) + ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true), + timeline = RoomEventFilter(limit = 1500)) ) } else { val newRoomEventFilter = filter.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() } From 2f6b38eb39f76fc645403b2d3237719b93f34bcb Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 2 Jul 2020 15:32:58 +0200 Subject: [PATCH 2/8] Introduce EventInsertEntity to handle db updates --- .../tasks/RoomVerificationUpdateTask.kt | 161 ----------- .../VerificationMessageLiveObserver.kt | 177 +++++++++--- .../database/EventInsertLiveObserver.kt | 101 +++++++ .../database/RealmLiveEntityObserver.kt | 3 +- .../internal/database/mapper/EventMapper.kt | 1 - .../database/model/EventInsertEntity.kt | 28 ++ .../database/model/SessionRealmModule.kt | 1 + .../database/query/EventEntityQueries.kt | 17 +- .../internal/session/DefaultSession.kt | 6 +- .../session/EventInsertLiveProcessor.kt | 27 ++ .../android/internal/session/SessionModule.kt | 29 +- .../session/call/CallEventObserver.kt | 66 ----- .../session/call/CallEventProcessor.kt | 65 +++++ .../session/call/CallEventsObserverTask.kt | 92 ------- .../internal/session/call/CallModule.kt | 2 - .../session/group/GroupSummaryUpdater.kt | 4 +- ... => EventRelationsAggregationProcessor.kt} | 259 ++++++++---------- .../room/EventRelationsAggregationUpdater.kt | 76 ----- .../internal/session/room/RoomModule.kt | 8 - .../create/RoomCreateEventLiveObserver.kt | 72 ----- .../room/create/RoomCreateEventProcessor.kt | 45 +++ .../session/room/prune/EventsPruner.kt | 56 ---- ...ventTask.kt => RedactionEventProcessor.kt} | 29 +- .../session/room/timeline/DefaultTimeline.kt | 11 - .../timeline/TimelineHiddenReadReceipts.kt | 2 +- .../RoomTombstoneEventLiveObserver.kt | 75 ----- .../tombstone/RoomTombstoneEventProcessor.kt | 48 ++++ 27 files changed, 617 insertions(+), 844 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventsObserverTask.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/{EventRelationsAggregationTask.kt => EventRelationsAggregationProcessor.kt} (71%) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/{PruneEventTask.kt => RedactionEventProcessor.kt} (87%) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt deleted file mode 100644 index 400febc15f..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ /dev/null @@ -1,161 +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.crypto.tasks - -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.crypto.verification.VerificationService -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.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent -import im.vector.matrix.android.api.session.room.model.message.MessageType -import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent -import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent -import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent -import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult -import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService -import im.vector.matrix.android.internal.di.DeviceId -import im.vector.matrix.android.internal.di.UserId -import im.vector.matrix.android.internal.task.Task -import timber.log.Timber -import java.util.ArrayList -import javax.inject.Inject - -internal interface RoomVerificationUpdateTask : Task { - data class Params( - val events: List, - val verificationService: DefaultVerificationService, - val cryptoService: CryptoService - ) -} - -internal class DefaultRoomVerificationUpdateTask @Inject constructor( - @UserId private val userId: String, - @DeviceId private val deviceId: String?, - private val cryptoService: CryptoService) : RoomVerificationUpdateTask { - - companion object { - // XXX what about multi-account? - private val transactionsHandledByOtherDevice = ArrayList() - } - - override suspend fun execute(params: RoomVerificationUpdateTask.Params) { - // TODO ignore initial sync or back pagination? - - params.events.forEach { event -> - Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") - - // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, - // the message should be ignored by the receiver. - - if (!VerificationService.isValidRequest(event.ageLocalTs - ?: event.originServerTs)) return@forEach Unit.also { - Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated") - } - - // decrypt if needed? - if (event.isEncrypted() && event.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, "") - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.e("## SAS Failed to decrypt event: ${event.eventId}") - params.verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event) - } - } - Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") - - // Relates to is not encrypted - val relatesToEventId = event.content.toModel()?.relatesTo?.eventId - - if (event.senderId == userId) { - // If it's send from me, we need to keep track of Requests or Start - // done from another device of mine - - if (EventType.MESSAGE == event.getClearType()) { - val msgType = event.getClearContent().toModel()?.msgType - if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { - event.getClearContent().toModel()?.let { - if (it.fromDevice != deviceId) { - // The verification is requested from another device - Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") - event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } - } - } - } - } else if (EventType.KEY_VERIFICATION_START == event.getClearType()) { - event.getClearContent().toModel()?.let { - if (it.fromDevice != deviceId) { - // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") - relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } - params.verificationService.onRoomRequestHandledByOtherDevice(event) - } - } - } else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) { - event.getClearContent().toModel()?.let { - if (it.fromDevice != deviceId) { - // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") - relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } - params.verificationService.onRoomRequestHandledByOtherDevice(event) - } - } - } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { - relatesToEventId?.let { - transactionsHandledByOtherDevice.remove(it) - params.verificationService.onRoomRequestHandledByOtherDevice(event) - } - } - - Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}") - return@forEach - } - - if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) { - // Ignore this event, it is directed to another of my devices - Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ") - return@forEach - } - when (event.getClearType()) { - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_DONE -> { - params.verificationService.onRoomEvent(event) - } - EventType.MESSAGE -> { - if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.msgType) { - params.verificationService.onRoomRequestReceived(event) - } - } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 4eab1748b8..f932dd7a69 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -15,59 +15,150 @@ */ package im.vector.matrix.android.internal.crypto.verification -import com.zhuinden.monarchy.Monarchy 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.crypto.verification.VerificationService +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.LocalEcho -import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask -import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask -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.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults +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.api.session.room.model.message.MessageRelationContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.di.DeviceId +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor +import io.realm.Realm +import timber.log.Timber +import java.util.ArrayList import javax.inject.Inject -internal class VerificationMessageLiveObserver @Inject constructor( - @SessionDatabase realmConfiguration: RealmConfiguration, - private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask, +internal class VerificationMessageProcessor @Inject constructor( private val cryptoService: CryptoService, private val verificationService: DefaultVerificationService, - private val taskExecutor: TaskExecutor -) : RealmLiveEntityObserver(realmConfiguration) { + @UserId private val userId: String, + @DeviceId private val deviceId: String? +) : EventInsertLiveProcessor { - override val query = Monarchy.Query { - EventEntity.whereTypes(it, listOf( - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_READY, - EventType.MESSAGE, - EventType.ENCRYPTED) - ) + private val transactionsHandledByOtherDevice = ArrayList() + + private val allowedTypes = listOf( + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_READY, + EventType.MESSAGE, + EventType.ENCRYPTED + ) + + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return allowedTypes.contains(eventType) && !LocalEcho.isLocalEchoId(eventId) } - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - // Should we ignore when it's an initial sync? - val events = changeSet.insertions - .asSequence() - .mapNotNull { results[it]?.asDomain() } - .filterNot { - // ignore local echos - LocalEcho.isLocalEchoId(it.eventId ?: "") - } - .toList() + override suspend fun process(realm: Realm, event: Event) { + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") - roomVerificationUpdateTask.configureWith( - RoomVerificationUpdateTask.Params(events, verificationService, cryptoService) - ).executeBy(taskExecutor) + // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + // the message should be ignored by the receiver. + + if (!VerificationService.isValidRequest(event.ageLocalTs + ?: event.originServerTs)) return Unit.also { + Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated") + } + + // decrypt if needed? + if (event.isEncrypted() && event.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, "") + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.e("## SAS Failed to decrypt event: ${event.eventId}") + verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event) + } + } + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") + + // Relates to is not encrypted + val relatesToEventId = event.content.toModel()?.relatesTo?.eventId + + if (event.senderId == userId) { + // If it's send from me, we need to keep track of Requests or Start + // done from another device of mine + + if (EventType.MESSAGE == event.getClearType()) { + val msgType = event.getClearContent().toModel()?.msgType + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is requested from another device + Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") + event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + } + } + } + } else if (EventType.KEY_VERIFICATION_START == event.getClearType()) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is started from another device + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + verificationService.onRoomRequestHandledByOtherDevice(event) + } + } + } else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) { + event.getClearContent().toModel()?.let { + if (it.fromDevice != deviceId) { + // The verification is started from another device + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + verificationService.onRoomRequestHandledByOtherDevice(event) + } + } + } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { + relatesToEventId?.let { + transactionsHandledByOtherDevice.remove(it) + verificationService.onRoomRequestHandledByOtherDevice(event) + } + } + + Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}") + return + } + + if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) { + // Ignore this event, it is directed to another of my devices + Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ") + return + } + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_DONE -> { + verificationService.onRoomEvent(event) + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.msgType) { + verificationService.onRoomRequestReceived(event) + } + } + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt new file mode 100644 index 0000000000..443ca781bc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 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 + +import com.zhuinden.monarchy.Monarchy +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.internal.crypto.algorithms.olm.OlmDecryptionResult +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.EventInsertEntity +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor +import io.realm.OrderedCollectionChangeSet +import io.realm.RealmConfiguration +import io.realm.RealmResults +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, + private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>, + private val cryptoService: CryptoService) + : RealmLiveEntityObserver(realmConfiguration) { + + override val query = Monarchy.Query { + it.where(EventInsertEntity::class.java) + } + + override fun onChange(results: RealmResults) { + if (!results.isLoaded || results.isEmpty()) { + return + } + Timber.v("EventInsertEntity updated with ${results.size} results in db") + val filteredEventIds = results.mapNotNull { + val shouldProcess = shouldProcess(it) + if (shouldProcess) { + it.eventId + } else { + null + } + } + Timber.v("There are ${filteredEventIds.size} events to process") + observerScope.launch { + awaitTransaction(realmConfiguration) { realm -> + filteredEventIds.forEach { eventId -> + val event = EventEntity.where(realm, eventId).findFirst() + if (event == null) { + Timber.v("Event $eventId not found") + return@forEach + } + val domainEvent = event.asDomain() + decryptIfNeeded(domainEvent) + processors.forEach { + it.process(realm, domainEvent) + } + } + realm.where(EventInsertEntity::class.java).findAll().deleteAllFromRealm() + } + } + } + + private fun decryptIfNeeded(event: Event) { + if (event.isEncrypted() && event.mxDecryptionResult == null) { + try { + val result = cryptoService.decryptEvent(event, event.roomId ?: "") + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.v("Call service: Failed to decrypt event") + // TODO -> we should keep track of this and retry, or aggregation will be broken + } + } + } + + private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { + return processors.any { + it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt index c3ace55e1c..af67bae526 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.session.SessionLifecycleObserver import im.vector.matrix.android.internal.util.createBackgroundHandler import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm +import io.realm.RealmChangeListener import io.realm.RealmConfiguration import io.realm.RealmObject import io.realm.RealmResults @@ -33,7 +34,7 @@ import java.util.concurrent.atomic.AtomicReference internal interface LiveEntityObserver: SessionLifecycleObserver internal abstract class RealmLiveEntityObserver(protected val realmConfiguration: RealmConfiguration) - : LiveEntityObserver, OrderedRealmCollectionChangeListener> { + : LiveEntityObserver, RealmChangeListener> { private companion object { val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND") 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 141403b6d4..11a5616bfb 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 @@ -45,7 +45,6 @@ internal object EventMapper { eventEntity.redacts = event.redacts eventEntity.age = event.unsignedData?.age ?: event.originServerTs eventEntity.unsignedData = uds - eventEntity.decryptionResultJson = event.mxDecryptionResult?.let { MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(it) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt new file mode 100644 index 0000000000..c2744fc721 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 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 + +internal open class EventInsertEntity(var eventId: String = "", + var eventType: String = "" +) : RealmObject() { + + companion object + +} 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 9eceb56141..efe4c4955e 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, + EventInsertEntity::class, TimelineEventEntity::class, FilterEntity::class, GroupEntity::class, 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 d998c41ccb..1020b2bfaf 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 @@ -18,16 +18,25 @@ package im.vector.matrix.android.internal.database.query 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.EventInsertEntity import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.kotlin.where internal fun EventEntity.copyToRealmOrIgnore(realm: Realm): EventEntity { - return realm.where() - .equalTo(EventEntityFields.EVENT_ID, eventId) - .equalTo(EventEntityFields.ROOM_ID, roomId) - .findFirst() ?: realm.copyToRealm(this) + val eventEntity = realm.where() + .equalTo(EventEntityFields.EVENT_ID, eventId) + .equalTo(EventEntityFields.ROOM_ID, roomId) + .findFirst() + return if (eventEntity == null) { + val insertEntity = EventInsertEntity(eventId = eventId, eventType = type) + realm.insert(insertEntity) + // copy this event entity and return it + realm.copyToRealm(this) + } else { + eventEntity + } } internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 5f7f178d8c..72d6f95852 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -166,6 +166,7 @@ internal class DefaultSession @Inject constructor( } eventBus.register(this) timelineEventDecryptor.start() + taskExecutor.executorScope.launch(Dispatchers.Default) { awaitTransaction(realmConfiguration) { realm -> val allRooms = realm.where(RoomEntity::class.java).findAll() @@ -176,9 +177,9 @@ internal class DefaultSession @Inject constructor( if (numberOfTimelineEvents < 30_000L) { Timber.v("Db is low enough") } else { - val hugeChunks = realm.where(ChunkEntity::class.java).greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, 250).findAll() Timber.v("There are ${hugeChunks.size} chunks to clean") + /* for (chunk in hugeChunks) { val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS) val thresholdDisplayIndex = maxDisplayIndex - 250 @@ -203,8 +204,9 @@ internal class DefaultSession @Inject constructor( it.deleteFromRealm() } } - } + */ + } } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt new file mode 100644 index 0000000000..c781d91c04 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 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 + +import im.vector.matrix.android.api.session.events.model.Event +import io.realm.Realm + +internal interface EventInsertLiveProcessor { + + fun shouldProcess(eventId: String, eventType: String): Boolean + + suspend fun process(realm: Realm, event: Event) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index fb05bc68a2..b8aaddd086 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -39,7 +39,8 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer import im.vector.matrix.android.api.session.typing.TypingUsersTracker import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService -import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver +import im.vector.matrix.android.internal.crypto.verification.VerificationMessageProcessor +import im.vector.matrix.android.internal.database.EventInsertLiveObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.Authenticated import im.vector.matrix.android.internal.di.DeviceId @@ -64,16 +65,16 @@ import im.vector.matrix.android.internal.network.httpclient.addSocketFactory import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor import im.vector.matrix.android.internal.network.token.AccessTokenProvider import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider -import im.vector.matrix.android.internal.session.call.CallEventObserver +import im.vector.matrix.android.internal.session.call.CallEventProcessor import im.vector.matrix.android.internal.session.download.DownloadProgressInterceptor import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService import im.vector.matrix.android.internal.session.identity.DefaultIdentityService import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager -import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater -import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver -import im.vector.matrix.android.internal.session.room.prune.EventsPruner -import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver +import im.vector.matrix.android.internal.session.room.EventRelationsAggregationProcessor +import im.vector.matrix.android.internal.session.room.create.RoomCreateEventProcessor +import im.vector.matrix.android.internal.session.room.prune.RedactionEventProcessor +import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventProcessor import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStorageService import im.vector.matrix.android.internal.session.typing.DefaultTypingUsersTracker import im.vector.matrix.android.internal.session.user.accountdata.DefaultAccountDataService @@ -297,27 +298,31 @@ internal abstract class SessionModule { @Binds @IntoSet - abstract fun bindEventsPruner(pruner: EventsPruner): SessionLifecycleObserver + abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor @Binds @IntoSet - abstract fun bindEventRelationsAggregationUpdater(updater: EventRelationsAggregationUpdater): SessionLifecycleObserver + abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor @Binds @IntoSet - abstract fun bindRoomTombstoneEventLiveObserver(observer: RoomTombstoneEventLiveObserver): SessionLifecycleObserver + abstract fun bindRoomTombstoneEventProcessor(processor: RoomTombstoneEventProcessor): EventInsertLiveProcessor @Binds @IntoSet - abstract fun bindRoomCreateEventLiveObserver(observer: RoomCreateEventLiveObserver): SessionLifecycleObserver + abstract fun bindRoomCreateEventProcessor(processor: RoomCreateEventProcessor): EventInsertLiveProcessor @Binds @IntoSet - abstract fun bindVerificationMessageLiveObserver(observer: VerificationMessageLiveObserver): SessionLifecycleObserver + abstract fun bindVerificationMessageProcessor(processor: VerificationMessageProcessor): EventInsertLiveProcessor @Binds @IntoSet - abstract fun bindCallEventObserver(observer: CallEventObserver): SessionLifecycleObserver + abstract fun bindCallEventProcessor(processor: CallEventProcessor): EventInsertLiveProcessor + + @Binds + @IntoSet + abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver @Binds @IntoSet diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt deleted file mode 100644 index 585ecb61ca..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2020 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.call - -import com.zhuinden.monarchy.Monarchy -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.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.di.UserId -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -internal class CallEventObserver @Inject constructor( - @SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - private val task: CallEventsObserverTask -) : RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { - EventEntity.whereTypes(it, listOf( - EventType.CALL_ANSWER, - EventType.CALL_CANDIDATES, - EventType.CALL_INVITE, - EventType.CALL_HANGUP, - EventType.ENCRYPTED) - ) - } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - Timber.v("EventRelationsAggregationUpdater called with ${changeSet.insertions.size} insertions") - - val insertedDomains = changeSet.insertions - .asSequence() - .mapNotNull { results[it]?.asDomain() } - .toList() - - val params = CallEventsObserverTask.Params( - insertedDomains, - userId - ) - observerScope.launch { - task.execute(params) - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt new file mode 100644 index 0000000000..8a4a5407d5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 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.call + +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.internal.di.UserId +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor +import io.realm.Realm +import timber.log.Timber +import javax.inject.Inject + +internal class CallEventProcessor @Inject constructor( + @UserId private val userId: String, + private val callService: DefaultCallSignalingService +) : EventInsertLiveProcessor { + + private val allowedTypes = listOf( + EventType.CALL_ANSWER, + EventType.CALL_CANDIDATES, + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.ENCRYPTED + ) + + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return allowedTypes.contains(eventType) + } + + override suspend fun process(realm: Realm, event: Event) { + update(realm, event) + } + + private fun update(realm: Realm, event: Event) { + val now = System.currentTimeMillis() + // TODO might check if an invite is not closed (hangup/answsered) in the same event batch? + event.roomId ?: return Unit.also { + Timber.w("Event with no room id ${event.eventId}") + } + val age = now - (event.ageLocalTs ?: now) + if (age > 40_000) { + // To old to ring? + return + } + event.ageLocalTs + if (EventType.isCallEvent(event.getClearType())) { + callService.onCallEvent(event) + } + Timber.v("$realm : $userId") + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventsObserverTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventsObserverTask.kt deleted file mode 100644 index 2d96bd3b23..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventsObserverTask.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2020 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.call - -import com.zhuinden.monarchy.Monarchy -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.internal.crypto.algorithms.olm.OlmDecryptionResult -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.awaitTransaction -import io.realm.Realm -import timber.log.Timber -import javax.inject.Inject - -internal interface CallEventsObserverTask : Task { - - data class Params( - val events: List, - val userId: String - ) -} - -internal class DefaultCallEventsObserverTask @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, - private val cryptoService: CryptoService, - private val callService: DefaultCallSignalingService) : CallEventsObserverTask { - - override suspend fun execute(params: CallEventsObserverTask.Params) { - val events = params.events - val userId = params.userId - monarchy.awaitTransaction { realm -> - Timber.v(">>> DefaultCallEventsObserverTask[${params.hashCode()}] called with ${events.size} events") - update(realm, events, userId) - Timber.v("<<< DefaultCallEventsObserverTask[${params.hashCode()}] finished") - } - } - - private fun update(realm: Realm, events: List, userId: String) { - val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answsered) in the same event batch? - events.forEach { event -> - event.roomId ?: return@forEach Unit.also { - Timber.w("Event with no room id ${event.eventId}") - } - val age = now - (event.ageLocalTs ?: now) - if (age > 40_000) { - // To old to ring? - return@forEach - } - event.ageLocalTs - decryptIfNeeded(event) - if (EventType.isCallEvent(event.getClearType())) { - callService.onCallEvent(event) - } - } - Timber.v("$realm : $userId") - } - - private fun decryptIfNeeded(event: Event) { - if (event.isEncrypted() && event.mxDecryptionResult == null) { - try { - val result = cryptoService.decryptEvent(event, event.roomId ?: "") - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.v("Call service: Failed to decrypt event") - // TODO -> we should keep track of this and retry, or aggregation will be broken - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt index a25d198e83..5e75738dec 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt @@ -42,6 +42,4 @@ internal abstract class CallModule { @Binds abstract fun bindGetTurnServerTask(task: DefaultGetTurnServerTask): GetTurnServerTask - @Binds - abstract fun bindCallEventsObserverTask(task: DefaultCallEventsObserverTask): CallEventsObserverTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index b8f8e84bde..52268ab7de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -43,7 +43,8 @@ internal class GroupSummaryUpdater @Inject constructor( override val query = Monarchy.Query { GroupEntity.where(it) } - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { + override fun onChange(results: RealmResults) { + /* // `insertions` for new groups and `changes` to handle left groups val modifiedGroupEntity = (changeSet.insertions + changeSet.changes) .asSequence() @@ -63,6 +64,7 @@ internal class GroupSummaryUpdater @Inject constructor( deleteGroups(it) } } + */ } private fun fetchGroupsData(groupIds: List) { 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/EventRelationsAggregationProcessor.kt similarity index 71% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt index c84b39118e..9cb9b7254b 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/EventRelationsAggregationProcessor.kt @@ -15,9 +15,7 @@ */ package im.vector.matrix.android.internal.session.room -import com.zhuinden.monarchy.Monarchy 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.AggregatedAnnotation import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType @@ -32,7 +30,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent import im.vector.matrix.android.api.session.room.model.relation.ReactionContent -import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.EventMapper @@ -47,21 +44,12 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.awaitTransaction +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor import io.realm.Realm import timber.log.Timber import javax.inject.Inject -internal interface EventRelationsAggregationTask : Task { - - data class Params( - val events: List, - val userId: String - ) -} - enum class VerificationState { REQUEST, WAITING, @@ -89,161 +77,146 @@ private fun VerificationState?.toState(newState: VerificationState): Verificatio return newState } -/** - * Called by EventRelationAggregationUpdater, when new events that can affect relations are inserted in base. - */ -internal class DefaultEventRelationsAggregationTask @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, - private val cryptoService: CryptoService) : EventRelationsAggregationTask { +internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String, + private val cryptoService: CryptoService +) : EventInsertLiveProcessor { - // OPT OUT serer aggregation until API mature enough - private val SHOULD_HANDLE_SERVER_AGREGGATION = false + private val allowedTypes = listOf( + EventType.MESSAGE, + EventType.REDACTION, + EventType.REACTION, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + // TODO Add ? + // EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_KEY, + EventType.ENCRYPTED + ) - override suspend fun execute(params: EventRelationsAggregationTask.Params) { - val events = params.events - val userId = params.userId - monarchy.awaitTransaction { realm -> - Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events") - update(realm, events, userId) - Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished") - } + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return allowedTypes.contains(eventType) } - private fun update(realm: Realm, events: List, userId: String) { - events.forEach { event -> - try { // Temporary catch, should be removed - val roomId = event.roomId - if (roomId == null) { - Timber.w("Event has no room id ${event.eventId}") - return@forEach + override suspend fun process(realm: Realm, event: Event) { + try { // Temporary catch, should be removed + val roomId = event.roomId + if (roomId == null) { + Timber.w("Event has no room id ${event.eventId}") + return + } + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") + when (event.getClearType()) { + EventType.REACTION -> { + // we got a reaction!! + Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") + handleReaction(event, roomId, realm, userId, isLocalEcho) } - val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - when (event.type) { - EventType.REACTION -> { - // we got a reaction!! - Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, userId, isLocalEcho) - } - EventType.MESSAGE -> { - if (event.unsignedData?.relations?.annotations != null) { - Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") - handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) + EventType.MESSAGE -> { + if (event.unsignedData?.relations?.annotations != null) { + Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") + handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) - EventAnnotationsSummaryEntity.where(realm, event.eventId - ?: "").findFirst()?.let { - TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId - ?: "").findFirst()?.let { tet -> - tet.annotations = it - } - } - } - - val content: MessageContent? = event.content.toModel() - if (content?.relatesTo?.type == RelationType.REPLACE) { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - // A replace! - handleReplace(realm, event, content, roomId, isLocalEcho) - } else if (content?.relatesTo?.type == RelationType.RESPONSE) { - Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, content, roomId, isLocalEcho) - } - } - - EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { - Timber.v("## SAS REF in room $roomId for event ${event.eventId}") - event.content.toModel()?.relatesTo?.let { - if (it.type == RelationType.REFERENCE && it.eventId != null) { - handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId) + EventAnnotationsSummaryEntity.where(realm, event.eventId + ?: "").findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId + ?: "").findFirst()?.let { tet -> + tet.annotations = it } } } - EventType.ENCRYPTED -> { - // Relation type is in clear - val encryptedEventContent = event.content.toModel() - if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE - || encryptedEventContent?.relatesTo?.type == RelationType.RESPONSE - ) { - // we need to decrypt if needed - decryptIfNeeded(event) - event.getClearContent().toModel()?.let { - if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - // A replace! - handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) { - Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } + val content: MessageContent? = event.content.toModel() + if (content?.relatesTo?.type == RelationType.REPLACE) { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + // A replace! + handleReplace(realm, event, content, roomId, isLocalEcho) + } else if (content?.relatesTo?.type == RelationType.RESPONSE) { + Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") + handleResponse(realm, userId, event, content, roomId, isLocalEcho) + } + } + + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_KEY -> { + Timber.v("## SAS REF in room $roomId for event ${event.eventId}") + event.content.toModel()?.relatesTo?.let { + if (it.type == RelationType.REFERENCE && it.eventId != null) { + handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId) + } + } + } + + EventType.ENCRYPTED -> { + // Relation type is in clear + val encryptedEventContent = event.content.toModel() + if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE + || encryptedEventContent?.relatesTo?.type == RelationType.RESPONSE + ) { + // we need to decrypt if needed + event.getClearContent().toModel()?.let { + if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + // A replace! + handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) { + Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") + handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } - } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { - decryptIfNeeded(event) - when (event.getClearType()) { - EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { - Timber.v("## SAS REF in room $roomId for event ${event.eventId}") - encryptedEventContent.relatesTo.eventId?.let { - handleVerification(realm, event, roomId, isLocalEcho, it, userId) - } + } + } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { + when (event.getClearType()) { + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_KEY -> { + Timber.v("## SAS REF in room $roomId for event ${event.eventId}") + encryptedEventContent.relatesTo.eventId?.let { + handleVerification(realm, event, roomId, isLocalEcho, it, userId) } } } } - EventType.REDACTION -> { - val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } - ?: return@forEach - when (eventToPrune.type) { - EventType.MESSAGE -> { - Timber.d("REDACTION for message ${eventToPrune.eventId}") + } + EventType.REDACTION -> { + val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } + ?: return + when (eventToPrune.type) { + EventType.MESSAGE -> { + Timber.d("REDACTION for message ${eventToPrune.eventId}") // val unsignedData = EventMapper.map(eventToPrune).unsignedData // ?: UnsignedData(null, null) - // was this event a m.replace - val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() - if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { - handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) - } - } - EventType.REACTION -> { - handleReactionRedact(eventToPrune, realm, userId) + // was this event a m.replace + val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() + if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { + handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) } } + EventType.REACTION -> { + handleReactionRedact(eventToPrune, realm, userId) + } } - else -> Timber.v("UnHandled event ${event.eventId}") } - } catch (t: Throwable) { - Timber.e(t, "## Should not happen ") + else -> Timber.v("UnHandled event ${event.eventId}") } + } catch (t: Throwable) { + Timber.e(t, "## Should not happen ") } } - private fun decryptIfNeeded(event: Event) { - if (event.mxDecryptionResult == null) { - try { - val result = cryptoService.decryptEvent(event, "") - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.v("Failed to decrypt e2e replace") - // TODO -> we should keep track of this and retry, or aggregation will be broken - } - } - } + // OPT OUT serer aggregation until API mature enough + private val SHOULD_HANDLE_SERVER_AGREGGATION = false private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) { val eventId = event.eventId ?: return diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt deleted file mode 100644 index 7ddcf3542d..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ /dev/null @@ -1,76 +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 com.zhuinden.monarchy.Monarchy -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.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.di.UserId -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -/** - * Acts as a listener of incoming messages in order to incrementally computes a summary of annotations. - * For reactions will build a EventAnnotationsSummaryEntity, ans for edits a EditAggregatedSummaryEntity. - * The summaries can then be extracted and added (as a decoration) to a TimelineEvent for final display. - */ -internal class EventRelationsAggregationUpdater @Inject constructor( - @SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - private val task: EventRelationsAggregationTask) : - RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { - EventEntity.whereTypes(it, listOf( - EventType.MESSAGE, - EventType.REDACTION, - EventType.REACTION, - EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_MAC, - // TODO Add ? - // EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY, - EventType.ENCRYPTED) - ) - } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - Timber.v("EventRelationsAggregationUpdater called with ${changeSet.insertions.size} insertions") - - val insertedDomains = changeSet.insertions - .asSequence() - .mapNotNull { results[it]?.asDomain() } - .toList() - val params = EventRelationsAggregationTask.Params( - insertedDomains, - userId - ) - observerScope.launch { - task.execute(params) - } - } -} 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 5e84920fbd..ca59f9b7bb 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 @@ -44,8 +44,6 @@ import im.vector.matrix.android.internal.session.room.membership.joining.InviteT import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask -import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask -import im.vector.matrix.android.internal.session.room.prune.PruneEventTask import im.vector.matrix.android.internal.session.room.read.DefaultMarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask @@ -129,9 +127,6 @@ internal abstract class RoomModule { @Binds abstract fun bindFileService(service: DefaultFileService): FileService - @Binds - abstract fun bindEventRelationsAggregationTask(task: DefaultEventRelationsAggregationTask): EventRelationsAggregationTask - @Binds abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask @@ -156,9 +151,6 @@ internal abstract class RoomModule { @Binds abstract fun bindLoadRoomMembersTask(task: DefaultLoadRoomMembersTask): LoadRoomMembersTask - @Binds - abstract fun bindPruneEventTask(task: DefaultPruneEventTask): PruneEventTask - @Binds abstract fun bindSetReadMarkersTask(task: DefaultSetReadMarkersTask): SetReadMarkersTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt deleted file mode 100644 index fb3880e38d..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt +++ /dev/null @@ -1,72 +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.create - -import com.zhuinden.monarchy.Monarchy -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.VersioningState -import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent -import im.vector.matrix.android.internal.database.RealmLiveEntityObserver -import im.vector.matrix.android.internal.database.awaitTransaction -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.query.where -import im.vector.matrix.android.internal.database.query.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import javax.inject.Inject - -internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase - realmConfiguration: RealmConfiguration) - : RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { - EventEntity.whereTypes(it, listOf(EventType.STATE_ROOM_CREATE)) - } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - changeSet.insertions - .asSequence() - .mapNotNull { - results[it]?.asDomain() - } - .toList() - .also { - observerScope.launch { - handleRoomCreateEvents(it) - } - } - } - - private suspend fun handleRoomCreateEvents(createEvents: List) = awaitTransaction(realmConfiguration) { realm -> - for (event in createEvents) { - val createRoomContent = event.getClearContent().toModel() - val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: continue - - val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() - ?: RoomSummaryEntity(predecessorRoomId) - predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED - realm.insertOrUpdate(predecessorRoomSummary) - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt new file mode 100644 index 0000000000..f2a3da3a30 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 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.create + +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.VersioningState +import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor +import io.realm.Realm +import javax.inject.Inject + +internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor { + + override suspend fun process(realm: Realm, event: Event) { + val createRoomContent = event.getClearContent().toModel() + val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return + + val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() + ?: RoomSummaryEntity(predecessorRoomId) + predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED + realm.insertOrUpdate(predecessorRoomSummary) + } + + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return eventType == EventType.STATE_ROOM_CREATE + } +} 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 deleted file mode 100644 index 27e00c75ab..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.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.prune - -import com.zhuinden.monarchy.Monarchy -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.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -/** - * Listens to the database for the insertion of any redaction event. - * As it will actually delete the content, it should be called last in the list of listener. - */ -internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - private val pruneEventTask: PruneEventTask) : - RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { EventEntity.whereTypes(it, listOf(EventType.REDACTION)) } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - Timber.v("Event pruner called with ${changeSet.insertions.size} insertions") - - val insertedDomains = changeSet.insertions - .asSequence() - .mapNotNull { results[it]?.asDomain() } - .toList() - - observerScope.launch { - val params = PruneEventTask.Params(insertedDomains) - pruneEventTask.execute(params) - } - } -} 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/RedactionEventProcessor.kt similarity index 87% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/RedactionEventProcessor.kt index b801843d18..2f61a98df3 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/RedactionEventProcessor.kt @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package im.vector.matrix.android.internal.session.room.prune -import com.zhuinden.monarchy.Monarchy 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.LocalEcho @@ -27,28 +27,23 @@ 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.di.SessionDatabase -import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.awaitTransaction +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor import io.realm.Realm import timber.log.Timber import javax.inject.Inject -internal interface PruneEventTask : Task { +/** + * Listens to the database for the insertion of any redaction event. + * As it will actually delete the content, it should be called last in the list of listener. + */ +internal class RedactionEventProcessor @Inject constructor() : EventInsertLiveProcessor { - data class Params( - val redactionEvents: List - ) -} + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return eventType == EventType.REDACTION + } -internal class DefaultPruneEventTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : PruneEventTask { - - override suspend fun execute(params: PruneEventTask.Params) { - monarchy.awaitTransaction { realm -> - params.redactionEvents.forEach { event -> - pruneEvent(realm, event) - } - } + override suspend fun process(realm: Realm, event: Event) { + pruneEvent(realm, event) } private fun pruneEvent(realm: Realm, redactionEvent: Event) { 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 2b1f50d000..c93d1e7e4b 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 @@ -174,24 +174,13 @@ internal class DefaultTimeline( backgroundRealm.set(realm) roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - roomEntity?.sendingTimelineEvents?.addChangeListener { events -> - // Remove in memory as soon as they are known by database - events.forEach { te -> - inMemorySendingEvents.removeAll { te.eventId == it.eventId } - } - postSnapshot() - } - nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() filteredEvents = nonFilteredEvents.where() .filterEventsWithSettings() .findAll() handleInitialLoad() - nonFilteredEvents.addChangeListener(eventsChangeListener) - eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId) .findAllAsync() - .also { it.addChangeListener(relationsListener) } if (settings.shouldHandleHiddenReadReceipts()) { hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index ddfa7e91fe..ae10ccf80d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -125,7 +125,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`) .filterReceiptsWithSettings() .findAllAsync() - .also { it.addChangeListener(hiddenReadReceiptsListener) } + //.also { it.addChangeListener(hiddenReadReceiptsListener) } } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt deleted file mode 100644 index 7ca8aaa1d6..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt +++ /dev/null @@ -1,75 +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.tombstone - -import com.zhuinden.monarchy.Monarchy -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.VersioningState -import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent -import im.vector.matrix.android.internal.database.RealmLiveEntityObserver -import im.vector.matrix.android.internal.database.awaitTransaction -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.query.where -import im.vector.matrix.android.internal.database.query.whereTypes -import im.vector.matrix.android.internal.di.SessionDatabase -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import javax.inject.Inject - -internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDatabase - realmConfiguration: RealmConfiguration) - : RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { - EventEntity.whereTypes(it, listOf(EventType.STATE_ROOM_TOMBSTONE)) - } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - changeSet.insertions - .asSequence() - .mapNotNull { - results[it]?.asDomain() - } - .toList() - .also { - observerScope.launch { - handleRoomTombstoneEvents(it) - } - } - } - - private suspend fun handleRoomTombstoneEvents(tombstoneEvents: List) = awaitTransaction(realmConfiguration) { realm -> - for (event in tombstoneEvents) { - if (event.roomId == null) continue - val createRoomContent = event.getClearContent().toModel() - if (createRoomContent?.replacementRoomId == null) continue - - val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst() - ?: RoomSummaryEntity(event.roomId) - if (predecessorRoomSummary.versioningState == VersioningState.NONE) { - predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED - } - realm.insertOrUpdate(predecessorRoomSummary) - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt new file mode 100644 index 0000000000..68ef2981d8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 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.tombstone + +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.VersioningState +import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.EventInsertLiveProcessor +import io.realm.Realm +import javax.inject.Inject + +internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor { + + override suspend fun process(realm: Realm, event: Event) { + if (event.roomId == null) return + val createRoomContent = event.getClearContent().toModel() + if (createRoomContent?.replacementRoomId == null) return + + val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst() + ?: RoomSummaryEntity(event.roomId) + if (predecessorRoomSummary.versioningState == VersioningState.NONE) { + predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED + } + realm.insertOrUpdate(predecessorRoomSummary) + } + + override fun shouldProcess(eventId: String, eventType: String): Boolean { + return eventType == EventType.STATE_ROOM_TOMBSTONE + } +} From 283f32479db0a18f7f746a44c7461a7389108045 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 3 Jul 2020 21:11:54 +0200 Subject: [PATCH 3/8] Rebranch timeline + continue clean up strategy --- .../internal/database/DatabaseCleaner.kt | 98 +++++++++++++++++++ .../internal/session/DefaultSession.kt | 43 -------- .../android/internal/session/SessionModule.kt | 5 + .../internal/session/room/RoomModule.kt | 6 +- .../session/room/timeline/DefaultTimeline.kt | 89 ++++++++--------- .../room/timeline/DefaultTimelineService.kt | 4 +- ...teTask.kt => FetchTokenAndPaginateTask.kt} | 30 ++++-- .../timeline/TimelineHiddenReadReceipts.kt | 2 +- 8 files changed, 169 insertions(+), 108 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/{FetchNextTokenAndPaginateTask.kt => FetchTokenAndPaginateTask.kt} (67%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt new file mode 100644 index 0000000000..a13cd6e078 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 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 + +import im.vector.matrix.android.internal.database.helper.nextDisplayIndex +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.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.model.TimelineEventEntityFields +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.session.SessionLifecycleObserver +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection +import im.vector.matrix.android.internal.task.TaskExecutor +import io.realm.Realm +import io.realm.RealmConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +private const val MAX_NUMBER_OF_EVENTS = 35_000L +private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300 + +/** + * This class makes sure to stay under a maximum number of events as it makes Realm to be unusable when listening to events + * when the database is getting too big. + */ +internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration, + private val taskExecutor: TaskExecutor) : SessionLifecycleObserver { + + override fun onStart() { + taskExecutor.executorScope.launch(Dispatchers.Default) { + awaitTransaction(realmConfiguration) { realm -> + val allRooms = realm.where(RoomEntity::class.java).findAll() + Timber.v("There are ${allRooms.size} rooms in this session") + cleanUp(realm, MAX_NUMBER_OF_EVENTS / 2L) + } + } + } + + private suspend fun cleanUp(realm: Realm, threshold: Long) { + val numberOfEvents = realm.where(EventEntity::class.java).findAll().size + val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size + Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") + if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS) { + Timber.v("Db is low enough") + } else { + val thresholdChunks = realm.where(ChunkEntity::class.java) + .greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, threshold) + .findAll() + + Timber.v("There are ${thresholdChunks.size} chunks to clean with more than $threshold events") + for (chunk in thresholdChunks) { + val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS) + val thresholdDisplayIndex = maxDisplayIndex - threshold + val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll() + Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}") + chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size + eventsToRemove.forEach { + val canDeleteRoot = it.root?.stateKey == null + if (canDeleteRoot) { + it.root?.deleteFromRealm() + } + it.readReceipts?.readReceipts?.deleteAllFromRealm() + it.readReceipts?.deleteFromRealm() + it.annotations?.apply { + editSummary?.deleteFromRealm() + pollResponseSummary?.deleteFromRealm() + referencesSummaryEntity?.deleteFromRealm() + reactionsSummary.deleteAllFromRealm() + } + it.annotations?.deleteFromRealm() + it.readReceipts?.deleteFromRealm() + it.deleteFromRealm() + } + // We reset the prevToken so we will need to fetch again. + chunk.prevToken = null + } + cleanUp(realm, (threshold / 1.5).toLong()) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 466ad6fc7d..e7ab2a7926 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -164,49 +164,6 @@ internal class DefaultSession @Inject constructor( } eventBus.register(this) timelineEventDecryptor.start() - - taskExecutor.executorScope.launch(Dispatchers.Default) { - awaitTransaction(realmConfiguration) { realm -> - val allRooms = realm.where(RoomEntity::class.java).findAll() - val numberOfEvents = realm.where(EventEntity::class.java).findAll().size - val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size - Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") - Timber.v("Number of rooms in db: ${allRooms.size}") - if (numberOfTimelineEvents < 30_000L) { - Timber.v("Db is low enough") - } else { - val hugeChunks = realm.where(ChunkEntity::class.java).greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, 250).findAll() - Timber.v("There are ${hugeChunks.size} chunks to clean") - /* - for (chunk in hugeChunks) { - val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS) - val thresholdDisplayIndex = maxDisplayIndex - 250 - val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll() - Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}") - chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size - eventsToRemove.forEach { - val canDeleteRoot = it.root?.stateKey == null - if (canDeleteRoot) { - it.root?.deleteFromRealm() - } - it.readReceipts?.readReceipts?.deleteAllFromRealm() - it.readReceipts?.deleteFromRealm() - it.annotations?.apply { - editSummary?.deleteFromRealm() - pollResponseSummary?.deleteFromRealm() - referencesSummaryEntity?.deleteFromRealm() - reactionsSummary.deleteAllFromRealm() - } - it.annotations?.deleteFromRealm() - it.readReceipts?.deleteFromRealm() - it.deleteFromRealm() - } - } - - */ - } - } - } } override fun requireBackgroundSync() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index b8aaddd086..8aabd709ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.typing.TypingUsersTracker import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService import im.vector.matrix.android.internal.crypto.verification.VerificationMessageProcessor +import im.vector.matrix.android.internal.database.DatabaseCleaner import im.vector.matrix.android.internal.database.EventInsertLiveObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.Authenticated @@ -340,6 +341,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindIdentityService(observer: DefaultIdentityService): SessionLifecycleObserver + @Binds + @IntoSet + abstract fun bindDatabaseCleaner(observer: DatabaseCleaner): SessionLifecycleObserver + @Binds abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService 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 ca59f9b7bb..7fa9c1526a 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 @@ -62,10 +62,10 @@ import im.vector.matrix.android.internal.session.room.tags.AddTagToRoomTask import im.vector.matrix.android.internal.session.room.tags.DefaultAddTagToRoomTask import im.vector.matrix.android.internal.session.room.tags.DefaultDeleteTagFromRoomTask import im.vector.matrix.android.internal.session.room.tags.DeleteTagFromRoomTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask +import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask -import im.vector.matrix.android.internal.session.room.timeline.FetchNextTokenAndPaginateTask +import im.vector.matrix.android.internal.session.room.timeline.FetchTokenAndPaginateTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask @@ -176,7 +176,7 @@ internal abstract class RoomModule { abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask @Binds - abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchNextTokenAndPaginateTask): FetchNextTokenAndPaginateTask + abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchTokenAndPaginateTask): FetchTokenAndPaginateTask @Binds abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask 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 c93d1e7e4b..1822587972 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 @@ -29,17 +29,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper -import im.vector.matrix.android.internal.database.mapper.asDomain 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.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.TimelineEventFilter import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.database.query.whereInRoom import im.vector.matrix.android.internal.database.query.whereRoomId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -72,7 +69,7 @@ internal class DefaultTimeline( private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, - private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, @@ -98,9 +95,7 @@ internal class DefaultTimeline( private lateinit var nonFilteredEvents: RealmResults private lateinit var filteredEvents: RealmResults - private lateinit var eventRelations: RealmResults - - private var roomEntity: RoomEntity? = null + private lateinit var sendingEvents: RealmResults private var prevDisplayIndex: Int? = null private var nextDisplayIndex: Int? = null @@ -119,23 +114,10 @@ internal class DefaultTimeline( if (!results.isLoaded || !results.isValid) { return@OrderedRealmCollectionChangeListener } + results.createSnapshot() handleUpdates(results, changeSet) } - private val relationsListener = OrderedRealmCollectionChangeListener> { collection, changeSet -> - var hasChange = false - - (changeSet.insertions + changeSet.changes).forEach { - val eventRelations = collection[it] - if (eventRelations != null) { - hasChange = rebuildEvent(eventRelations.eventId) { te -> - te.copy(annotations = eventRelations.asDomain()) - } || hasChange - } - } - if (hasChange) postSnapshot() - } - // Public methods ****************************************************************************** override fun paginate(direction: Timeline.Direction, count: Int) { @@ -173,15 +155,23 @@ internal class DefaultTimeline( val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) - roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() + ?: throw IllegalStateException("Can't open a timeline without a room") + + sendingEvents = roomEntity.sendingTimelineEvents.where().filterEventsWithSettings().findAll() + sendingEvents.addChangeListener { events -> + // Remove in memory as soon as they are known by database + events.forEach { te -> + inMemorySendingEvents.removeAll { te.eventId == it.eventId } + } + postSnapshot() + } nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() filteredEvents = nonFilteredEvents.where() .filterEventsWithSettings() .findAll() + filteredEvents.addChangeListener(eventsChangeListener) handleInitialLoad() - eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId) - .findAllAsync() - if (settings.shouldHandleHiddenReadReceipts()) { hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) } @@ -202,9 +192,8 @@ internal class DefaultTimeline( cancelableBag.cancel() BACKGROUND_HANDLER.removeCallbacksAndMessages(null) BACKGROUND_HANDLER.post { - roomEntity?.sendingTimelineEvents?.removeAllChangeListeners() - if (this::eventRelations.isInitialized) { - eventRelations.removeAllChangeListeners() + if (this::sendingEvents.isInitialized) { + sendingEvents.removeAllChangeListeners() } if (this::nonFilteredEvents.isInitialized) { nonFilteredEvents.removeAllChangeListeners() @@ -303,7 +292,7 @@ internal class DefaultTimeline( listeners.clear() } - // TimelineHiddenReadReceipts.Delegate +// TimelineHiddenReadReceipts.Delegate override fun rebuildEvent(eventId: String, readReceipts: List): Boolean { return rebuildEvent(eventId) { te -> @@ -336,7 +325,7 @@ internal class DefaultTimeline( } } - // Private methods ***************************************************************************** +// Private methods ***************************************************************************** private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean { return builtEventsIdMap[eventId]?.let { builtIndex -> @@ -400,20 +389,16 @@ internal class DefaultTimeline( } private fun buildSendingEvents(): List { - val sendingEvents = ArrayList() + val builtSendingEvents = ArrayList() if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) { - sendingEvents.addAll(inMemorySendingEvents.filterEventsWithSettings()) - roomEntity?.sendingTimelineEvents - ?.where() - ?.filterEventsWithSettings() - ?.findAll() - ?.forEach { timelineEventEntity -> - if (sendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) { - sendingEvents.add(timelineEventMapper.map(timelineEventEntity)) - } - } + builtSendingEvents.addAll(inMemorySendingEvents.filterEventsWithSettings()) + sendingEvents.forEach { timelineEventEntity -> + if (builtSendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) { + builtSendingEvents.add(timelineEventMapper.map(timelineEventEntity)) + } + } } - return sendingEvents + return builtSendingEvents } private fun canPaginate(direction: Timeline.Direction): Boolean { @@ -514,19 +499,25 @@ internal class DefaultTimeline( val currentChunk = getLiveChunk() val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken if (token == null) { - if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse()) { - // We are in the case that next event exists, but we do not know the next token. - // Fetch (again) the last event to get a nextToken - val lastKnownEventId = nonFilteredEvents.firstOrNull()?.eventId + if (direction == Timeline.Direction.BACKWARDS || + (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse())) { + // We are in the case where event exists, but we do not know the token. + // Fetch (again) the last event to get a token + val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) { + nonFilteredEvents.firstOrNull()?.eventId + } else { + nonFilteredEvents.lastOrNull()?.eventId + } if (lastKnownEventId == null) { updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } } else { - val params = FetchNextTokenAndPaginateTask.Params( + val params = FetchTokenAndPaginateTask.Params( roomId = roomId, limit = limit, + direction = direction.toPaginationDirection(), lastKnownEventId = lastKnownEventId ) - cancelableBag += fetchNextTokenAndPaginateTask + cancelableBag += fetchTokenAndPaginateTask .configureWith(params) { this.callback = createPaginationCallback(limit, direction) } @@ -755,7 +746,7 @@ internal class DefaultTimeline( } } -// Extension methods *************************************************************************** + // Extension methods *************************************************************************** private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS 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 449061c2f7..5723568197 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 @@ -43,7 +43,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv private val contextOfEventTask: GetContextOfEventTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, - private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper ) : TimelineService { @@ -66,7 +66,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), eventBus = eventBus, eventDecryptor = eventDecryptor, - fetchNextTokenAndPaginateTask = fetchNextTokenAndPaginateTask + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchTokenAndPaginateTask.kt similarity index 67% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index 6579e0031a..3c1f6b18bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -28,38 +28,48 @@ import im.vector.matrix.android.internal.util.awaitTransaction import org.greenrobot.eventbus.EventBus import javax.inject.Inject -internal interface FetchNextTokenAndPaginateTask : Task { +internal interface FetchTokenAndPaginateTask : Task { data class Params( val roomId: String, val lastKnownEventId: String, + val direction: PaginationDirection, val limit: Int ) } -internal class DefaultFetchNextTokenAndPaginateTask @Inject constructor( +internal class DefaultFetchTokenAndPaginateTask @Inject constructor( private val roomAPI: RoomAPI, @SessionDatabase private val monarchy: Monarchy, private val filterRepository: FilterRepository, private val paginationTask: PaginationTask, private val eventBus: EventBus -) : FetchNextTokenAndPaginateTask { +) : FetchTokenAndPaginateTask { - override suspend fun execute(params: FetchNextTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { + override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() val response = executeRequest(eventBus) { apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } - if (response.end == null) { - throw IllegalStateException("No next token found") + val fromToken = if (params.direction == PaginationDirection.FORWARDS) { + response.end + } else { + response.start } - monarchy.awaitTransaction { - ChunkEntity.findIncludingEvent(it, params.lastKnownEventId)?.nextToken = response.end + ?: throw IllegalStateException("No token found") + + monarchy.awaitTransaction { realm -> + val chunkToUpdate = ChunkEntity.findIncludingEvent(realm, params.lastKnownEventId) + if (params.direction == PaginationDirection.FORWARDS) { + chunkToUpdate?.nextToken = fromToken + } else { + chunkToUpdate?.prevToken = fromToken + } } val paginationParams = PaginationTask.Params( roomId = params.roomId, - from = response.end, - direction = PaginationDirection.FORWARDS, + from = fromToken, + direction = params.direction, limit = params.limit ) return paginationTask.execute(paginationParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index ae10ccf80d..ddfa7e91fe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -125,7 +125,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`) .filterReceiptsWithSettings() .findAllAsync() - //.also { it.addChangeListener(hiddenReadReceiptsListener) } + .also { it.addChangeListener(hiddenReadReceiptsListener) } } /** From 7434aed43fe0c048fd31423f28276a109fde6ee3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 3 Jul 2020 21:12:27 +0200 Subject: [PATCH 4/8] Use writeAsync for localEcho --- .../internal/session/room/send/LocalEchoEventFactory.kt | 5 +---- .../internal/session/room/send/LocalEchoRepository.kt | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 08dc4e80d8..cdb5f7dc7a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -62,7 +62,6 @@ import im.vector.matrix.android.internal.session.content.ThumbnailExtractor import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.StringProvider -import kotlinx.coroutines.launch import javax.inject.Inject /** @@ -474,9 +473,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createLocalEcho(event: Event) { checkNotNull(event.roomId) { "Your event should have a roomId" } - taskExecutor.executorScope.launch { - localEchoRepository.createLocalEcho(event) - } + localEchoRepository.createLocalEcho(event) } companion object { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt index fb47c16943..2d2c64ef7f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt @@ -48,7 +48,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private private val eventBus: EventBus, private val timelineEventMapper: TimelineEventMapper) { - suspend fun createLocalEcho(event: Event) { + fun createLocalEcho(event: Event) { val roomId = event.roomId ?: throw IllegalStateException("You should have set a roomId for your event") val senderId = event.senderId ?: throw IllegalStateException("You should have set a senderIf for your event") if (event.eventId == null) { @@ -70,8 +70,8 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } val timelineEvent = timelineEventMapper.map(timelineEventEntity) eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent)) - monarchy.awaitTransaction { realm -> - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@awaitTransaction + monarchy.writeAsync { realm -> + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync roomEntity.sendingTimelineEvents.add(0, timelineEventEntity) roomSummaryUpdater.update(realm, roomId) } From 32d2cea7f8dca84afbc25068e1325c45d46d750c Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 6 Jul 2020 18:38:30 +0200 Subject: [PATCH 5/8] EventInsert: add InsertType to avoid trying to process events we shouldn't --- ...ver.kt => VerificationMessageProcessor.kt} | 6 +++- .../internal/database/DatabaseCleaner.kt | 10 ++++--- .../database/EventInsertLiveObserver.kt | 19 ++++++------ .../database/RealmLiveEntityObserver.kt | 3 +- .../database/model/EventInsertEntity.kt | 14 ++++++++- .../database/model/EventInsertType.java | 24 +++++++++++++++ .../database/query/EventEntityQueries.kt | 7 +++-- .../session/EventInsertLiveProcessor.kt | 3 +- .../session/call/CallEventProcessor.kt | 7 ++++- .../EventRelationsAggregationProcessor.kt | 3 +- .../room/create/RoomCreateEventProcessor.kt | 3 +- .../room/membership/LoadRoomMembersTask.kt | 4 ++- .../room/prune/RedactionEventProcessor.kt | 3 +- .../session/room/send/LocalEchoRepository.kt | 6 ++++ .../session/room/timeline/DefaultTimeline.kt | 4 ++- .../room/timeline/TokenChunkEventPersistor.kt | 5 ++-- .../tombstone/RoomTombstoneEventProcessor.kt | 3 +- .../internal/session/sync/RoomSyncHandler.kt | 29 +++++++++++++------ 18 files changed, 115 insertions(+), 38 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerificationMessageLiveObserver.kt => VerificationMessageProcessor.kt} (97%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertType.java diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageProcessor.kt similarity index 97% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageProcessor.kt index f932dd7a69..c266d965cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageProcessor.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.EventInsertLiveProcessor @@ -58,7 +59,10 @@ internal class VerificationMessageProcessor @Inject constructor( EventType.ENCRYPTED ) - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { + if (insertType != EventInsertType.INCREMENTAL_SYNC) { + return false + } return allowedTypes.contains(eventType) && !LocalEcho.isLocalEchoId(eventId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt index a13cd6e078..ca2126e621 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/DatabaseCleaner.kt @@ -34,12 +34,14 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -private const val MAX_NUMBER_OF_EVENTS = 35_000L +private const val MAX_NUMBER_OF_EVENTS_IN_DB = 35_000L private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300 /** * This class makes sure to stay under a maximum number of events as it makes Realm to be unusable when listening to events - * when the database is getting too big. + * when the database is getting too big. This will try incrementally to remove the biggest chunks until we get below the threshold. + * We make sure to still have a minimum number of events so it's not becoming unusable. + * So this won't work for users with a big number of very active rooms. */ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor) : SessionLifecycleObserver { @@ -49,7 +51,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val awaitTransaction(realmConfiguration) { realm -> val allRooms = realm.where(RoomEntity::class.java).findAll() Timber.v("There are ${allRooms.size} rooms in this session") - cleanUp(realm, MAX_NUMBER_OF_EVENTS / 2L) + cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) } } } @@ -58,7 +60,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val val numberOfEvents = realm.where(EventEntity::class.java).findAll().size val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") - if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS) { + if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS_IN_DB) { Timber.v("Db is low enough") } else { val thresholdChunks = realm.where(ChunkEntity::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt index 443ca781bc..f0884918c0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/EventInsertLiveObserver.kt @@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.EventInsertEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.session.EventInsertLiveProcessor -import io.realm.OrderedCollectionChangeSet import io.realm.RealmConfiguration import io.realm.RealmResults import kotlinx.coroutines.launch @@ -48,18 +47,18 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real return } Timber.v("EventInsertEntity updated with ${results.size} results in db") - val filteredEventIds = results.mapNotNull { - val shouldProcess = shouldProcess(it) - if (shouldProcess) { - it.eventId + val filteredEvents = results.mapNotNull { + if (shouldProcess(it)) { + results.realm.copyFromRealm(it) } else { null } } - Timber.v("There are ${filteredEventIds.size} events to process") + Timber.v("There are ${filteredEvents.size} events to process") observerScope.launch { awaitTransaction(realmConfiguration) { realm -> - filteredEventIds.forEach { eventId -> + filteredEvents.forEach { eventInsert -> + val eventId = eventInsert.eventId val event = EventEntity.where(realm, eventId).findFirst() if (event == null) { Timber.v("Event $eventId not found") @@ -67,7 +66,9 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real } val domainEvent = event.asDomain() decryptIfNeeded(domainEvent) - processors.forEach { + processors.filter { + it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType) + }.forEach { it.process(realm, domainEvent) } } @@ -95,7 +96,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { return processors.any { - it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType) + it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt index af67bae526..3f0dc4cddd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.database import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.session.SessionLifecycleObserver import im.vector.matrix.android.internal.util.createBackgroundHandler -import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm import io.realm.RealmChangeListener import io.realm.RealmConfiguration @@ -31,7 +30,7 @@ import kotlinx.coroutines.cancelChildren import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference -internal interface LiveEntityObserver: SessionLifecycleObserver +internal interface LiveEntityObserver : SessionLifecycleObserver internal abstract class RealmLiveEntityObserver(protected val realmConfiguration: RealmConfiguration) : LiveEntityObserver, RealmChangeListener> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt index c2744fc721..bb5186263c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt @@ -16,13 +16,25 @@ package im.vector.matrix.android.internal.database.model +import im.vector.matrix.android.api.session.room.model.Membership import io.realm.RealmObject import io.realm.annotations.Index +/** + * This class is used to get notification on new events being inserted. It's to avoid realm getting slow when listening to insert + * in EventEntity table. + */ internal open class EventInsertEntity(var eventId: String = "", var eventType: String = "" ) : RealmObject() { - companion object + private var insertTypeStr: String = EventInsertType.INCREMENTAL_SYNC.name + var insertType: EventInsertType + get() { + return EventInsertType.valueOf(insertTypeStr) + } + set(value) { + insertTypeStr = value.name + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertType.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertType.java new file mode 100644 index 0000000000..179b0f791c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 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; + +public enum EventInsertType { + INITIAL_SYNC, + INCREMENTAL_SYNC, + PAGINATION, + LOCAL_ECHO +} 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 1020b2bfaf..3618f3f7a8 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 @@ -19,18 +19,21 @@ package im.vector.matrix.android.internal.database.query 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.EventInsertEntity +import im.vector.matrix.android.internal.database.model.EventInsertType import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.kotlin.where -internal fun EventEntity.copyToRealmOrIgnore(realm: Realm): EventEntity { +internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where() .equalTo(EventEntityFields.EVENT_ID, eventId) .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val insertEntity = EventInsertEntity(eventId = eventId, eventType = type) + val insertEntity = EventInsertEntity(eventId = eventId, eventType = type).apply { + this.insertType = insertType + } realm.insert(insertEntity) // copy this event entity and return it realm.copyToRealm(this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt index c781d91c04..70d0a0d99f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/EventInsertLiveProcessor.kt @@ -17,11 +17,12 @@ package im.vector.matrix.android.internal.session import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.internal.database.model.EventInsertType import io.realm.Realm internal interface EventInsertLiveProcessor { - fun shouldProcess(eventId: String, eventType: String): Boolean + fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean suspend fun process(realm: Realm, event: Event) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt index 8a4a5407d5..142e64969f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt @@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.session.call 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.internal.database.model.EventInsertEntity +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.EventInsertLiveProcessor import io.realm.Realm @@ -37,7 +39,10 @@ internal class CallEventProcessor @Inject constructor( EventType.ENCRYPTED ) - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { + if (insertType != EventInsertType.INCREMENTAL_SYNC) { + return false + } return allowedTypes.contains(eventType) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt index 9cb9b7254b..bde0cc512d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationProcessor.kt @@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.database.mapper.EventMapper 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.EventEntity +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.PollResponseAggregatedSummaryEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields @@ -96,7 +97,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr EventType.ENCRYPTED ) - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return allowedTypes.contains(eventType) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt index f2a3da3a30..fe2bf846ff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventProcessor.kt @@ -21,6 +21,7 @@ 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.VersioningState import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.EventInsertLiveProcessor @@ -39,7 +40,7 @@ internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveP realm.insertOrUpdate(predecessorRoomSummary) } - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return eventType == EventType.STATE_ROOM_CREATE } } 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 ce4b31b89a..3ae3f812a9 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 @@ -21,6 +21,8 @@ import im.vector.matrix.android.api.session.room.model.Membership 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.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.model.EventInsertEntity +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.copyToRealmOrIgnore import im.vector.matrix.android.internal.database.query.getOrCreate @@ -76,7 +78,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( continue } val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } - val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) CurrentStateEventEntity.getOrCreate(realm, roomId, roomMemberEvent.stateKey, roomMemberEvent.type).apply { eventId = roomMemberEvent.eventId root = eventEntity diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/RedactionEventProcessor.kt index 2f61a98df3..f0e080cb37 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/RedactionEventProcessor.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.UnsignedData 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.EventInsertType 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 @@ -38,7 +39,7 @@ import javax.inject.Inject */ internal class RedactionEventProcessor @Inject constructor() : EventInsertLiveProcessor { - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return eventType == EventType.REDACTION } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt index 2d2c64ef7f..9ebced26e0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoRepository.kt @@ -29,6 +29,8 @@ import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper 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.EventEntity +import im.vector.matrix.android.internal.database.model.EventInsertEntity +import im.vector.matrix.android.internal.database.model.EventInsertType 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.findAllInRoomWithSendStates @@ -71,6 +73,10 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val timelineEvent = timelineEventMapper.map(timelineEventEntity) eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent)) monarchy.writeAsync { realm -> + val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply { + this.insertType = EventInsertType.LOCAL_ECHO + } + realm.insert(eventInsertEntity) val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync roomEntity.sendingTimelineEvents.add(0, timelineEventEntity) roomSummaryUpdater.update(realm, roomId) 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 1822587972..6ecd8bbe7d 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 @@ -29,14 +29,17 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper +import im.vector.matrix.android.internal.database.mapper.asDomain 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.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.TimelineEventFilter import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.query.whereInRoom import im.vector.matrix.android.internal.database.query.whereRoomId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -114,7 +117,6 @@ internal class DefaultTimeline( if (!results.isLoaded || !results.isValid) { return@OrderedRealmCollectionChangeListener } - results.createSnapshot() handleUpdates(results, changeSet) } 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 609ebe16fc..8e0e4759e9 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 @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.helper.deleteOnCascade import im.vector.matrix.android.internal.database.helper.merge 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.EventInsertType import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity @@ -204,7 +205,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri for (stateEvent in stateEvents) { val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } - val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) currentChunk.addStateEvent(roomId, stateEventEntity, direction) if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) { roomMemberContentsByUser[stateEvent.stateKey] = stateEvent.content.toModel() @@ -217,7 +218,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } val ageLocalTs = event.unsignedData?.age?.let { now - it } eventIds.add(event.eventId) - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) { event.prevContent diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt index 68ef2981d8..cfdf43c737 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt @@ -21,6 +21,7 @@ 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.VersioningState import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.EventInsertLiveProcessor @@ -42,7 +43,7 @@ internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLi realm.insertOrUpdate(predecessorRoomSummary) } - override fun shouldProcess(eventId: String, eventType: String): Boolean { + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return eventType == EventType.STATE_ROOM_TOMBSTONE } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index b1d8d7b0b5..f3af24001d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -35,6 +35,7 @@ import im.vector.matrix.android.internal.database.mapper.ContentMapper 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.CurrentStateEventEntity +import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.query.copyToRealmOrIgnore @@ -47,9 +48,9 @@ import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.mapWithProgress -import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler import im.vector.matrix.android.internal.session.room.read.FullyReadContent +import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor @@ -97,20 +98,25 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle // PRIVATE METHODS ***************************************************************************** private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService?) { + val insertType = if (isInitialSync) { + EventInsertType.INITIAL_SYNC + } else { + EventInsertType.INCREMENTAL_SYNC + } val syncLocalTimeStampMillis = System.currentTimeMillis() val rooms = when (handlingStrategy) { is HandlingStrategy.JOINED -> handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, isInitialSync, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, isInitialSync, insertType, syncLocalTimeStampMillis) } is HandlingStrategy.INVITED -> handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.1f) { - handleInvitedRoom(realm, it.key, it.value, syncLocalTimeStampMillis) + handleInvitedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) } is HandlingStrategy.LEFT -> { handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.3f) { - handleLeftRoom(realm, it.key, it.value, syncLocalTimeStampMillis) + handleLeftRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) } } } @@ -121,6 +127,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomId: String, roomSync: RoomSync, isInitialSync: Boolean, + insertType: EventInsertType, syncLocalTimestampMillis: Long): RoomEntity { Timber.v("Handle join sync for room $roomId") @@ -147,7 +154,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle continue } val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId root = eventEntity @@ -165,6 +172,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomSync.timeline.events, roomSync.timeline.prevToken, roomSync.timeline.limited, + insertType, syncLocalTimestampMillis, isInitialSync ) @@ -191,6 +199,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleInvitedRoom(realm: Realm, roomId: String, roomSync: InvitedRoomSync, + insertType: EventInsertType, syncLocalTimestampMillis: Long): RoomEntity { Timber.v("Handle invited sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) @@ -201,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return@forEach } val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = eventEntity.eventId root = eventEntity @@ -219,6 +228,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleLeftRoom(realm: Realm, roomId: String, roomSync: RoomSync, + insertType: EventInsertType, syncLocalTimestampMillis: Long): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) for (event in roomSync.state?.events.orEmpty()) { @@ -226,7 +236,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle continue } val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId root = eventEntity @@ -238,7 +248,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle continue } val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId @@ -263,6 +273,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle eventList: List, prevToken: String? = null, isLimited: Boolean = true, + insertType: EventInsertType, syncLocalTimestampMillis: Long, isInitialSync: Boolean): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) @@ -289,7 +300,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId From 9ebf87df62bd9c3f5ead2455b01aa79b60957ffa Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 6 Jul 2020 18:47:59 +0200 Subject: [PATCH 6/8] Group: rework a bit how and when we fetch data about groups --- .../matrix/android/api/session/group/Group.kt | 11 +++ .../internal/database/mapper/GroupMapper.kt | 34 ------- .../internal/database/model/GroupEntity.kt | 3 +- .../database/query/GroupEntityQueries.kt | 9 +- .../query/GroupSummaryEntityQueries.kt | 6 ++ .../internal/di/WorkManagerProvider.kt | 12 +++ .../android/internal/session/SessionModule.kt | 5 - .../session/group/DefaultGetGroupDataTask.kt | 83 +++++++++++------ .../internal/session/group/DefaultGroup.kt | 16 +++- .../session/group/DefaultGroupService.kt | 10 +- .../session/group/GetGroupDataWorker.kt | 16 ++-- .../internal/session/group/GroupFactory.kt | 40 ++++++++ .../internal/session/group/GroupModule.kt | 5 + .../session/group/GroupSummaryUpdater.kt | 93 ------------------- .../internal/session/sync/GroupSyncHandler.kt | 10 +- .../session/sync/SyncResponseHandler.kt | 40 ++++++++ .../features/grouplist/GroupListViewModel.kt | 3 + 17 files changed, 212 insertions(+), 184 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/GroupMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupFactory.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/Group.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/Group.kt index 3967c15704..cdc8bc1621 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/Group.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/Group.kt @@ -16,9 +16,20 @@ package im.vector.matrix.android.api.session.group +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.util.Cancelable + /** * This interface defines methods to interact within a group. */ interface Group { val groupId: String + + /** + * This methods allows you to refresh data about this group. It will be reflected on the GroupSummary. + * The SDK also takes care of refreshing group data every hour. + * @param callback : the matrix callback to be notified of success or failure + * @return a Cancelable to be able to cancel requests. + */ + fun fetchGroupData(callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/GroupMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/GroupMapper.kt deleted file mode 100644 index 89ed5844c2..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/GroupMapper.kt +++ /dev/null @@ -1,34 +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.database.mapper - -import im.vector.matrix.android.api.session.group.Group -import im.vector.matrix.android.internal.database.model.GroupEntity -import im.vector.matrix.android.internal.session.group.DefaultGroup - -internal object GroupMapper { - - fun map(groupEntity: GroupEntity): Group { - return DefaultGroup( - groupEntity.groupId - ) - } -} - -internal fun GroupEntity.asDomain(): Group { - return GroupMapper.map(this) -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/GroupEntity.kt index eb346a74ca..a0054ae8d6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/GroupEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/GroupEntity.kt @@ -22,8 +22,7 @@ import io.realm.annotations.PrimaryKey /** * This class is used to store group info (groupId and membership) from the sync response. - * Then [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] observes change and - * makes requests to fetch group information from the homeserver + * Then GetGroupDataTask is called regularly to fetch group information from the homeserver. */ internal open class GroupEntity(@PrimaryKey var groupId: String = "") : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupEntityQueries.kt index 802bfbeae6..1e4f5639c4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupEntityQueries.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntityFields +import im.vector.matrix.android.internal.query.process import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where @@ -28,10 +29,6 @@ internal fun GroupEntity.Companion.where(realm: Realm, groupId: String): RealmQu .equalTo(GroupEntityFields.GROUP_ID, groupId) } -internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery { - val query = realm.where() - if (membership != null) { - query.equalTo(GroupEntityFields.MEMBERSHIP_STR, membership.name) - } - return query +internal fun GroupEntity.Companion.where(realm: Realm, memberships: List): RealmQuery { + return realm.where().process(GroupEntityFields.MEMBERSHIP_STR, memberships) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt index 601da098ca..b062793f00 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt @@ -18,8 +18,10 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import io.realm.Realm import io.realm.RealmQuery +import io.realm.kotlin.createObject import io.realm.kotlin.where internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupId: String? = null): RealmQuery { @@ -34,3 +36,7 @@ internal fun GroupSummaryEntity.Companion.where(realm: Realm, groupIds: List() .`in`(GroupSummaryEntityFields.GROUP_ID, groupIds.toTypedArray()) } + +internal fun GroupSummaryEntity.Companion.getOrCreate(realm: Realm, groupId: String): GroupSummaryEntity { + return where(realm, groupId).findFirst() ?: realm.createObject(groupId) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt index 5a0202719b..71a3c4bfba 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt @@ -21,7 +21,10 @@ import androidx.work.Constraints import androidx.work.ListenableWorker import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager +import java.time.Duration +import java.util.concurrent.TimeUnit import javax.inject.Inject internal class WorkManagerProvider @Inject constructor( @@ -39,6 +42,15 @@ internal class WorkManagerProvider @Inject constructor( OneTimeWorkRequestBuilder() .addTag(tag) + /** + * Create a PeriodicWorkRequestBuilder, with the Matrix SDK tag + */ + inline fun matrixPeriodicWorkRequestBuilder(repeatInterval: Long, + repeatIntervalTimeUnit: TimeUnit) = + PeriodicWorkRequestBuilder(repeatInterval, repeatIntervalTimeUnit) + .addTag(tag) + + /** * Cancel all works instantiated by the Matrix SDK for the current session, and not those from the SDK client, or for other sessions */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 8aabd709ab..0feb944b38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -68,7 +68,6 @@ import im.vector.matrix.android.internal.network.token.AccessTokenProvider import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider import im.vector.matrix.android.internal.session.call.CallEventProcessor import im.vector.matrix.android.internal.session.download.DownloadProgressInterceptor -import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService import im.vector.matrix.android.internal.session.identity.DefaultIdentityService import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager @@ -293,10 +292,6 @@ internal abstract class SessionModule { @Binds abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker - @Binds - @IntoSet - abstract fun bindGroupSummaryUpdater(updater: GroupSummaryUpdater): SessionLifecycleObserver - @Binds @IntoSet abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt index 7c5de5b137..ee43441453 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt @@ -18,7 +18,9 @@ package im.vector.matrix.android.internal.session.group import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity +import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.network.executeRequest @@ -28,11 +30,14 @@ import im.vector.matrix.android.internal.session.group.model.GroupUsers import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import org.greenrobot.eventbus.EventBus +import timber.log.Timber import javax.inject.Inject internal interface GetGroupDataTask : Task { - - data class Params(val groupId: String) + sealed class Params { + object FetchAllActive : Params() + data class FetchWithIds(val groupIds: List) : Params() + } } internal class DefaultGetGroupDataTask @Inject constructor( @@ -41,44 +46,64 @@ internal class DefaultGetGroupDataTask @Inject constructor( private val eventBus: EventBus ) : GetGroupDataTask { + private data class GroupData( + val groupId: String, + val groupSummary: GroupSummaryResponse, + val groupRooms: GroupRooms, + val groupUsers: GroupUsers + ) + override suspend fun execute(params: GetGroupDataTask.Params) { - val groupId = params.groupId - val groupSummary = executeRequest(eventBus) { - apiCall = groupAPI.getSummary(groupId) + val groupIds = when (params) { + is GetGroupDataTask.Params.FetchAllActive -> { + getActiveGroupIds() + } + is GetGroupDataTask.Params.FetchWithIds -> { + params.groupIds + } } - val groupRooms = executeRequest(eventBus) { - apiCall = groupAPI.getRooms(groupId) + Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}") + val data = groupIds.map { groupId -> + val groupSummary = executeRequest(eventBus) { + apiCall = groupAPI.getSummary(groupId) + } + val groupRooms = executeRequest(eventBus) { + apiCall = groupAPI.getRooms(groupId) + } + val groupUsers = executeRequest(eventBus) { + apiCall = groupAPI.getUsers(groupId) + } + GroupData(groupId, groupSummary, groupRooms, groupUsers) } - val groupUsers = executeRequest(eventBus) { - apiCall = groupAPI.getUsers(groupId) - } - insertInDb(groupSummary, groupRooms, groupUsers, groupId) + insertInDb(data) } - private suspend fun insertInDb(groupSummary: GroupSummaryResponse, - groupRooms: GroupRooms, - groupUsers: GroupUsers, - groupId: String) { + private fun getActiveGroupIds(): List { + return monarchy.fetchAllMappedSync( + { realm -> + GroupEntity.where(realm, Membership.activeMemberships()) + }, + { it.groupId } + ) + } + + private suspend fun insertInDb(groupDataList: List) { monarchy .awaitTransaction { realm -> - val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst() - ?: realm.createObject(GroupSummaryEntity::class.java, groupId) + groupDataList.forEach { groupData -> - groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: "" - val name = groupSummary.profile?.name - groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name - groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: "" + val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupData.groupId) - groupSummaryEntity.roomIds.clear() - groupRooms.rooms.mapTo(groupSummaryEntity.roomIds) { it.roomId } + groupSummaryEntity.avatarUrl = groupData.groupSummary.profile?.avatarUrl ?: "" + val name = groupData.groupSummary.profile?.name + groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupData.groupId else name + groupSummaryEntity.shortDescription = groupData.groupSummary.profile?.shortDescription ?: "" - groupSummaryEntity.userIds.clear() - groupUsers.users.mapTo(groupSummaryEntity.userIds) { it.userId } + groupSummaryEntity.roomIds.clear() + groupData.groupRooms.rooms.mapTo(groupSummaryEntity.roomIds) { it.roomId } - groupSummaryEntity.membership = when (groupSummary.user?.membership) { - Membership.JOIN.value -> Membership.JOIN - Membership.INVITE.value -> Membership.INVITE - else -> Membership.LEAVE + groupSummaryEntity.userIds.clear() + groupData.groupUsers.users.mapTo(groupSummaryEntity.userIds) { it.userId } } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroup.kt index 6c7b5b2a8b..a9e77a73d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroup.kt @@ -16,6 +16,20 @@ package im.vector.matrix.android.internal.session.group +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.group.Group +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith -internal class DefaultGroup(override val groupId: String) : Group +internal class DefaultGroup(override val groupId: String, + private val taskExecutor: TaskExecutor, + private val getGroupDataTask: GetGroupDataTask) : Group { + + override fun fetchGroupData(callback: MatrixCallback): Cancelable { + val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) + return getGroupDataTask.configureWith(params) { + this.callback = callback + }.executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt index af73b896f4..4dd162276f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields import im.vector.matrix.android.internal.database.query.where @@ -33,10 +34,15 @@ import io.realm.Realm import io.realm.RealmQuery import javax.inject.Inject -internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : GroupService { +internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val groupFactory: GroupFactory) : GroupService { override fun getGroup(groupId: String): Group? { - return null + return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + GroupEntity.where(realm, groupId).findFirst()?.let { + groupFactory.create(groupId) + } + } } override fun getGroupSummary(groupId: String): GroupSummary? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt index bb33212f9c..f025040c39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt @@ -35,7 +35,6 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : @JsonClass(generateAdapter = true) internal data class Params( override val sessionId: String, - val groupIds: List, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -48,14 +47,11 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) - val results = params.groupIds.map { groupId -> - runCatching { fetchGroupData(groupId) } - } - val isSuccessful = results.none { it.isFailure } - return if (isSuccessful) Result.success() else Result.retry() - } - - private suspend fun fetchGroupData(groupId: String) { - getGroupDataTask.execute(GetGroupDataTask.Params(groupId)) + return runCatching { + getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive) + }.fold( + { Result.success() }, + { Result.retry() } + ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupFactory.kt new file mode 100644 index 0000000000..a5046d45d4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupFactory.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 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.group + +import im.vector.matrix.android.api.session.group.Group +import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.task.TaskExecutor +import javax.inject.Inject + +internal interface GroupFactory { + fun create(groupId: String): Group +} + +@SessionScope +internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask, + private val taskExecutor: TaskExecutor) : + GroupFactory { + + override fun create(groupId: String): Group { + return DefaultGroup( + groupId = groupId, + taskExecutor = taskExecutor, + getGroupDataTask = getGroupDataTask + ) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt index b48c6a96e8..bf960f061f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt @@ -21,6 +21,8 @@ import dagger.Module import dagger.Provides import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.room.DefaultRoomFactory +import im.vector.matrix.android.internal.session.room.RoomFactory import retrofit2.Retrofit @Module @@ -36,6 +38,9 @@ internal abstract class GroupModule { } } + @Binds + abstract fun bindGroupFactory(factory: DefaultGroupFactory): GroupFactory + @Binds abstract fun bindGetGroupDataTask(task: DefaultGetGroupDataTask): GetGroupDataTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt deleted file mode 100644 index 52268ab7de..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ /dev/null @@ -1,93 +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.group - -import androidx.work.ExistingWorkPolicy -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.internal.database.RealmLiveEntityObserver -import im.vector.matrix.android.internal.database.awaitTransaction -import im.vector.matrix.android.internal.database.model.GroupEntity -import im.vector.matrix.android.internal.database.model.GroupSummaryEntity -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.di.SessionId -import im.vector.matrix.android.internal.di.WorkManagerProvider -import im.vector.matrix.android.internal.worker.WorkerParamsFactory -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmResults -import kotlinx.coroutines.launch -import javax.inject.Inject - -private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" - -internal class GroupSummaryUpdater @Inject constructor( - private val workManagerProvider: WorkManagerProvider, - @SessionId private val sessionId: String, - @SessionDatabase private val monarchy: Monarchy) - : RealmLiveEntityObserver(monarchy.realmConfiguration) { - - override val query = Monarchy.Query { GroupEntity.where(it) } - - override fun onChange(results: RealmResults) { - /* - // `insertions` for new groups and `changes` to handle left groups - val modifiedGroupEntity = (changeSet.insertions + changeSet.changes) - .asSequence() - .mapNotNull { results[it] } - - fetchGroupsData(modifiedGroupEntity - .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } - .map { it.groupId } - .toList()) - - modifiedGroupEntity - .filter { it.membership == Membership.LEAVE } - .map { it.groupId } - .toList() - .also { - observerScope.launch { - deleteGroups(it) - } - } - */ - } - - private fun fetchGroupsData(groupIds: List) { - val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId, groupIds) - - val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) - - val getGroupWork = workManagerProvider.matrixOneTimeWorkRequestBuilder() - .setInputData(workData) - .setConstraints(WorkManagerProvider.workConstraints) - .build() - - workManagerProvider.workManager - .beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, getGroupWork) - .enqueue() - } - - /** - * Delete the GroupSummaryEntity of left groups - */ - private suspend fun deleteGroups(groupIds: List) = awaitTransaction(monarchy.realmConfiguration) { realm -> - GroupSummaryEntity.where(realm, groupIds) - .findAll() - .deleteAllFromRealm() - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt index 392db0a73c..e3d4eae575 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.session.sync import im.vector.matrix.android.R import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.model.GroupEntity +import im.vector.matrix.android.internal.database.model.GroupSummaryEntity +import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.mapWithProgress @@ -64,29 +66,33 @@ internal class GroupSyncHandler @Inject constructor() { handleLeftGroup(realm, it.key) } } - - /** Note: [im.vector.matrix.android.internal.session.group.GroupSummaryUpdater] is observing changes */ realm.insertOrUpdate(groups) } private fun handleJoinedGroup(realm: Realm, groupId: String): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) + val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.JOIN + groupSummaryEntity.membership = Membership.JOIN return groupEntity } private fun handleInvitedGroup(realm: Realm, groupId: String): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) + val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.INVITE + groupSummaryEntity.membership = Membership.INVITE return groupEntity } private fun handleLeftGroup(realm: Realm, groupId: String): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) + val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.LEAVE + groupSummaryEntity.membership = Membership.LEAVE return groupEntity } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt index 5e8ef5a608..0769895d38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt @@ -16,23 +16,34 @@ package im.vector.matrix.android.internal.session.sync +import androidx.work.ExistingPeriodicWorkPolicy import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService +import im.vector.matrix.android.internal.session.group.GetGroupDataWorker import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask import im.vector.matrix.android.internal.session.reportSubtask +import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.util.awaitTransaction +import im.vector.matrix.android.internal.worker.WorkerParamsFactory import timber.log.Timber +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.system.measureTimeMillis +private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" + internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, private val roomSyncHandler: RoomSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val groupSyncHandler: GroupSyncHandler, @@ -109,10 +120,39 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private checkPushRules(it, isInitialSync) userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) } + syncResponse.groups?.also { + scheduleGroupDataFetchingIfNeeded(it) + } + Timber.v("On sync completed") cryptoSyncHandler.onSyncCompleted(syncResponse) } + /** + * At the moment we don't get any group data through the sync, so we poll where every hour. + You can also force to refetch group data using [Group] API. + */ + private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) { + val groupIds = ArrayList() + groupIds.addAll(groupsSyncResponse.join.keys) + groupIds.addAll(groupsSyncResponse.invite.keys) + if (groupIds.isEmpty()) { + Timber.v("No new groups to fetch data for.") + return + } + Timber.v("There are ${groupIds.size} new groups to fetch data for.") + val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId) + val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) + + val getGroupWork = workManagerProvider.matrixPeriodicWorkRequestBuilder(1, TimeUnit.HOURS) + .setInputData(workData) + .setConstraints(WorkManagerProvider.workConstraints) + .build() + + workManagerProvider.workManager + .enqueueUniquePeriodicWork(GET_GROUP_DATA_WORKER, ExistingPeriodicWorkPolicy.REPLACE, getGroupWork) + } + private suspend fun checkPushRules(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean) { Timber.v("[PushRules] --> checkPushRules") if (isInitialSync) { diff --git a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt index f14583c5d5..b4a65d67c8 100644 --- a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt @@ -23,6 +23,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.groupSummaryQueryParams @@ -88,6 +89,8 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state -> if (state.selectedGroup?.groupId != action.groupSummary.groupId) { + // We take care of refreshing group data when selecting to be sure we get all the rooms and users + session.getGroup(action.groupSummary.groupId)?.fetchGroupData(NoOpMatrixCallback()) setState { copy(selectedGroup = action.groupSummary) } } } From c1da4aecd77eb5eba9f3f596bd7d6e27eb030115 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 6 Jul 2020 19:09:08 +0200 Subject: [PATCH 7/8] Fix leaving selected group --- .../im/vector/riotx/features/grouplist/GroupListViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt index b4a65d67c8..4c8e5c2333 100644 --- a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt @@ -75,6 +75,11 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro } val optionGroup = Option.just(groupSummary) selectedGroupStore.post(optionGroup) + } else { + // If selected group is null we force to default. It can happens when leaving the selected group. + setState { + copy(selectedGroup = this.asyncGroups()?.find { it.groupId == ALL_COMMUNITIES_GROUP_ID }) + } } } } From bf03b367f129b2b8fb7c97d3a4eadd684b3686a6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 6 Jul 2020 19:12:24 +0200 Subject: [PATCH 8/8] Clean code --- .../android/internal/database/model/ChunkEntity.kt | 2 +- .../internal/database/model/EventInsertEntity.kt | 3 --- .../database/query/GroupSummaryEntityQueries.kt | 1 - .../matrix/android/internal/di/WorkManagerProvider.kt | 2 -- .../matrix/android/internal/session/DefaultSession.kt | 11 ----------- .../internal/session/call/CallEventProcessor.kt | 1 - .../android/internal/session/call/CallModule.kt | 1 - .../android/internal/session/filter/FilterUtil.kt | 3 +-- .../android/internal/session/group/GroupModule.kt | 2 -- .../session/room/membership/LoadRoomMembersTask.kt | 1 - .../internal/session/room/timeline/DefaultTimeline.kt | 7 ++----- 11 files changed, 4 insertions(+), 30 deletions(-) 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 b1e08fdeea..7014146539 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 @@ -27,7 +27,7 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, @Index var nextToken: String? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), - var numberOfTimelineEvents: Long= 0, + var numberOfTimelineEvents: Long = 0, // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt index bb5186263c..e23ea5c3d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventInsertEntity.kt @@ -16,9 +16,7 @@ package im.vector.matrix.android.internal.database.model -import im.vector.matrix.android.api.session.room.model.Membership import io.realm.RealmObject -import io.realm.annotations.Index /** * This class is used to get notification on new events being inserted. It's to avoid realm getting slow when listening to insert @@ -36,5 +34,4 @@ internal open class EventInsertEntity(var eventId: String = "", set(value) { insertTypeStr = value.name } - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt index b062793f00..18d40d0e68 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/GroupSummaryEntityQueries.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.database.query import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields -import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.createObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt index 71a3c4bfba..a7d1b68c92 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt @@ -23,7 +23,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager -import java.time.Duration import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -50,7 +49,6 @@ internal class WorkManagerProvider @Inject constructor( PeriodicWorkRequestBuilder(repeatInterval, repeatIntervalTimeUnit) .addTag(tag) - /** * Cancel all works instantiated by the Matrix SDK for the current session, and not those from the SDK client, or for other sessions */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index e7ab2a7926..83ba76d5b8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -50,21 +50,10 @@ import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.widgets.WidgetService import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.crypto.DefaultCryptoService -import im.vector.matrix.android.internal.database.awaitTransaction -import im.vector.matrix.android.internal.database.helper.nextDisplayIndex -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.CurrentStateEventEntity -import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields -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.model.TimelineEventEntityFields import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.identity.DefaultIdentityService -import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt index 142e64969f..34ee7b2c54 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventProcessor.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.call 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.internal.database.model.EventInsertEntity import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.EventInsertLiveProcessor diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt index 5e75738dec..bc4cef8772 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallModule.kt @@ -41,5 +41,4 @@ internal abstract class CallModule { @Binds abstract fun bindGetTurnServerTask(task: DefaultGetTurnServerTask): GetTurnServerTask - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt index d95a53cb07..53ede5ad45 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt @@ -98,8 +98,7 @@ internal object FilterUtil { state = filter.room.state?.copy(lazyLoadMembers = true) ?: RoomEventFilter(lazyLoadMembers = true) ) - ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true), - timeline = RoomEventFilter(limit = 1500)) + ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true)) ) } else { val newRoomEventFilter = filter.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt index bf960f061f..6799ffd3e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt @@ -21,8 +21,6 @@ import dagger.Module import dagger.Provides import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.session.room.DefaultRoomFactory -import im.vector.matrix.android.internal.session.room.RoomFactory import retrofit2.Retrofit @Module 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 3ae3f812a9..d860ccc7e4 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 @@ -21,7 +21,6 @@ import im.vector.matrix.android.api.session.room.model.Membership 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.CurrentStateEventEntity -import im.vector.matrix.android.internal.database.model.EventInsertEntity import im.vector.matrix.android.internal.database.model.EventInsertType import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.copyToRealmOrIgnore 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 6ecd8bbe7d..16c98770e2 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 @@ -29,17 +29,14 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper -import im.vector.matrix.android.internal.database.mapper.asDomain 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.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.TimelineEventFilter import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.database.query.whereInRoom import im.vector.matrix.android.internal.database.query.whereRoomId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -501,8 +498,8 @@ internal class DefaultTimeline( val currentChunk = getLiveChunk() val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken if (token == null) { - if (direction == Timeline.Direction.BACKWARDS || - (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse())) { + if (direction == Timeline.Direction.BACKWARDS + || (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse())) { // We are in the case where event exists, but we do not know the token. // Fetch (again) the last event to get a token val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) {