Realm-kotlin: handle a bunch of classes on session db (timeline, eventinsertentity and some more tasks)

This commit is contained in:
ganfra 2022-10-13 12:54:26 +02:00
parent 6edea43ab4
commit 6379c199ea
55 changed files with 803 additions and 1122 deletions

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.api.debug package org.matrix.android.sdk.api.debug
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
/** /**
* Useful methods to access to some private data managed by the SDK. * Useful methods to access to some private data managed by the SDK.

View File

@ -17,7 +17,7 @@
package org.matrix.android.sdk.api.session package org.matrix.android.sdk.api.session
import androidx.annotation.MainThread import androidx.annotation.MainThread
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams

View File

@ -16,14 +16,12 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.database.query.whereType
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.util.fetchCopied
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -31,18 +29,17 @@ import javax.inject.Inject
* in the session DB, this class encapsulate this functionality. * in the session DB, this class encapsulate this functionality.
*/ */
internal class CryptoSessionInfoProvider @Inject constructor( internal class CryptoSessionInfoProvider @Inject constructor(
@SessionDatabase private val monarchy: Monarchy @SessionDatabase private val realmInstance: RealmInstance,
) { ) {
fun isRoomEncrypted(roomId: String): Boolean { fun isRoomEncrypted(roomId: String): Boolean {
// We look at the presence at any m.room.encryption state event no matter if it's // We look at the presence at any m.room.encryption state event no matter if it's
// the latest one or if it is well formed // the latest one or if it is well formed
val encryptionEvent = monarchy.fetchCopied { realm -> val realm = realmInstance.getBlockingRealm()
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) return EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.isEmpty(EventEntityFields.STATE_KEY) .query("stateKey == ''")
.findFirst() .first()
} .find() != null
return encryptionEvent != null
} }
/** /**
@ -50,14 +47,11 @@ internal class CryptoSessionInfoProvider @Inject constructor(
* @param allActive if true return joined as well as invited, if false, only joined * @param allActive if true return joined as well as invited, if false, only joined
*/ */
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> { fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
var userIds: List<String> = emptyList() val realm = realmInstance.getBlockingRealm()
monarchy.doWithRealm { realm -> return if (allActive) {
userIds = if (allActive) { RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() } else {
} else { RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
}
} }
return userIds
} }
} }

View File

@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
import android.content.Context import android.content.Context
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration
import io.realm.kotlin.MutableRealm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm import io.realm.kotlin.TypedRealm
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
@ -38,12 +37,10 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.query.crossSigningInfoEntityQueries import org.matrix.android.sdk.internal.crypto.store.db.query.crossSigningInfoEntityQueries
import org.matrix.android.sdk.internal.crypto.store.db.query.userEntityQueries import org.matrix.android.sdk.internal.crypto.store.db.query.userEntityQueries
import org.matrix.android.sdk.internal.database.RealmInstance import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.queryIn
import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
@ -76,7 +73,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
@Inject lateinit var cryptoRealmInstance: RealmInstance @Inject lateinit var cryptoRealmInstance: RealmInstance
@SessionDatabase @SessionDatabase
@Inject lateinit var sessionRealmConfiguration: RealmConfiguration @Inject lateinit var sessionRealmInstance: RealmInstance
@UserId @UserId
@Inject lateinit var myUserId: String @Inject lateinit var myUserId: String
@ -205,21 +202,22 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) { private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) {
Timber.d("## CrossSigning - Updating shields for impacted rooms...") Timber.d("## CrossSigning - Updating shields for impacted rooms...")
awaitTransaction(sessionRealmConfiguration) { sessionRealm -> sessionRealmInstance.write {
val cryptoRealm = cryptoRealmInstance.getBlockingRealm() val cryptoRealm = cryptoRealmInstance.getBlockingRealm()
sessionRealm.where(RoomMemberSummaryEntity::class.java) query(RoomMemberSummaryEntity::class)
.`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) .queryIn("userId", userList)
.distinct(RoomMemberSummaryEntityFields.ROOM_ID) .distinct("roomId")
.findAll() .find()
.map { it.roomId } .map { it.roomId }
.also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") }
.forEach { roomId -> .forEach { roomId ->
RoomSummaryEntity.where(sessionRealm, roomId) RoomSummaryEntity.where(this, roomId)
.equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) .query("isEncrypted == true")
.findFirst() .first()
.find()
?.let { roomSummary -> ?.let { roomSummary ->
Timber.v("## CrossSigning - Check shield state for room $roomId") Timber.v("## CrossSigning - Check shield state for room $roomId")
val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() val allActiveRoomMembers = RoomMemberHelper(this, roomId).getActiveRoomMemberIds()
try { try {
val updatedTrust = computeRoomShield( val updatedTrust = computeRoomShield(
myCrossSigningInfo, myCrossSigningInfo,

View File

@ -16,16 +16,16 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.query.RealmResults
import io.realm.RealmConfiguration import kotlinx.coroutines.flow.flatMapConcat
import io.realm.RealmResults import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventInsertEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity
import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
@ -33,68 +33,54 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class EventInsertLiveObserver @Inject constructor( internal class EventInsertLiveObserver @Inject constructor(
@SessionDatabase realmConfiguration: RealmConfiguration, @SessionDatabase realmInstance: RealmInstance,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor> private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
) : ) :
RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) { RealmLiveEntityObserver<EventInsertEntity>(realmInstance, coroutineDispatchers.io) {
private val lock = Mutex() private val lock = Mutex()
override val query = Monarchy.Query { init {
it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) realmInstance.getRealmFlow().flatMapConcat { realm ->
realm.query(EventInsertEntity::class, "canBeProcessed == true").asFlow()
}.onEach { resultChange ->
onChange(resultChange.list)
}.launchIn(observerScope)
} }
override fun onChange(results: RealmResults<EventInsertEntity>) { private suspend fun onChange(results: RealmResults<EventInsertEntity>) {
observerScope.launch { fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
lock.withLock { return processors.any {
if (!results.isLoaded || results.isEmpty()) { it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
return@withLock
}
val idsToDeleteAfterProcess = ArrayList<String>()
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
Timber.v("EventInsertEntity updated with ${results.size} results in db")
results.forEach {
if (shouldProcess(it)) {
// don't use copy from realm over there
val copiedEvent = EventInsertEntity(
eventId = it.eventId,
eventType = it.eventType
).apply {
insertType = it.insertType
}
filteredEvents.add(copiedEvent)
}
idsToDeleteAfterProcess.add(it.eventId)
}
awaitTransaction(realmConfiguration) { realm ->
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
filteredEvents.forEach { eventInsert ->
val eventId = eventInsert.eventId
val event = EventEntity.where(realm, eventId).findFirst()
if (event == null) {
Timber.v("Event $eventId not found")
return@forEach
}
val domainEvent = event.asDomain()
processors.filter {
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
}.forEach {
it.process(realm, domainEvent)
}
}
realm.where(EventInsertEntity::class.java)
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
.findAll()
.deleteAllFromRealm()
}
processors.forEach { it.onPostProcess() }
} }
} }
}
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { lock.withLock {
return processors.any { if (results.isEmpty()) {
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType) return@withLock
}
Timber.v("EventInsertEntity updated with ${results.size} results in db")
realmInstance.write { ->
results
.filter(::shouldProcess)
.forEach { eventInsert ->
val eventId = eventInsert.eventId
val event = EventEntity.where(this, eventId).first().find()
if (event == null) {
Timber.v("Event $eventId not found")
return@forEach
}
val domainEvent = event.asDomain()
processors.filter {
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
}.forEach {
it.process(this, domainEvent)
}
deleteNullable(findLatest(eventInsert))
}
}
processors.forEach { it.onPostProcess() }
} }
} }
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database
import io.realm.kotlin.Deleteable import io.realm.kotlin.Deleteable
import io.realm.kotlin.MutableRealm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.dynamic.DynamicMutableRealm
import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicMutableRealmObject
import io.realm.kotlin.dynamic.DynamicRealmObject import io.realm.kotlin.dynamic.DynamicRealmObject
import io.realm.kotlin.migration.AutomaticSchemaMigration import io.realm.kotlin.migration.AutomaticSchemaMigration
@ -115,6 +116,14 @@ internal fun MutableRealm.deleteAll() {
} }
} }
internal fun DynamicMutableRealm.deleteAll() {
configuration.schema.mapNotNull {
it.simpleName
}.forEach { className ->
delete(query(className).find())
}
}
internal fun MutableRealm.deleteNullable(deleteable: Deleteable?) { internal fun MutableRealm.deleteNullable(deleteable: Deleteable?) {
if (deleteable == null) return if (deleteable == null) return
delete(deleteable) delete(deleteable)

View File

@ -16,59 +16,24 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.types.RealmObject
import io.realm.Realm import kotlinx.coroutines.CoroutineDispatcher
import io.realm.RealmChangeListener
import io.realm.RealmConfiguration
import io.realm.RealmModel
import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
internal interface LiveEntityObserver : SessionLifecycleObserver internal interface LiveEntityObserver : SessionLifecycleObserver
internal abstract class RealmLiveEntityObserver<T : RealmModel>(protected val realmConfiguration: RealmConfiguration) : internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmInstance: RealmInstance, coroutineDispatcher: CoroutineDispatcher) :
LiveEntityObserver, RealmChangeListener<RealmResults<T>> { LiveEntityObserver {
private companion object { protected val observerScope = CoroutineScope(SupervisorJob() + coroutineDispatcher)
val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-LIVE_ENTITY_BACKGROUND")
}
protected val observerScope = CoroutineScope(SupervisorJob() + BACKGROUND_HANDLER.asCoroutineDispatcher())
protected abstract val query: Monarchy.Query<T>
private val isStarted = AtomicBoolean(false)
private val backgroundRealm = AtomicReference<Realm>()
private lateinit var results: AtomicReference<RealmResults<T>>
override fun onSessionStarted(session: Session) {
if (isStarted.compareAndSet(false, true)) {
BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm)
val queryResults = query.createQuery(realm).findAll()
queryResults.addChangeListener(this)
results = AtomicReference(queryResults)
}
}
}
override fun onSessionStopped(session: Session) { override fun onSessionStopped(session: Session) {
if (isStarted.compareAndSet(true, false)) { observerScope.coroutineContext.cancelChildren()
BACKGROUND_HANDLER.post {
results.getAndSet(null).removeAllChangeListeners()
backgroundRealm.getAndSet(null).also {
it.close()
}
observerScope.coroutineContext.cancelChildren()
}
}
} }
override fun onClearCache(session: Session) { override fun onClearCache(session: Session) {

View File

@ -16,53 +16,10 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import io.realm.Realm
import io.realm.RealmChangeListener
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
internal suspend fun <T> awaitNotEmptyResult(
realmConfiguration: RealmConfiguration,
timeoutMillis: Long,
builder: (Realm) -> RealmQuery<T>
) {
withTimeout(timeoutMillis) {
// Confine Realm interaction to a single thread with Looper.
withContext(Dispatchers.Main) {
val latch = CompletableDeferred<Unit>()
Realm.getInstance(realmConfiguration).use { realm ->
val result = builder(realm).findAllAsync()
val listener = object : RealmChangeListener<RealmResults<T>> {
override fun onChange(it: RealmResults<T>) {
if (it.isNotEmpty()) {
result.removeChangeListener(this)
latch.complete(Unit)
}
}
}
result.addChangeListener(listener)
try {
latch.await()
} catch (e: CancellationException) {
result.removeChangeListener(listener)
throw e
}
}
}
}
}
internal suspend fun <T : RealmObject> awaitNotEmptyResult( internal suspend fun <T : RealmObject> awaitNotEmptyResult(
realmInstance: RealmInstance, realmInstance: RealmInstance,
timeoutMillis: Long, timeoutMillis: Long,

View File

@ -1,73 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database
import android.os.Looper
import androidx.annotation.MainThread
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
import kotlin.concurrent.getOrSet
/**
* This class keeps an instance of realm open in the main thread so you can grab it whenever you want to get a realm
* instance. This does check each time if you are on the main thread or not and returns the appropriate realm instance.
*/
@SessionScope
internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) :
SessionLifecycleObserver {
private val realmThreadLocal = ThreadLocal<Realm>()
/**
* Allow you to execute a block with an opened realm. It automatically closes it if necessary (ie. when not in main thread)
*/
fun <R> withRealm(block: (Realm) -> R): R {
return getRealmWrapper().withRealm(block)
}
@MainThread
override fun onSessionStarted(session: Session) {
realmThreadLocal.getOrSet {
Realm.getInstance(monarchy.realmConfiguration)
}
}
@MainThread
override fun onSessionStopped(session: Session) {
realmThreadLocal.get()?.close()
realmThreadLocal.remove()
}
private fun getRealmWrapper(): RealmInstanceWrapper {
val isOnMainThread = isOnMainThread()
val realm = if (isOnMainThread) {
realmThreadLocal.getOrSet {
Realm.getInstance(monarchy.realmConfiguration)
}
} else {
Realm.getInstance(monarchy.realmConfiguration)
}
return RealmInstanceWrapper(realm, closeRealmOnClose = !isOnMainThread)
}
private fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
}

View File

@ -16,94 +16,18 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm import io.realm.kotlin.migration.AutomaticSchemaMigration
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo001
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo002
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo003
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo004
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo005
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo006
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo007
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo008
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo009
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo010
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo011
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo012
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo013
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo014
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo015
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo016
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo017
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo018
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo019
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo020
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject import javax.inject.Inject
internal class RealmSessionStoreMigration @Inject constructor( internal class RealmSessionStoreMigration @Inject constructor() : MatrixAutomaticSchemaMigration(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session", dbName = "Session",
schemaVersion = 35L, schemaVersion = 36L,
) { ) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
* Avoids Realm throwing when multiple instances of the migration are set.
*/
override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) { override fun doMigrate(oldVersion: Long, migrationContext: AutomaticSchemaMigration.MigrationContext) {
if (oldVersion < 1) MigrateSessionTo001(realm).perform() if (oldVersion < 36L) {
if (oldVersion < 2) MigrateSessionTo002(realm).perform() // Don't bother with old migrations we force a clear cache here
if (oldVersion < 3) MigrateSessionTo003(realm).perform() migrationContext.newRealm.deleteAll()
if (oldVersion < 4) MigrateSessionTo004(realm).perform() }
if (oldVersion < 5) MigrateSessionTo005(realm).perform()
if (oldVersion < 6) MigrateSessionTo006(realm).perform()
if (oldVersion < 7) MigrateSessionTo007(realm).perform()
if (oldVersion < 8) MigrateSessionTo008(realm).perform()
if (oldVersion < 9) MigrateSessionTo009(realm).perform()
if (oldVersion < 10) MigrateSessionTo010(realm).perform()
if (oldVersion < 11) MigrateSessionTo011(realm).perform()
if (oldVersion < 12) MigrateSessionTo012(realm).perform()
if (oldVersion < 13) MigrateSessionTo013(realm).perform()
if (oldVersion < 14) MigrateSessionTo014(realm).perform()
if (oldVersion < 15) MigrateSessionTo015(realm).perform()
if (oldVersion < 16) MigrateSessionTo016(realm).perform()
if (oldVersion < 17) MigrateSessionTo017(realm).perform()
if (oldVersion < 18) MigrateSessionTo018(realm).perform()
if (oldVersion < 19) MigrateSessionTo019(realm, normalizer).perform()
if (oldVersion < 20) MigrateSessionTo020(realm).perform()
if (oldVersion < 21) MigrateSessionTo021(realm).perform()
if (oldVersion < 22) MigrateSessionTo022(realm).perform()
if (oldVersion < 23) MigrateSessionTo023(realm).perform()
if (oldVersion < 24) MigrateSessionTo024(realm).perform()
if (oldVersion < 25) MigrateSessionTo025(realm).perform()
if (oldVersion < 26) MigrateSessionTo026(realm).perform()
if (oldVersion < 27) MigrateSessionTo027(realm).perform()
if (oldVersion < 28) MigrateSessionTo028(realm).perform()
if (oldVersion < 29) MigrateSessionTo029(realm).perform()
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
if (oldVersion < 32) MigrateSessionTo032(realm).perform()
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
} }
} }

View File

@ -16,21 +16,15 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import android.content.Context import io.realm.kotlin.RealmConfiguration
import androidx.core.content.edit import org.matrix.android.sdk.internal.database.model.SESSION_REALM_SCHEMA
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.di.UserMd5
import org.matrix.android.sdk.internal.session.SessionModule import org.matrix.android.sdk.internal.session.SessionModule
import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
private const val REALM_NAME = "disk_store.realm" private const val REALM_NAME = "disk_store.realm"
/** /**
@ -44,61 +38,19 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File, @SessionFilesDirectory val directory: File,
@SessionId val sessionId: String, @SessionId val sessionId: String,
@UserMd5 val userMd5: String, @UserMd5 val userMd5: String,
context: Context
) { ) {
// Keep legacy preferences name for compatibility reason
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
fun create(): RealmConfiguration { fun create(): RealmConfiguration {
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false) val realmConfiguration = RealmConfiguration.Builder(SESSION_REALM_SCHEMA)
if (shouldClearRealm) { .directory(directory.path)
Timber.e("************************************************************")
Timber.e("The realm file session was corrupted and couldn't be loaded.")
Timber.e("The file has been deleted to recover.")
Timber.e("************************************************************")
deleteRealmFiles()
}
sharedPreferences.edit {
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
}
val realmConfiguration = RealmConfiguration.Builder()
.compactOnLaunch(RealmCompactOnLaunch())
.directory(directory)
.name(REALM_NAME) .name(REALM_NAME)
.apply { .apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
} }
.allowWritesOnUiThread(true)
.modules(SessionRealmModule())
.schemaVersion(realmSessionStoreMigration.schemaVersion) .schemaVersion(realmSessionStoreMigration.schemaVersion)
.migration(realmSessionStoreMigration) .migration(realmSessionStoreMigration)
.build() .build()
// Try creating a realm instance and if it succeeds we can clear the flag
Realm.getInstance(realmConfiguration).use {
Timber.v("Successfully create realm instance")
sharedPreferences.edit {
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
}
}
return realmConfiguration return realmConfiguration
} }
// Delete all the realm files of the session
private fun deleteRealmFiles() {
if (BuildConfig.DEBUG) {
Timber.e("No op because it is a debug build")
return
}
listOf(REALM_NAME, "${REALM_NAME}.lock", "${REALM_NAME}.note", "${REALM_NAME}.management").forEach { file ->
try {
File(directory, file).deleteRecursively()
} catch (e: Exception) {
Timber.e(e, "Unable to delete files")
}
}
}
} }

View File

@ -24,20 +24,18 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.model.cleanUp import org.matrix.android.sdk.internal.database.model.cleanUp
import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.find
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereChunkId
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import timber.log.Timber import timber.log.Timber
@ -47,14 +45,11 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
} else { } else {
val stateKey = stateEvent.stateKey ?: return val stateKey = stateEvent.stateKey ?: return
val type = stateEvent.type val type = stateEvent.type
val pastStateEvent = stateEvents.where() val indexOfStateEvent = stateEvents.indexOfFirst {
.equalTo(EventEntityFields.ROOM_ID, roomId) it.roomId == roomId && it.stateKey == stateKey && it.type == type
.equalTo(EventEntityFields.STATE_KEY, stateKey) }
.equalTo(CurrentStateEventEntityFields.TYPE, type) if (indexOfStateEvent != -1) {
.findFirst() stateEvents.removeAt(indexOfStateEvent)
if (pastStateEvent != null) {
stateEvents.remove(pastStateEvent)
} }
stateEvents.add(stateEvent) stateEvents.add(stateEvent)
} }
@ -72,7 +67,7 @@ internal fun ChunkEntity.addTimelineEvent(
if (timelineEvents.find(eventId) != null) { if (timelineEvents.find(eventId) != null) {
return null return null
} }
val displayIndex = nextDisplayIndex(direction) val displayIndex = nextDisplayIndex(realm, direction)
val localId = TimelineEventEntity.nextId(realm) val localId = TimelineEventEntity.nextId(realm)
val senderId = eventEntity.sender ?: "" val senderId = eventEntity.sender ?: ""
@ -149,13 +144,17 @@ private fun handleReadReceipts(realm: MutableRealm, roomId: String, eventEntity:
return readReceiptsSummaryEntity return readReceiptsSummaryEntity
} }
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int { internal fun ChunkEntity.nextDisplayIndex(realm: TypedRealm, direction: PaginationDirection): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> { PaginationDirection.FORWARDS -> {
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1 (TimelineEventEntity.whereChunkId(realm, chunkId)
.max("displayIndex", Int::class)
.find() ?: 0) + 1
} }
PaginationDirection.BACKWARDS -> { PaginationDirection.BACKWARDS -> {
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1 (TimelineEventEntity.whereChunkId(realm, chunkId)
.min("displayIndex", Int::class)
.find() ?: 0) - 1
} }
} }
} }

View File

@ -17,11 +17,10 @@
package org.matrix.android.sdk.internal.database.helper package org.matrix.android.sdk.internal.database.helper
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort
import io.realm.kotlin.MutableRealm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.Sort
import org.matrix.android.sdk.api.session.events.model.UnsignedData import org.matrix.android.sdk.api.session.events.model.UnsignedData
import org.matrix.android.sdk.api.session.events.model.isRedacted import org.matrix.android.sdk.api.session.events.model.isRedacted
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -32,14 +31,14 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.find
import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.findIncludingEvent
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereChunkId
import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.database.query.whereRoomId
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.extensions.realm import org.matrix.android.sdk.internal.query.process
import timber.log.Timber import timber.log.Timber
private typealias Summary = Pair<Int, TimelineEventEntity>? private typealias Summary = Pair<Int, TimelineEventEntity>?
@ -62,7 +61,7 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
val latestEventInThread = threadSummary.second val latestEventInThread = threadSummary.second
// If this is a thread message, find its root event if exists // If this is a thread message, find its root event if exists
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent(realm) else eventEntity
rootThreadEvent?.markEventAsRoot( rootThreadEvent?.markEventAsRoot(
inThreadMessages = inThreadMessages, inThreadMessages = inThreadMessages,
@ -80,11 +79,12 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
* Finds the root event of the the current thread event message. * Finds the root event of the the current thread event message.
* Returns the EventEntity or null if the root event do not exist * Returns the EventEntity or null if the root event do not exist
*/ */
internal fun EventEntity.findRootThreadEvent(): EventEntity? = internal fun EventEntity.findRootThreadEvent(realm: TypedRealm): EventEntity? =
rootThreadEventId?.let { rootThreadEventId?.let { eventId ->
EventEntity EventEntity
.where(realm, it) .where(realm, eventId)
.findFirst() .first()
.find()
} }
/** /**
@ -122,15 +122,15 @@ internal fun EventEntity.threadSummaryInThread(realm: TypedRealm, rootThreadEven
// Iterate the chunk until we find our latest event // Iterate the chunk until we find our latest event
while (result == null) { while (result == null) {
result = findLatestSortedChunkEvent(chunk, rootThreadEventId) result = findLatestSortedChunkEvent(realm, chunk, rootThreadEventId)
chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break
} }
if (result == null && chunkEntity != null) { if (result == null && chunkEntity != null) {
// Find latest event from our current chunk // Find latest event from our current chunk
result = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId) result = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
} else if (result != null && chunkEntity != null) { } else if (result != null && chunkEntity != null) {
val currentChunkLatestEvent = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId) val currentChunkLatestEvent = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
result = findMostRecentEvent(result, currentChunkLatestEvent) result = findMostRecentEvent(result, currentChunkLatestEvent)
} }
@ -185,37 +185,41 @@ private fun findMostRecentEvent(result: TimelineEventEntity, currentChunkLatestE
/** /**
* Find the latest event of the current chunk. * Find the latest event of the current chunk.
*/ */
private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? = private fun findLatestSortedChunkEvent(realm: TypedRealm, chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull { TimelineEventEntity.whereChunkId(realm, chunk.chunkId)
it.root?.rootThreadEventId == rootThreadEventId .sort("displayIndex", Sort.DESCENDING)
} .find()
.firstOrNull {
it.root?.rootThreadEventId == rootThreadEventId
}
/** /**
* Find all TimelineEventEntity that are root threads for the specified room. * Find all TimelineEventEntity that are root threads for the specified room.
* @param realm the realm instance * @param realm the realm instance
* @param roomId The room that all stored root threads will be returned * @param roomId The room that all stored root threads will be returned
*/ */
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> = internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: TypedRealm, roomId: String): RealmQuery<TimelineEventEntity> =
TimelineEventEntity TimelineEventEntity
.whereRoomId(realm, roomId = roomId) .whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) .query("root.isRootThread == true")
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) .query("ownedByThreadChunk == false")
.sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING) .sort("root.originServerTs", Sort.DESCENDING)
/** /**
* Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement. * Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement.
*/ */
internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: String): List<TimelineEvent> = internal fun List<TimelineEvent>.mapEventsWithEdition(realm: TypedRealm, roomId: String): List<TimelineEvent> =
this.map { this.map {
EventAnnotationsSummaryEntity EventAnnotationsSummaryEntity
.where(realm, roomId, eventId = it.eventId) .where(realm, roomId, eventId = it.eventId)
.findFirst() .first()
.find()
?.editSummary ?.editSummary
?.editions ?.editions
?.lastOrNull() ?.lastOrNull()
?.eventId ?.eventId
?.let { editedEventId -> ?.let { editedEventId ->
TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent -> TimelineEventEntity.where(realm, roomId, eventId = editedEventId).first().find()?.let { editedEvent ->
it.root.threadDetails = it.root.threadDetails?.copy( it.root.threadDetails = it.root.threadDetails?.copy(
lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
?: "(edited)" ?: "(edited)"
@ -230,15 +234,11 @@ internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: Stri
* @param realm the realm instance * @param realm the realm instance
* @param roomId The roomId that the user is currently in * @param roomId The roomId that the user is currently in
*/ */
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> = internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: TypedRealm, roomId: String): RealmQuery<TimelineEventEntity> =
TimelineEventEntity TimelineEventEntity
.whereRoomId(realm, roomId = roomId) .whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) .query("root.isRootThread == true")
.beginGroup() .process("root.threadNotificationStateStr", listOf(ThreadNotificationState.NEW_MESSAGE, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE))
.equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_MESSAGE.name)
.or()
.equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE.name)
.endGroup()
/** /**
* Returns whether or not the given user is participating in a current thread. * Returns whether or not the given user is participating in a current thread.
@ -247,14 +247,13 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
* @param rootThreadEventId the thread that the search will be done * @param rootThreadEventId the thread that the search will be done
* @param senderId the user that will try to find participation * @param senderId the user that will try to find participation
*/ */
internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Realm, roomId: String, rootThreadEventId: String, senderId: String): Boolean = internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: TypedRealm, roomId: String, rootThreadEventId: String, senderId: String): Boolean =
TimelineEventEntity TimelineEventEntity
.whereRoomId(realm, roomId = roomId) .whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .query("root.rootThreadEventId == $0", rootThreadEventId)
.equalTo(TimelineEventEntityFields.ROOT.SENDER, senderId) .query("root.sender == $0", senderId)
.findFirst() .first()
?.let { true } .find() != null
?: false
/** /**
* Returns whether or not the given user is mentioned in a current thread. * Returns whether or not the given user is mentioned in a current thread.
@ -263,22 +262,21 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re
* @param rootThreadEventId the thread that the search will be done * @param rootThreadEventId the thread that the search will be done
* @param userId the user that will try to find if there is a mention * @param userId the user that will try to find if there is a mention
*/ */
internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm, roomId: String, rootThreadEventId: String, userId: String): Boolean = internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: TypedRealm, roomId: String, rootThreadEventId: String, userId: String): Boolean =
TimelineEventEntity TimelineEventEntity
.whereRoomId(realm, roomId = roomId) .whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .query("root.rootThreadEventId == $0", rootThreadEventId)
.equalTo(TimelineEventEntityFields.ROOT.SENDER, userId) .query("root.sender == $0", userId)
.findAll() .find()
.firstOrNull { isUserMentioned(userId, it) } .firstOrNull { isUserMentioned(userId, it) } != null
?.let { true }
?: false
/** /**
* Find the read receipt for the current user. * Find the read receipt for the current user.
*/ */
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? = internal fun findMyReadReceipt(realm: TypedRealm, roomId: String, userId: String): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId) ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
.findFirst() .first()
.find()
?.eventId ?.eventId
/** /**
@ -296,18 +294,15 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin
* Important: It will work only with the latest chunk, while read marker will be changed * Important: It will work only with the latest chunk, while read marker will be changed
* immediately so we should not display wrong notifications * immediately so we should not display wrong notifications
*/ */
internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) { internal fun updateNotificationsNew(roomId: String, realm: MutableRealm, currentUserId: String) {
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return
val readReceiptChunk = ChunkEntity val readReceiptChunk = ChunkEntity
.findIncludingEvent(realm, readReceipt) ?: return .findIncludingEvent(realm, readReceipt) ?: return
val readReceiptChunkTimelineEvents = readReceiptChunk val readReceiptChunkTimelineEvents = TimelineEventEntity.whereChunkId(realm, chunkId = readReceiptChunk.chunkId)
.timelineEvents .sort("displayIndex", Sort.ASCENDING)
.where() .find() ?: return
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll() ?: return
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt } val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
@ -357,7 +352,7 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
rootThreadEventId = eventId, rootThreadEventId = eventId,
senderId = currentUserId senderId = currentUserId
) )
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst() val rootThreadEventEntity = EventEntity.where(realm, eventId).first().find()
if (isUserParticipating) { if (isUserParticipating) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.database.model package org.matrix.android.sdk.internal.database.model
import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
@ -24,52 +23,48 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
/** /**
* Realm module for Session. * Realm module for Session.
*/ */
@RealmModule( internal val SESSION_REALM_SCHEMA = setOf(
library = true, ChunkEntity::class,
classes = [ EventEntity::class,
ChunkEntity::class, EventInsertEntity::class,
EventEntity::class, TimelineEventEntity::class,
EventInsertEntity::class, FilterEntity::class,
TimelineEventEntity::class, ReadReceiptEntity::class,
FilterEntity::class, RoomEntity::class,
ReadReceiptEntity::class, RoomSummaryEntity::class,
RoomEntity::class, RoomTagEntity::class,
RoomSummaryEntity::class, SyncEntity::class,
RoomTagEntity::class, PendingThreePidEntity::class,
SyncEntity::class, UserEntity::class,
PendingThreePidEntity::class, IgnoredUserEntity::class,
UserEntity::class, BreadcrumbsEntity::class,
IgnoredUserEntity::class, UserThreePidEntity::class,
BreadcrumbsEntity::class, EventAnnotationsSummaryEntity::class,
UserThreePidEntity::class, ReactionAggregatedSummaryEntity::class,
EventAnnotationsSummaryEntity::class, EditAggregatedSummaryEntity::class,
ReactionAggregatedSummaryEntity::class, EditionOfEvent::class,
EditAggregatedSummaryEntity::class, PollResponseAggregatedSummaryEntity::class,
EditionOfEvent::class, LiveLocationShareAggregatedSummaryEntity::class,
PollResponseAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class, PushRulesEntity::class,
ReferencesAggregatedSummaryEntity::class, PushRuleEntity::class,
PushRulesEntity::class, PushConditionEntity::class,
PushRuleEntity::class, PreviewUrlCacheEntity::class,
PushConditionEntity::class, PusherEntity::class,
PreviewUrlCacheEntity::class, PusherDataEntity::class,
PusherEntity::class, ReadReceiptsSummaryEntity::class,
PusherDataEntity::class, ReadMarkerEntity::class,
ReadReceiptsSummaryEntity::class, UserDraftsEntity::class,
ReadMarkerEntity::class, DraftEntity::class,
UserDraftsEntity::class, HomeServerCapabilitiesEntity::class,
DraftEntity::class, RoomMemberSummaryEntity::class,
HomeServerCapabilitiesEntity::class, CurrentStateEventEntity::class,
RoomMemberSummaryEntity::class, UserAccountDataEntity::class,
CurrentStateEventEntity::class, ScalarTokenEntity::class,
UserAccountDataEntity::class, WellknownIntegrationManagerConfigEntity::class,
ScalarTokenEntity::class, RoomAccountDataEntity::class,
WellknownIntegrationManagerConfigEntity::class, SpaceChildSummaryEntity::class,
RoomAccountDataEntity::class, SpaceParentSummaryEntity::class,
SpaceChildSummaryEntity::class, UserPresenceEntity::class,
SpaceParentSummaryEntity::class, ThreadSummaryEntity::class
UserPresenceEntity::class,
ThreadSummaryEntity::class
]
) )
internal class SessionRealmModule

View File

@ -16,20 +16,19 @@
package org.matrix.android.sdk.internal.database.query package org.matrix.android.sdk.internal.database.query
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.RealmQuery import io.realm.kotlin.TypedRealm
import io.realm.kotlin.where import io.realm.kotlin.query.RealmQuery
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntityFields
internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<ReferencesAggregatedSummaryEntity> { internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: TypedRealm, eventId: String): RealmQuery<ReferencesAggregatedSummaryEntity> {
val query = realm.where<ReferencesAggregatedSummaryEntity>() return realm.query(ReferencesAggregatedSummaryEntity::class)
query.equalTo(ReferencesAggregatedSummaryEntityFields.EVENT_ID, eventId) .query("eventId == $0", eventId)
return query
} }
internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, txID: String): ReferencesAggregatedSummaryEntity { internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: MutableRealm, txID: String): ReferencesAggregatedSummaryEntity {
return realm.createObject(ReferencesAggregatedSummaryEntity::class.java).apply { val entity = ReferencesAggregatedSummaryEntity().apply {
this.eventId = txID this.eventId = txID
} }
return realm.copyToRealm(entity)
} }

View File

@ -20,10 +20,12 @@ import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.RealmResults
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.andIf import org.matrix.android.sdk.internal.database.andIf
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.database.queryNotIn import org.matrix.android.sdk.internal.database.queryNotIn
import org.matrix.android.sdk.internal.query.process
internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String? = null): RealmQuery<RoomSummaryEntity> { internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
return realm.query(RoomSummaryEntity::class) return realm.query(RoomSummaryEntity::class)
@ -32,6 +34,12 @@ internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String
} }
} }
internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String, memberships: List<Membership>): RealmQuery<RoomSummaryEntity> {
return realm.query(RoomSummaryEntity::class)
.query("roomId == $0", roomId)
.process("membershipStr", memberships)
}
internal fun RoomSummaryEntity.Companion.findByAlias(realm: TypedRealm, roomAlias: String): RoomSummaryEntity? { internal fun RoomSummaryEntity.Companion.findByAlias(realm: TypedRealm, roomAlias: String): RoomSummaryEntity? {
val roomSummary = realm.query(RoomSummaryEntity::class) val roomSummary = realm.query(RoomSummaryEntity::class)
.query("canonicalAlias == $0", roomAlias) .query("canonicalAlias == $0", roomAlias)

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.query
import io.realm.kotlin.TypedRealm import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.types.ObjectId
import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmList
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
@ -60,6 +61,14 @@ internal fun TimelineEventEntity.Companion.whereRoomId(
.query("roomId == $0", roomId) .query("roomId == $0", roomId)
} }
internal fun TimelineEventEntity.Companion.whereChunkId(
realm: TypedRealm,
chunkId: ObjectId
): RealmQuery<TimelineEventEntity> {
return where(realm)
.query("chunkId == $0", chunkId)
}
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent( internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(
realm: TypedRealm, realm: TypedRealm,
senderMembershipEventId: String senderMembershipEventId: String

View File

@ -16,42 +16,40 @@
package org.matrix.android.sdk.internal.database.tools package org.matrix.android.sdk.internal.database.tools
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.database.RealmInstance
internal class RealmDebugTools( internal class RealmDebugTools(
private val realmConfiguration: RealmConfiguration private val realmInstance: RealmInstance
) { ) {
/** /**
* Get info about the DB. * Get info about the DB.
*/ */
fun getInfo(baseName: String): String { fun getInfo(baseName: String): String {
val realm = realmInstance.getBlockingRealm()
return buildString { return buildString {
append("\n$baseName Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}") val realmConfiguration = realmInstance.realmConfiguration
append("\n$baseName Realm located at : ${realmConfiguration.path}/${realmConfiguration.name}")
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
val key = realmConfiguration.encryptionKey.joinToString("") { byte -> "%02x".format(byte) } val key = realmConfiguration.encryptionKey?.joinToString("") { byte -> "%02x".format(byte) }
append("\n$baseName Realm encryption key : $key") append("\n$baseName Realm encryption key : $key")
} }
Realm.getInstance(realmConfiguration).use { realm -> // Check if we have data
// Check if we have data separator()
separator() separator()
separator() var total = 0L
append("\n$baseName Realm is empty: ${realm.isEmpty}") val maxNameLength = realmConfiguration.schema.maxOf { it.simpleName?.length ?: 0 }
var total = 0L realmConfiguration.schema.forEach { modelClazz ->
val maxNameLength = realmConfiguration.realmObjectClasses.maxOf { it.simpleName.length } val count = realm.query(modelClazz).count().find()
realmConfiguration.realmObjectClasses.forEach { modelClazz -> total += count
val count = realm.where(modelClazz).count() append("\n$baseName Realm - count ${modelClazz.simpleName?.padEnd(maxNameLength)} : $count")
total += count
append("\n$baseName Realm - count ${modelClazz.simpleName.padEnd(maxNameLength)} : $count")
}
separator()
append("\n$baseName Realm - total count: $total")
separator()
separator()
} }
separator()
append("\n$baseName Realm - total count: $total")
separator()
separator()
} }
} }

View File

@ -16,26 +16,29 @@
package org.matrix.android.sdk.internal.debug package org.matrix.android.sdk.internal.debug
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import org.matrix.android.sdk.api.debug.DebugService import org.matrix.android.sdk.api.debug.DebugService
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
import org.matrix.android.sdk.internal.di.AuthDatabase
import org.matrix.android.sdk.internal.di.GlobalDatabase
import javax.inject.Inject import javax.inject.Inject
internal class DefaultDebugService @Inject constructor( internal class DefaultDebugService @Inject constructor(
// @AuthDatabase private val realmConfigurationAuth: RealmConfiguration, @AuthDatabase private val authRealmInstance: RealmInstance,
// @GlobalDatabase private val realmConfigurationGlobal: RealmConfiguration, @GlobalDatabase private val globalRealmInstance: RealmInstance,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
) : DebugService { ) : DebugService {
override fun getAllRealmConfigurations(): List<RealmConfiguration> { override fun getAllRealmConfigurations(): List<RealmConfiguration> {
return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() +
// realmConfigurationAuth + authRealmInstance.realmConfiguration + globalRealmInstance.realmConfiguration
// realmConfigurationGlobal
} }
override fun getDbUsageInfo() = buildString { override fun getDbUsageInfo() = buildString {
//append(RealmDebugTools(realmConfigurationAuth).getInfo("Auth")) append(RealmDebugTools(authRealmInstance).getInfo("Auth"))
//append(RealmDebugTools(realmConfigurationGlobal).getInfo("Global")) append(RealmDebugTools(globalRealmInstance).getInfo("Global"))
append(sessionManager.getLastSession()?.getDbUsageInfo()) append(sessionManager.getLastSession()?.getDbUsageInfo())
} }
} }

View File

@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session
import androidx.annotation.MainThread import androidx.annotation.MainThread
import dagger.Lazy import dagger.Lazy
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -68,7 +68,11 @@ import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.IdentityDatabase
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
@ -82,10 +86,12 @@ internal class DefaultSession @Inject constructor(
override val sessionParams: SessionParams, override val sessionParams: SessionParams,
private val workManagerProvider: WorkManagerProvider, private val workManagerProvider: WorkManagerProvider,
private val globalErrorHandler: GlobalErrorHandler, private val globalErrorHandler: GlobalErrorHandler,
@SessionId @SessionDatabase private val sessionRealmInstance: RealmInstance,
override val sessionId: String, @CryptoDatabase private val cryptoRealmInstance: RealmInstance,
@IdentityDatabase private val identityRealmInstance: RealmInstance,
@ContentScannerDatabase private val contentScannerRealmInstance: RealmInstance,
@SessionId override val sessionId: String,
override val coroutineDispatchers: MatrixCoroutineDispatchers, override val coroutineDispatchers: MatrixCoroutineDispatchers,
@SessionDatabase private val realmConfiguration: RealmConfiguration,
private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>,
private val sessionListeners: SessionListeners, private val sessionListeners: SessionListeners,
private val roomService: Lazy<RoomService>, private val roomService: Lazy<RoomService>,
@ -258,18 +264,18 @@ internal class DefaultSession @Inject constructor(
} }
override fun getDbUsageInfo() = buildString { override fun getDbUsageInfo() = buildString {
append(RealmDebugTools(realmConfiguration).getInfo("Session")) append(RealmDebugTools(sessionRealmInstance).getInfo("Session"))
//append(RealmDebugTools(realmConfigurationCrypto).getInfo("Crypto")) append(RealmDebugTools(cryptoRealmInstance).getInfo("Crypto"))
//append(RealmDebugTools(realmConfigurationIdentity).getInfo("Identity")) append(RealmDebugTools(identityRealmInstance).getInfo("Identity"))
//append(RealmDebugTools(realmConfigurationContentScanner).getInfo("ContentScanner")) append(RealmDebugTools(contentScannerRealmInstance).getInfo("ContentScanner"))
} }
override fun getRealmConfigurations(): List<RealmConfiguration> { override fun getRealmConfigurations(): List<RealmConfiguration> {
return listOf( return listOf(
realmConfiguration, sessionRealmInstance.realmConfiguration,
// realmConfigurationCrypto, cryptoRealmInstance.realmConfiguration,
// realmConfigurationIdentity, identityRealmInstance.realmConfiguration,
// realmConfigurationContentScanner, contentScannerRealmInstance.realmConfiguration
) )
} }
} }

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.internal.session package org.matrix.android.sdk.internal.session
import io.realm.Realm import io.realm.kotlin.MutableRealm
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
@ -24,7 +24,7 @@ internal interface EventInsertLiveProcessor {
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
suspend fun process(realm: Realm, event: Event) fun process(realm: MutableRealm, event: Event)
/** /**
* Called after transaction. * Called after transaction.

View File

@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.session
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import com.zhuinden.monarchy.Monarchy
import dagger.Binds import dagger.Binds
import dagger.Lazy import dagger.Lazy
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
import io.realm.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
@ -49,7 +49,7 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.Authenticated
import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.CacheDirectory
@ -202,10 +202,16 @@ internal abstract class SessionModule {
@Provides @Provides
@SessionDatabase @SessionDatabase
@SessionScope @SessionScope
fun providesMonarchy(@SessionDatabase realmConfiguration: RealmConfiguration): Monarchy { fun providesRealmInstance(
return Monarchy.Builder() @SessionDatabase realmConfiguration: RealmConfiguration,
.setRealmConfiguration(realmConfiguration) @SessionCoroutineScope coroutineScope: CoroutineScope,
.build() matrixCoroutineDispatchers: MatrixCoroutineDispatchers
): RealmInstance {
return RealmInstance(
coroutineScope = coroutineScope,
realmConfiguration = realmConfiguration,
coroutineDispatcher = matrixCoroutineDispatchers.io
)
} }
@JvmStatic @JvmStatic
@ -367,10 +373,6 @@ internal abstract class SessionModule {
@IntoSet @IntoSet
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
@Binds
@IntoSet
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver

View File

@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.cache
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.cache.CacheService
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
@Module @Module
@ -31,8 +31,8 @@ internal abstract class CacheModule {
@JvmStatic @JvmStatic
@Provides @Provides
@SessionDatabase @SessionDatabase
fun providesClearCacheTask(@SessionDatabase realmConfiguration: RealmConfiguration): ClearCacheTask { fun providesClearCacheTask(@SessionDatabase realmInstance: RealmInstance): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration) return RealmKotlinClearCacheTask(realmInstance)
} }
} }

View File

@ -16,24 +16,13 @@
package org.matrix.android.sdk.internal.session.cache package org.matrix.android.sdk.internal.session.cache
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.RealmInstance import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.deleteAll import org.matrix.android.sdk.internal.database.deleteAll
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface ClearCacheTask : Task<Unit, Unit> internal interface ClearCacheTask : Task<Unit, Unit>
internal class RealmClearCacheTask @Inject constructor(private val realmConfiguration: RealmConfiguration) : ClearCacheTask {
override suspend fun execute(params: Unit) {
awaitTransaction(realmConfiguration) {
it.deleteAll()
}
}
}
internal class RealmKotlinClearCacheTask @Inject constructor(private val realmInstance: RealmInstance) : ClearCacheTask { internal class RealmKotlinClearCacheTask @Inject constructor(private val realmInstance: RealmInstance) : ClearCacheTask {
override suspend fun execute(params: Unit) { override suspend fun execute(params: Unit) {

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.call package org.matrix.android.sdk.internal.session.call
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.MutableRealm
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
@ -54,7 +55,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
return allowedTypes.contains(eventType) return allowedTypes.contains(eventType)
} }
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
eventsToPostProcess.add(event) eventsToPostProcess.add(event)
} }

View File

@ -16,9 +16,6 @@
package org.matrix.android.sdk.internal.session.cleanup package org.matrix.android.sdk.internal.session.cleanup
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.delay
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.crypto.CryptoModule import org.matrix.android.sdk.internal.crypto.CryptoModule
@ -47,7 +44,7 @@ internal class CleanupSession @Inject constructor(
@SessionFilesDirectory private val sessionFiles: File, @SessionFilesDirectory private val sessionFiles: File,
@SessionDownloadsDirectory private val sessionCache: File, @SessionDownloadsDirectory private val sessionCache: File,
private val realmKeysUtils: RealmKeysUtils, private val realmKeysUtils: RealmKeysUtils,
@SessionDatabase private val realmSessionConfiguration: RealmConfiguration, @SessionDatabase private val sessionRealmInstance: RealmInstance,
@CryptoDatabase private val cryptoRealmInstance: RealmInstance, @CryptoDatabase private val cryptoRealmInstance: RealmInstance,
@UserMd5 private val userMd5: String @UserMd5 private val userMd5: String
) { ) {
@ -61,8 +58,8 @@ internal class CleanupSession @Inject constructor(
} }
suspend fun cleanup() { suspend fun cleanup() {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration) val sessionNumberOfActiveVersions = sessionRealmInstance.getRealm().getNumberOfActiveVersions()
Timber.d("Realm instance ($sessionRealmCount)") Timber.d("Realm active sessions ($sessionNumberOfActiveVersions)")
Timber.d("Cleanup: release session...") Timber.d("Cleanup: release session...")
sessionManager.releaseSession(sessionId) sessionManager.releaseSession(sessionId)
@ -80,33 +77,12 @@ internal class CleanupSession @Inject constructor(
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
// Wait for all the Realm instance to be released properly. Closing Realm instance is async. Timber.d("Closing databases")
// After that we can safely delete the Realm files sessionRealmInstance.close()
waitRealmRelease()
cryptoRealmInstance.close() cryptoRealmInstance.close()
Timber.d("Cleanup: clear file system") Timber.d("Cleanup: clear file system")
sessionFiles.deleteRecursively() sessionFiles.deleteRecursively()
sessionCache.deleteRecursively() sessionCache.deleteRecursively()
} }
private suspend fun waitRealmRelease() {
var timeToWaitMillis = MAX_TIME_TO_WAIT_MILLIS
do {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
if (sessionRealmCount > 0) {
Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms for all Realm instance to be closed ($sessionRealmCount)")
delay(TIME_TO_WAIT_MILLIS)
timeToWaitMillis -= TIME_TO_WAIT_MILLIS
} else {
Timber.d("Finished waiting for all Realm instance to be closed ($sessionRealmCount)")
timeToWaitMillis = 0
}
} while (timeToWaitMillis > 0)
}
companion object {
private const val MAX_TIME_TO_WAIT_MILLIS = 10_000L
private const val TIME_TO_WAIT_MILLIS = 10L
}
} }

View File

@ -16,7 +16,8 @@
package org.matrix.android.sdk.internal.session.room package org.matrix.android.sdk.internal.session.room
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.deleteFromRealm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
@ -49,7 +50,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
@ -95,9 +95,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
return allowedTypes.contains(eventType) return allowedTypes.contains(eventType)
} }
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
try { // Temporary catch, should be removed try { // Temporary catch, should be removed
val roomId = event.roomId val roomId = event.roomId
event.eventId ?: return
if (roomId == null) { if (roomId == null) {
Timber.w("Event has no room id ${event.eventId}") Timber.w("Event has no room id ${event.eventId}")
return return
@ -114,10 +115,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId).first().find()
?.let { ?.let {
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId).find()
?.forEach { tet -> tet.annotations = it } .forEach { tet -> tet.annotations = it }
} }
} }
@ -213,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// } // }
} }
EventType.REDACTION -> { EventType.REDACTION -> {
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).first().find() }
?: return ?: return
when (eventToPrune.type) { when (eventToPrune.type) {
EventType.MESSAGE -> { EventType.MESSAGE -> {
@ -273,7 +274,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e
private fun handleReplace( private fun handleReplace(
realm: Realm, realm: MutableRealm,
event: Event, event: Event,
content: MessageContent, content: MessageContent,
roomId: String, roomId: String,
@ -285,7 +286,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val newContent = content.newContent ?: return val newContent = content.newContent ?: return
// Check that the sender is the same // Check that the sender is the same
val editedEvent = EventEntity.where(realm, targetEventId).findFirst() val editedEvent = EventEntity.where(realm, targetEventId).first().find()
if (editedEvent == null) { if (editedEvent == null) {
// We do not know yet about the edited event // We do not know yet about the edited event
} else if (editedEvent.sender != event.senderId) { } else if (editedEvent.sender != event.senderId) {
@ -302,16 +303,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (existingSummary == null) { if (existingSummary == null) {
Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)") Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
// create the edit summary // create the edit summary
eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) eventAnnotationsSummaryEntity.editSummary = realm.copyToRealm(EditAggregatedSummaryEntity())
.also { editSummary -> .also { editSummary ->
editSummary.editions.add( editSummary.editions.add(
EditionOfEvent( EditionOfEvent().apply {
senderId = event.senderId ?: "", this.senderId = event.senderId ?: ""
eventId = event.eventId, this.eventId = event.eventId
content = ContentMapper.map(newContent), this.content = ContentMapper.map(newContent)
timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0, this.timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0
isLocalEcho = isLocalEcho this.isLocalEcho = isLocalEcho
) }
) )
} }
} else { } else {
@ -334,18 +335,18 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} else { } else {
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)") Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
existingSummary.editions.add( existingSummary.editions.add(
EditionOfEvent( EditionOfEvent().apply {
senderId = event.senderId ?: "", this.senderId = event.senderId ?: ""
eventId = event.eventId, this.eventId = event.eventId
content = ContentMapper.map(newContent), this.content = ContentMapper.map(newContent)
timestamp = if (isLocalEcho) { this.timestamp = if (isLocalEcho) {
clock.epochMillis() clock.epochMillis()
} else { } else {
// Do not take local echo originServerTs here, could mess up ordering (keep old ts) // Do not take local echo originServerTs here, could mess up ordering (keep old ts)
event.originServerTs ?: clock.epochMillis() event.originServerTs ?: clock.epochMillis()
}, }
isLocalEcho = isLocalEcho this.isLocalEcho = isLocalEcho
) }
) )
} }
} }
@ -357,9 +358,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (!isLocalEcho) { if (!isLocalEcho) {
val replaceEvent = TimelineEventEntity val replaceEvent = TimelineEventEntity
.where(realm, roomId, eventId) .where(realm, roomId, eventId)
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) .query("ownedByThreadChunk == false")
.findFirst() .first()
handleThreadSummaryEdition(editedEvent, replaceEvent, existingSummary?.editions) .find()
handleThreadSummaryEdition(realm, editedEvent, replaceEvent, existingSummary?.editions)
} }
} }
@ -370,13 +372,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* @param editions list of edition of event * @param editions list of edition of event
*/ */
private fun handleThreadSummaryEdition( private fun handleThreadSummaryEdition(
realm: TypedRealm,
editedEvent: EventEntity?, editedEvent: EventEntity?,
replaceEvent: TimelineEventEntity?, replaceEvent: TimelineEventEntity?,
editions: List<EditionOfEvent>? editions: List<EditionOfEvent>?
) { ) {
replaceEvent ?: return replaceEvent ?: return
editedEvent ?: return editedEvent ?: return
editedEvent.findRootThreadEvent()?.apply { editedEvent.findRootThreadEvent(realm)?.apply {
val threadSummaryEventId = threadSummaryLatestMessage?.eventId val threadSummaryEventId = threadSummaryLatestMessage?.eventId
if (editedEvent.eventId == threadSummaryEventId || editions?.any { it.eventId == threadSummaryEventId } == true) { if (editedEvent.eventId == threadSummaryEventId || editions?.any { it.eventId == threadSummaryEventId } == true) {
// The edition is for the latest event or for any event replaced, this is to handle multiple // The edition is for the latest event or for any event replaced, this is to handle multiple
@ -393,7 +396,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
private fun handleInitialAggregatedRelations( private fun handleInitialAggregatedRelations(
realm: Realm, realm: MutableRealm,
event: Event, event: Event,
roomId: String, roomId: String,
aggregation: AggregatedAnnotation aggregation: AggregatedAnnotation
@ -402,10 +405,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
aggregation.chunk?.forEach { aggregation.chunk?.forEach {
if (it.type == EventType.REACTION) { if (it.type == EventType.REACTION) {
val eventId = event.eventId ?: "" val eventId = event.eventId ?: ""
val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).first().find()
if (existing == null) { if (existing == null) {
val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId) val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) val sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
sum.key = it.key sum.key = it.key
sum.firstTimestamp = event.originServerTs sum.firstTimestamp = event.originServerTs
?: 0 // TODO how to maintain order? ?: 0 // TODO how to maintain order?
@ -420,7 +423,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
private fun handleReaction( private fun handleReaction(
realm: Realm, realm: MutableRealm,
event: Event, event: Event,
roomId: String, roomId: String,
isLocalEcho: Boolean isLocalEcho: Boolean
@ -434,7 +437,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (RelationType.ANNOTATION == content.relatesTo?.type) { if (RelationType.ANNOTATION == content.relatesTo?.type) {
val reaction = content.relatesTo.key val reaction = content.relatesTo.key
val relatedEventID = content.relatesTo.eventId val relatedEventID = content.relatesTo.eventId
val reactionEventId = event.eventId val reactionEventId = event.eventId ?: return
Timber.v("Reaction $reactionEventId relates to $relatedEventID") Timber.v("Reaction $reactionEventId relates to $relatedEventID")
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventID) val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventID)
@ -442,14 +445,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val txId = event.unsignedData?.transactionId val txId = event.unsignedData?.transactionId
if (isLocalEcho && txId.isNullOrBlank()) { if (isLocalEcho && txId.isNullOrBlank()) {
Timber.w("Received a local echo with no transaction ID") Timber.w("Received a local echo with no transaction ID")
return
} }
if (sum == null) { if (sum == null) {
sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
sum.key = reaction sum.key = reaction
sum.firstTimestamp = event.originServerTs ?: 0 sum.firstTimestamp = event.originServerTs ?: 0
if (isLocalEcho) { if (isLocalEcho) {
Timber.v("Adding local echo reaction") Timber.v("Adding local echo reaction")
sum.sourceLocalEcho.add(txId) sum.sourceLocalEcho.add(txId!!)
sum.count = 1 sum.count = 1
} else { } else {
Timber.v("Adding synced reaction") Timber.v("Adding synced reaction")
@ -471,7 +475,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
sum.count += 1 sum.count += 1
if (isLocalEcho) { if (isLocalEcho) {
Timber.v("Adding local echo reaction") Timber.v("Adding local echo reaction")
sum.sourceLocalEcho.add(txId) sum.sourceLocalEcho.add(txId!!)
} else { } else {
Timber.v("Adding synced reaction") Timber.v("Adding synced reaction")
sum.sourceEvents.add(reactionEventId) sum.sourceEvents.add(reactionEventId)
@ -490,12 +494,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* Called when an event is deleted. * Called when an event is deleted.
*/ */
private fun handleRedactionOfReplace( private fun handleRedactionOfReplace(
realm: Realm, realm: MutableRealm,
redacted: EventEntity, redacted: EventEntity,
relatedEventId: String relatedEventId: String
) { ) {
Timber.d("Handle redaction of m.replace") Timber.d("Handle redaction of m.replace")
val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst() val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).first().find()
if (eventSummary == null) { if (eventSummary == null) {
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId") Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
return return
@ -506,11 +510,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
return return
} }
// Need to remove this event from the edition list // Need to remove this event from the edition list
sourceToDiscard.deleteFromRealm() realm.delete(sourceToDiscard)
} }
private fun handleReactionRedact( private fun handleReactionRedact(
realm: Realm, realm: MutableRealm,
eventToPrune: EventEntity eventToPrune: EventEntity
) { ) {
Timber.v("REDACTION of reaction ${eventToPrune.eventId}") Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
@ -520,11 +524,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val reactionKey = reactionContent.relatesTo.key val reactionKey = reactionContent.relatesTo.key
Timber.v("REMOVE reaction for key $reactionKey") Timber.v("REMOVE reaction for key $reactionKey")
val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).findFirst() val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).first().find()
if (summary != null) { if (summary != null) {
summary.reactionsSummary.where() summary.reactionsSummary
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey) .firstOrNull {
.findFirst()?.let { aggregation -> it.key == reactionKey
}?.let { aggregation ->
Timber.v("Find summary for key with ${aggregation.sourceEvents.size} known reactions (count:${aggregation.count})") Timber.v("Find summary for key with ${aggregation.sourceEvents.size} known reactions (count:${aggregation.count})")
Timber.v("Known reactions ${aggregation.sourceEvents.joinToString(",")}") Timber.v("Known reactions ${aggregation.sourceEvents.joinToString(",")}")
if (aggregation.sourceEvents.contains(eventToPrune.eventId)) { if (aggregation.sourceEvents.contains(eventToPrune.eventId)) {
@ -538,7 +543,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
if (aggregation.count == 0) { if (aggregation.count == 0) {
// delete! // delete!
aggregation.deleteFromRealm() realm.delete(aggregation)
} }
} else { } else {
Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known") Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known")
@ -549,7 +554,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) { private fun handleVerification(realm: MutableRealm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) {
event.eventId ?: return
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId) val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
val verifSummary = eventSummary.referencesSummaryEntity val verifSummary = eventSummary.referencesSummaryEntity
@ -597,7 +603,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
private fun handleBeaconLocationData(event: Event, realm: Realm, roomId: String, isLocalEcho: Boolean) { private fun handleBeaconLocationData(event: Event, realm: MutableRealm, roomId: String, isLocalEcho: Boolean) {
event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let { event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleBeaconLocationData( liveLocationAggregationProcessor.handleBeaconLocationData(
realm, realm,

View File

@ -16,14 +16,15 @@
package org.matrix.android.sdk.internal.session.room package org.matrix.android.sdk.internal.session.room
import io.realm.Realm import io.realm.kotlin.TypedRealm
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject import javax.inject.Inject
@ -35,29 +36,27 @@ internal interface RoomGetter {
@SessionScope @SessionScope
internal class DefaultRoomGetter @Inject constructor( internal class DefaultRoomGetter @Inject constructor(
private val realmSessionProvider: RealmSessionProvider, @SessionDatabase private val realmInstance: RealmInstance,
private val roomFactory: RoomFactory private val roomFactory: RoomFactory
) : RoomGetter { ) : RoomGetter {
override fun getRoom(roomId: String): Room? { override fun getRoom(roomId: String): Room? {
return realmSessionProvider.withRealm { realm -> val realm = realmInstance.getBlockingRealm()
createRoom(realm, roomId) return createRoom(realm, roomId)
}
} }
override fun getDirectRoomWith(otherUserId: String): String? { override fun getDirectRoomWith(otherUserId: String): String? {
return realmSessionProvider.withRealm { realm -> val realm = realmInstance.getBlockingRealm()
RoomSummaryEntity.where(realm) return RoomSummaryEntity.where(realm)
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) .query("isDirect == true")
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) .process("membershipStr", listOf(Membership.JOIN))
.findAll() .find()
.firstOrNull { dm -> dm.otherMemberIds.size == 1 && dm.otherMemberIds.first(null) == otherUserId } .firstOrNull { dm -> dm.otherMemberIds.size == 1 && dm.otherMemberIds.first() == otherUserId }
?.roomId ?.roomId
}
} }
private fun createRoom(realm: Realm, roomId: String): Room? { private fun createRoom(realm: TypedRealm, roomId: String): Room? {
return RoomEntity.where(realm, roomId).findFirst() return RoomEntity.where(realm, roomId).first().find()
?.let { roomFactory.create(roomId) } ?.let { roomFactory.create(roomId) }
} }
} }

View File

@ -22,6 +22,7 @@ import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.util.md5 import org.matrix.android.sdk.api.util.md5
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.get
@ -53,7 +54,7 @@ internal class DeactivateLiveLocationShareWorker(context: Context, params: Worke
) : SessionWorkerParams ) : SessionWorkerParams
@SessionDatabase @SessionDatabase
@Inject lateinit var realmConfiguration: RealmConfiguration @Inject lateinit var realmInstance: RealmInstance
override fun injectWith(injector: SessionComponent) { override fun injectWith(injector: SessionComponent) {
injector.inject(this) injector.inject(this)
@ -74,10 +75,10 @@ internal class DeactivateLiveLocationShareWorker(context: Context, params: Worke
} }
private suspend fun deactivateLiveLocationShare(params: Params) { private suspend fun deactivateLiveLocationShare(params: Params) {
awaitTransaction(realmConfiguration) { realm -> realmInstance.write {
Timber.d("deactivating live with id=${params.eventId}") Timber.d("deactivating live with id=${params.eventId}")
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get( val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm, realm = this,
roomId = params.roomId, roomId = params.roomId,
eventId = params.eventId eventId = params.eventId
) )

View File

@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.ext.realmListOf
import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
@ -50,7 +52,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
* Handle the content of a beacon info. * Handle the content of a beacon info.
* @return true if it has been processed, false if ignored. * @return true if it has been processed, false if ignored.
*/ */
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean { fun handleBeaconInfo(realm: MutableRealm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean {
if (event.senderId.isNullOrEmpty() || isLocalEcho) { if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return false return false
} }
@ -130,7 +132,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
* @return true if it has been processed, false if ignored. * @return true if it has been processed, false if ignored.
*/ */
fun handleBeaconLocationData( fun handleBeaconLocationData(
realm: Realm, realm: MutableRealm,
event: Event, event: Event,
content: MessageBeaconLocationDataContent, content: MessageBeaconLocationDataContent,
roomId: String, roomId: String,
@ -180,11 +182,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also { val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also {
it.add(eventId) it.add(eventId)
} }
aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray()) aggregatedSummary.relatedEventIds = realmListOf(*updatedEventIds.toTypedArray())
} }
private fun deactivateAllPreviousBeacons( private fun deactivateAllPreviousBeacons(
realm: Realm, realm: MutableRealm,
roomId: String, roomId: String,
userId: String, userId: String,
currentEventId: String, currentEventId: String,

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room.aggregation.poll package org.matrix.android.sdk.internal.session.room.aggregation.poll
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.MutableRealm
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -45,7 +46,7 @@ import javax.inject.Inject
class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor { class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor {
override fun handlePollStartEvent(realm: Realm, event: Event): Boolean { override fun handlePollStartEvent(realm: MutableRealm, event: Event): Boolean {
val content = event.getClearContent()?.toModel<MessagePollContent>() val content = event.getClearContent()?.toModel<MessagePollContent>()
if (content?.relatesTo?.type != RelationType.REPLACE) { if (content?.relatesTo?.type != RelationType.REPLACE) {
return false return false
@ -74,10 +75,11 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
return true return true
} }
override fun handlePollResponseEvent(session: Session, realm: Realm, event: Event): Boolean { override fun handlePollResponseEvent(session: Session, realm: MutableRealm, event: Event): Boolean {
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
val roomId = event.roomId ?: return false val roomId = event.roomId ?: return false
val senderId = event.senderId ?: return false val senderId = event.senderId ?: return false
event.eventId ?: return false
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
@ -95,7 +97,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
} }
val txId = event.unsignedData?.transactionId val txId = event.unsignedData?.transactionId
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId)
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) { if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId) aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
@ -153,12 +155,13 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
return true return true
} }
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean { override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: MutableRealm, event: Event): Boolean {
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
val roomId = event.roomId ?: return false val roomId = event.roomId ?: return false
val pollEventId = content.relatesTo?.eventId ?: return false val pollEventId = content.relatesTo?.eventId ?: return false
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
val isPollOwner = pollOwnerId == event.senderId val isPollOwner = pollOwnerId == event.senderId
event.eventId ?: return false
if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) { if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) {
return false return false
@ -170,7 +173,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
val txId = event.unsignedData?.transactionId val txId = event.unsignedData?.transactionId
aggregatedPollSummaryEntity.closedTime = event.originServerTs aggregatedPollSummaryEntity.closedTime = event.originServerTs
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId)
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) { if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId) aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
@ -188,17 +191,17 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
return pollEvent?.getLastMessageContent() as? MessagePollContent return pollEvent?.getLastMessageContent() as? MessagePollContent
} }
private fun getAnnotationsSummaryEntity(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { private fun getAnnotationsSummaryEntity(realm: MutableRealm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).first().find()
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
} }
private fun getAggregatedPollSummaryEntity( private fun getAggregatedPollSummaryEntity(
realm: Realm, realm: MutableRealm,
eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity
): PollResponseAggregatedSummaryEntity { ): PollResponseAggregatedSummaryEntity {
return eventAnnotationsSummaryEntity.pollResponseSummary return eventAnnotationsSummaryEntity.pollResponseSummary
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { ?: realm.copyToRealm(PollResponseAggregatedSummaryEntity()).also {
eventAnnotationsSummaryEntity.pollResponseSummary = it eventAnnotationsSummaryEntity.pollResponseSummary = it
} }
} }

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.internal.session.room.aggregation.poll package org.matrix.android.sdk.internal.session.room.aggregation.poll
import io.realm.Realm import io.realm.kotlin.MutableRealm
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
@ -28,7 +28,7 @@ interface PollAggregationProcessor {
* Returns true if the event is aggregated. * Returns true if the event is aggregated.
*/ */
fun handlePollStartEvent( fun handlePollStartEvent(
realm: Realm, realm: MutableRealm,
event: Event event: Event
): Boolean ): Boolean
@ -38,7 +38,7 @@ interface PollAggregationProcessor {
*/ */
fun handlePollResponseEvent( fun handlePollResponseEvent(
session: Session, session: Session,
realm: Realm, realm: MutableRealm,
event: Event event: Event
): Boolean ): Boolean
@ -49,7 +49,7 @@ interface PollAggregationProcessor {
fun handlePollEndEvent( fun handlePollEndEvent(
session: Session, session: Session,
powerLevelsHelper: PowerLevelsHelper, powerLevelsHelper: PowerLevelsHelper,
realm: Realm, realm: MutableRealm,
event: Event event: Event
): Boolean ): Boolean
} }

View File

@ -16,7 +16,8 @@
package org.matrix.android.sdk.internal.session.room.create package org.matrix.android.sdk.internal.session.room.create
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.UpdatePolicy
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -30,15 +31,16 @@ import javax.inject.Inject
internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor { internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor {
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
val createRoomContent = event.getClearContent().toModel<RoomCreateContent>() val createRoomContent = event.getClearContent().toModel<RoomCreateContent>()
val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).first().find()
?: RoomSummaryEntity(predecessorRoomId) ?: RoomSummaryEntity()
predecessorRoomSummary.roomId = predecessorRoomId
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
predecessorRoomSummary.isHiddenFromUser = true predecessorRoomSummary.isHiddenFromUser = true
realm.insertOrUpdate(predecessorRoomSummary) realm.copyToRealm(predecessorRoomSummary, updatePolicy = UpdatePolicy.ALL)
} }
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.room.location package org.matrix.android.sdk.internal.session.room.location
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.deleteFromRealm
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -41,12 +40,12 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO
} }
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) { if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) {
return return
} }
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst() val redactedEvent = EventEntity.where(realm, eventId = event.redacts).first().find()
?: return ?: return
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) { if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
@ -54,11 +53,11 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
if (liveSummary != null) { if (liveSummary != null) {
Timber.d("deleting live summary with id: ${liveSummary.eventId}") Timber.d("deleting live summary with id: ${liveSummary.eventId}")
liveSummary.deleteFromRealm() realm.delete(liveSummary)
val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId) val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId)
if (annotationsSummary != null) { if (annotationsSummary != null) {
Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}") Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}")
annotationsSummary.deleteFromRealm() realm.delete(annotationsSummary)
} }
} }
} }

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.room.location package org.matrix.android.sdk.internal.session.room.location
import io.realm.RealmConfiguration import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
@ -36,7 +35,7 @@ internal interface RedactLiveLocationShareTask : Task<RedactLiveLocationShareTas
} }
internal class DefaultRedactLiveLocationShareTask @Inject constructor( internal class DefaultRedactLiveLocationShareTask @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration, @SessionDatabase private val realmInstance: RealmInstance,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,
) : RedactLiveLocationShareTask { ) : RedactLiveLocationShareTask {
@ -60,9 +59,9 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor(
} }
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> { private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
return awaitTransaction(realmConfiguration) { realm -> return realmInstance.write {
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get( val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm, realm = this,
eventId = beaconInfoEventId eventId = beaconInfoEventId
) )
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList() aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()

View File

@ -16,22 +16,20 @@
package org.matrix.android.sdk.internal.session.room.membership.joining package org.matrix.android.sdk.internal.session.room.membership.joining
import io.realm.RealmConfiguration
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
@ -52,9 +50,7 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
internal class DefaultJoinRoomTask @Inject constructor( internal class DefaultJoinRoomTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val readMarkersTask: SetReadMarkersTask, private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase @SessionDatabase private val realmInstance: RealmInstance,
private val realmConfiguration: RealmConfiguration,
private val coroutineDispatcher: MatrixCoroutineDispatchers,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
private val globalErrorReceiver: GlobalErrorReceiver, private val globalErrorReceiver: GlobalErrorReceiver,
private val clock: Clock, private val clock: Clock,
@ -85,16 +81,15 @@ internal class DefaultJoinRoomTask @Inject constructor(
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val roomId = joinRoomResponse.roomId val roomId = joinRoomResponse.roomId
try { try {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
realm.where(RoomSummaryEntity::class.java) RoomSummaryEntity.where(realm, roomId = roomId)
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) .process("membershipStr", listOf(Membership.JOIN))
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
} }
} catch (exception: TimeoutCancellationException) { } catch (exception: TimeoutCancellationException) {
throw JoinRoomFailure.JoinedWithTimeout throw JoinRoomFailure.JoinedWithTimeout
} }
awaitTransaction(realmConfiguration) { realmInstance.write {
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis() RoomSummaryEntity.where(this, roomId).first().find()?.lastActivityTime = clock.epochMillis()
} }
setReadMarkers(roomId) setReadMarkers(roomId)
} }

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.room.prune package org.matrix.android.sdk.internal.session.room.prune
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.deleteFromRealm
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -47,22 +46,22 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
return eventType == EventType.REDACTION return eventType == EventType.REDACTION
} }
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
pruneEvent(realm, event) pruneEvent(realm, event)
} }
private fun pruneEvent(realm: Realm, redactionEvent: Event) { private fun pruneEvent(realm: MutableRealm, redactionEvent: Event) {
if (redactionEvent.redacts.isNullOrBlank()) { if (redactionEvent.redacts.isNullOrBlank()) {
return return
} }
// Check that we know this event // Check that we know this event
EventEntity.where(realm, eventId = redactionEvent.eventId ?: "").findFirst() ?: return EventEntity.where(realm, eventId = redactionEvent.eventId ?: "").first().find() ?: return
val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "") val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "")
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho") Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).first().find()
?: return ?: return
val typeToPrune = eventToPrune.type val typeToPrune = eventToPrune.type
@ -117,13 +116,13 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
* with respect to redactions. * with respect to redactions.
*/ */
private fun handleTimelineThreadSummaryIfNeeded( private fun handleTimelineThreadSummaryIfNeeded(
realm: Realm, realm: MutableRealm,
eventToPrune: EventEntity, eventToPrune: EventEntity,
isLocalEcho: Boolean, isLocalEcho: Boolean,
) { ) {
if (eventToPrune.isThread() && !isLocalEcho) { if (eventToPrune.isThread() && !isLocalEcho) {
val roomId = eventToPrune.roomId val roomId = eventToPrune.roomId
val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEvent = eventToPrune.findRootThreadEvent(realm) ?: return
val rootThreadEventId = eventToPrune.rootThreadEventId ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return
val inThreadMessages = countInThreadMessages( val inThreadMessages = countInThreadMessages(
@ -139,8 +138,10 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
rootThreadEvent.threadSummaryLatestMessage = null rootThreadEvent.threadSummaryLatestMessage = null
ThreadSummaryEntity ThreadSummaryEntity
.where(realm, roomId = roomId, rootThreadEventId) .where(realm, roomId = roomId, rootThreadEventId)
.findFirst() .first()
?.deleteFromRealm() .find()?.let {
realm.delete(it)
}
} }
} }
} }

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.room.read package org.matrix.android.sdk.internal.session.room.read
import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.TypedRealm
import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.internal.database.RealmInstance import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
@ -34,7 +33,6 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -65,9 +63,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
) : SetReadMarkersTask { ) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) { override suspend fun execute(params: SetReadMarkersTask.Params) {
val realm = realmInstance.getRealm()
val markers = mutableMapOf<String, String>() val markers = mutableMapOf<String, String>()
Timber.v("Execute set read marker with params: $params") Timber.v("Execute set read marker with params: $params")
val latestSyncedEventId = latestSyncedEventId(params.roomId) val latestSyncedEventId = latestSyncedEventId(realm, params.roomId)
val fullyReadEventId = if (params.forceReadMarker) { val fullyReadEventId = if (params.forceReadMarker) {
latestSyncedEventId latestSyncedEventId
} else { } else {
@ -79,7 +78,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
params.readReceiptEventId params.readReceiptEventId
} }
if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy.realmConfiguration, params.roomId, fullyReadEventId)) { if (fullyReadEventId != null && !isReadMarkerMoreRecent(realm, params.roomId, fullyReadEventId)) {
if (LocalEcho.isLocalEchoId(fullyReadEventId)) { if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
Timber.w("Can't set read marker for local event $fullyReadEventId") Timber.w("Can't set read marker for local event $fullyReadEventId")
} else { } else {
@ -87,7 +86,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
} }
} }
if (readReceiptEventId != null && if (readReceiptEventId != null &&
!isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId)) { !isEventRead(realm, userId, params.roomId, readReceiptEventId)) {
if (LocalEcho.isLocalEchoId(readReceiptEventId)) { if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event $readReceiptEventId") Timber.w("Can't set read receipt for local event $readReceiptEventId")
} else { } else {
@ -116,25 +115,23 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
} }
} }
private fun latestSyncedEventId(roomId: String): String? = private suspend fun latestSyncedEventId(realm: TypedRealm, roomId: String): String? =
Realm.getInstance(monarchy.realmConfiguration).use { realm -> TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
}
private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) { private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
monarchy.awaitTransaction { realm -> realmInstance.write {
val readMarkerId = markers[READ_MARKER] val readMarkerId = markers[READ_MARKER]
val readReceiptId = markers[READ_RECEIPT] val readReceiptId = markers[READ_RECEIPT]
if (readMarkerId != null) { if (readMarkerId != null) {
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId)) roomFullyReadHandler.handle(this, roomId, FullyReadContent(readMarkerId))
} }
if (readReceiptId != null) { if (readReceiptId != null) {
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis()) val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null) readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
} }
if (shouldUpdateRoomSummary) { if (shouldUpdateRoomSummary) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(this, roomId).first().find()
?: return@awaitTransaction ?: return@write
roomSummary.notificationCount = 0 roomSummary.notificationCount = 0
roomSummary.highlightCount = 0 roomSummary.highlightCount = 0
roomSummary.hasUnreadMessages = false roomSummary.hasUnreadMessages = false

View File

@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.util.toMatrixErrorStr import org.matrix.android.sdk.internal.util.toMatrixErrorStr
@ -55,7 +56,6 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
@Inject lateinit var sendEventTask: SendEventTask @Inject lateinit var sendEventTask: SendEventTask
@Inject lateinit var cryptoService: CryptoService @Inject lateinit var cryptoService: CryptoService
@Inject lateinit var cancelSendTracker: CancelSendTracker @Inject lateinit var cancelSendTracker: CancelSendTracker
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
override fun injectWith(injector: SessionComponent) { override fun injectWith(injector: SessionComponent) {
injector.inject(this) injector.inject(this)

View File

@ -17,14 +17,14 @@
package org.matrix.android.sdk.internal.session.room.threads.local package org.matrix.android.sdk.internal.session.room.threads.local
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy import androidx.lifecycle.asLiveData
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.realm.Realm
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread
@ -35,12 +35,11 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.util.awaitTransaction
internal class DefaultThreadsLocalService @AssistedInject constructor( internal class DefaultThreadsLocalService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
@UserId private val userId: String, @UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val realmInstance: RealmInstance,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
) : ThreadsLocalService { ) : ThreadsLocalService {
@ -50,56 +49,52 @@ internal class DefaultThreadsLocalService @AssistedInject constructor(
} }
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> { override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
return monarchy.findAllMappedWithChanges( return realmInstance.queryList(timelineEventMapper::map) {
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId)
{ timelineEventMapper.map(it) } }.asLiveData()
)
} }
override fun getMarkedThreadNotifications(): List<TimelineEvent> { override fun getMarkedThreadNotifications(): List<TimelineEvent> {
return monarchy.fetchAllMappedSync( val realm = realmInstance.getBlockingRealm()
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, return TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(realm, roomId = roomId)
{ timelineEventMapper.map(it) } .find()
) .map(timelineEventMapper::map)
} }
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> { override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
return monarchy.findAllMappedWithChanges( return realmInstance.queryList(timelineEventMapper::map) {
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId)
{ timelineEventMapper.map(it) } }.asLiveData()
)
} }
override fun getAllThreads(): List<TimelineEvent> { override fun getAllThreads(): List<TimelineEvent> {
return monarchy.fetchAllMappedSync( val realm = realmInstance.getBlockingRealm()
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, return TimelineEventEntity.findAllThreadsForRoomId(realm, roomId = roomId)
{ timelineEventMapper.map(it) } .find()
) .map(timelineEventMapper::map)
} }
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean { override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use { val realm = realmInstance.getBlockingRealm()
TimelineEventEntity.isUserParticipatingInThread( return TimelineEventEntity.isUserParticipatingInThread(
realm = it, realm = realm,
roomId = roomId, roomId = roomId,
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
senderId = userId senderId = userId
) )
}
} }
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> { override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
return Realm.getInstance(monarchy.realmConfiguration).use { val realm = realmInstance.getBlockingRealm()
threads.mapEventsWithEdition(it, roomId) return threads.mapEventsWithEdition(realm, roomId)
}
} }
override suspend fun markThreadAsRead(rootThreadEventId: String) { override suspend fun markThreadAsRead(rootThreadEventId: String) {
monarchy.awaitTransaction { realmInstance.write {
EventEntity.where( EventEntity.where(
realm = it, realm = this,
eventId = rootThreadEventId eventId = rootThreadEventId
).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE ).first().find()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
} }
} }
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.kotlin.TypedRealm
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
@ -57,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference
internal class DefaultTimeline( internal class DefaultTimeline(
private val roomId: String, private val roomId: String,
private val initialEventId: String?, private val initialEventId: String?,
private val realmConfiguration: RealmConfiguration, private val realmInstance: RealmInstance,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val readReceiptHandler: ReadReceiptHandler, private val readReceiptHandler: ReadReceiptHandler,
private val settings: TimelineSettings, private val settings: TimelineSettings,
@ -86,7 +88,6 @@ internal class DefaultTimeline(
private val forwardState = AtomicReference(Timeline.PaginationState()) private val forwardState = AtomicReference(Timeline.PaginationState())
private val backwardState = AtomicReference(Timeline.PaginationState()) private val backwardState = AtomicReference(Timeline.PaginationState())
private val backgroundRealm = AtomicReference<Realm>()
private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher() private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher()
private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher) private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher)
private val sequencer = SemaphoreCoroutineSequencer() private val sequencer = SemaphoreCoroutineSequencer()
@ -96,11 +97,11 @@ internal class DefaultTimeline(
private var rootThreadEventId: String? = null private var rootThreadEventId: String? = null
private val strategyDependencies = LoadTimelineStrategy.Dependencies( private val strategyDependencies = LoadTimelineStrategy.Dependencies(
timelineScope = timelineScope,
timelineSettings = settings, timelineSettings = settings,
realm = backgroundRealm, realmInstance = realmInstance,
eventDecryptor = eventDecryptor, eventDecryptor = eventDecryptor,
paginationTask = paginationTask, paginationTask = paginationTask,
realmConfiguration = realmConfiguration,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
fetchThreadTimelineTask = fetchThreadTimelineTask, fetchThreadTimelineTask = fetchThreadTimelineTask,
getContextOfEventTask = getEventTask, getContextOfEventTask = getEventTask,
@ -151,9 +152,7 @@ internal class DefaultTimeline(
isFromThreadTimeline = rootThreadEventId != null isFromThreadTimeline = rootThreadEventId != null
this@DefaultTimeline.rootThreadEventId = rootThreadEventId this@DefaultTimeline.rootThreadEventId = rootThreadEventId
// / // /
val realm = Realm.getInstance(realmConfiguration) ensureReadReceiptAreLoaded()
ensureReadReceiptAreLoaded(realm)
backgroundRealm.set(realm)
listenToPostSnapshotSignals() listenToPostSnapshotSignals()
openAround(initialEventId, rootThreadEventId) openAround(initialEventId, rootThreadEventId)
postSnapshot() postSnapshot()
@ -168,7 +167,6 @@ internal class DefaultTimeline(
sequencer.post { sequencer.post {
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
strategy.onStop() strategy.onStop()
backgroundRealm.get().closeQuietly()
} }
} }
} }
@ -408,14 +406,14 @@ internal class DefaultTimeline(
} }
} }
private fun ensureReadReceiptAreLoaded(realm: Realm) { private fun ensureReadReceiptAreLoaded() {
readReceiptHandler.getContentFromInitSync(roomId) readReceiptHandler.getContentFromInitSync(roomId)
?.also { ?.also {
Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId") Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
} }
?.let { readReceiptContent -> ?.let { readReceiptContent ->
realm.executeTransactionAsync { realmInstance.asyncWrite {
readReceiptHandler.handle(it, roomId, readReceiptContent, false, null) readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
readReceiptHandler.onContentFromInitSyncHandled(roomId) readReceiptHandler.onContentFromInitSyncHandled(roomId)
} }
} }

View File

@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
@ -39,7 +40,7 @@ import org.matrix.android.sdk.internal.util.time.Clock
internal class DefaultTimelineService @AssistedInject constructor( internal class DefaultTimelineService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val realmInstance: RealmInstance,
private val timelineInput: TimelineInput, private val timelineInput: TimelineInput,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val eventDecryptor: TimelineEventDecryptor, private val eventDecryptor: TimelineEventDecryptor,
@ -67,7 +68,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
roomId = roomId, roomId = roomId,
initialEventId = eventId, initialEventId = eventId,
settings = settings, settings = settings,
realmConfiguration = monarchy.realmConfiguration, realmInstance = realmInstance,
coroutineDispatchers = coroutineDispatchers, coroutineDispatchers = coroutineDispatchers,
paginationTask = paginationTask, paginationTask = paginationTask,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,

View File

@ -16,15 +16,16 @@
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.OrderedCollectionChangeSet import io.realm.kotlin.TypedRealm
import io.realm.OrderedRealmCollectionChangeListener import io.realm.kotlin.ext.isValid
import io.realm.Realm import io.realm.kotlin.notifications.InitialResults
import io.realm.RealmConfiguration import io.realm.kotlin.notifications.ResultsChange
import io.realm.RealmResults import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.createObject import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.executeTransactionAwait
import io.realm.kotlin.isValid
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
@ -34,10 +35,10 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addIfNecessary
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.deleteAndClearThreadEvents import org.matrix.android.sdk.internal.database.model.deleteAndClearThreadEvents
import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
@ -48,7 +49,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference
/** /**
* This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy. * This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy.
@ -89,11 +89,11 @@ internal class LoadTimelineStrategy constructor(
} }
data class Dependencies( data class Dependencies(
val timelineScope: CoroutineScope,
val timelineSettings: TimelineSettings, val timelineSettings: TimelineSettings,
val realm: AtomicReference<Realm>, val realmInstance: RealmInstance,
val eventDecryptor: TimelineEventDecryptor, val eventDecryptor: TimelineEventDecryptor,
val paginationTask: PaginationTask, val paginationTask: PaginationTask,
val realmConfiguration: RealmConfiguration,
val fetchThreadTimelineTask: FetchThreadTimelineTask, val fetchThreadTimelineTask: FetchThreadTimelineTask,
val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
val getContextOfEventTask: GetContextOfEventTask, val getContextOfEventTask: GetContextOfEventTask,
@ -112,20 +112,27 @@ internal class LoadTimelineStrategy constructor(
private var getContextLatch: CompletableDeferred<Unit>? = null private var getContextLatch: CompletableDeferred<Unit>? = null
private var chunkEntity: RealmResults<ChunkEntity>? = null private var chunkEntity: RealmResults<ChunkEntity>? = null
private var timelineChunk: TimelineChunk? = null private var timelineChunk: TimelineChunk? = null
private val chunkEntityListener = OrderedRealmCollectionChangeListener { _: RealmResults<ChunkEntity>, changeSet: OrderedCollectionChangeSet ->
// Can be call either when you open a permalink on an unknown event private suspend fun onChunkResultsChanged(resultsChange: ResultsChange<ChunkEntity>) {
// or when there is a gap in the timeline.
val shouldRebuildChunk = changeSet.insertions.isNotEmpty() suspend fun onUpdates(updatedResults: UpdatedResults<ChunkEntity>) {
if (shouldRebuildChunk) { val shouldRebuildChunk = updatedResults.insertions.isNotEmpty()
timelineChunk?.close(closeNext = true, closePrev = true) if (shouldRebuildChunk) {
timelineChunk = chunkEntity?.createTimelineChunk() timelineChunk?.close(closeNext = true, closePrev = true)
// If we are waiting for a result of get context, post completion timelineChunk = chunkEntity?.createTimelineChunk()
getContextLatch?.complete(Unit) // If we are waiting for a result of get context, post completion
// If we have a gap, just tell the timeline about it. getContextLatch?.complete(Unit)
if (timelineChunk?.hasReachedLastForward().orFalse()) { // If we have a gap, just tell the timeline about it.
dependencies.onLimitedTimeline() if (timelineChunk?.hasReachedLastForward().orFalse()) {
dependencies.onLimitedTimeline()
}
} }
} }
when (resultsChange) {
is InitialResults -> Unit
is UpdatedResults -> onUpdates(resultsChange)
}
} }
private val uiEchoManagerListener = object : UIEchoManager.Listener { private val uiEchoManagerListener = object : UIEchoManager.Listener {
@ -165,7 +172,8 @@ internal class LoadTimelineStrategy constructor(
private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock) private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource( private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
roomId = roomId, roomId = roomId,
realm = dependencies.realm, timelineScope = dependencies.timelineScope,
realmInstance = dependencies.realmInstance,
uiEchoManager = uiEchoManager, uiEchoManager = uiEchoManager,
timelineEventMapper = dependencies.timelineEventMapper, timelineEventMapper = dependencies.timelineEventMapper,
onEventsUpdated = dependencies.onEventsUpdated onEventsUpdated = dependencies.onEventsUpdated
@ -180,10 +188,11 @@ internal class LoadTimelineStrategy constructor(
suspend fun onStart() { suspend fun onStart() {
dependencies.eventDecryptor.start() dependencies.eventDecryptor.start()
dependencies.timelineInput.listeners.add(timelineInputListener) dependencies.timelineInput.listeners.add(timelineInputListener)
val realm = dependencies.realm.get() val realm = dependencies.realmInstance.getRealm()
sendingEventsDataSource.start()
chunkEntity = getChunkEntity(realm).also { chunkEntity = getChunkEntity(realm).also {
it.addChangeListener(chunkEntityListener) it.asFlow().onEach {
}.launchIn(dependencies.timelineScope)
timelineChunk = it.createTimelineChunk() timelineChunk = it.createTimelineChunk()
} }
@ -195,14 +204,12 @@ internal class LoadTimelineStrategy constructor(
suspend fun onStop() { suspend fun onStop() {
dependencies.eventDecryptor.destroy() dependencies.eventDecryptor.destroy()
dependencies.timelineInput.listeners.remove(timelineInputListener) dependencies.timelineInput.listeners.remove(timelineInputListener)
chunkEntity?.removeChangeListener(chunkEntityListener)
sendingEventsDataSource.stop()
timelineChunk?.close(closeNext = true, closePrev = true) timelineChunk?.close(closeNext = true, closePrev = true)
getContextLatch?.cancel() getContextLatch?.cancel()
chunkEntity = null chunkEntity = null
timelineChunk = null timelineChunk = null
if (mode is Mode.Thread) { if (mode is Mode.Thread) {
clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId) clearThreadChunkEntity(mode.rootThreadEventId)
} }
if (dependencies.timelineSettings.useLiveSenderInfo) { if (dependencies.timelineSettings.useLiveSenderInfo) {
liveRoomStateListener.stop() liveRoomStateListener.stop()
@ -267,22 +274,22 @@ internal class LoadTimelineStrategy constructor(
} }
} }
private suspend fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> { private suspend fun getChunkEntity(realm: TypedRealm): RealmResults<ChunkEntity> {
return when (mode) { return when (mode) {
is Mode.Live -> { is Mode.Live -> {
ChunkEntity.where(realm, roomId) ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .query("isLastForward == true")
.findAll() .find()
} }
is Mode.Permalink -> { is Mode.Permalink -> {
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId)) ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
} }
is Mode.Thread -> { is Mode.Thread -> {
recreateThreadChunkEntity(realm, mode.rootThreadEventId) recreateThreadChunkEntity(mode.rootThreadEventId)
ChunkEntity.where(realm, roomId) ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, mode.rootThreadEventId) .query("rootThreadEventId == $0", mode.rootThreadEventId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) .query("isLastForwardThread == true")
.findAll() .find()
} }
} }
} }
@ -291,19 +298,21 @@ internal class LoadTimelineStrategy constructor(
* Clear any existing thread chunk entity and create a new one, with the * Clear any existing thread chunk entity and create a new one, with the
* rootThreadEventId included. * rootThreadEventId included.
*/ */
private suspend fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) { private suspend fun recreateThreadChunkEntity(rootThreadEventId: String) {
realm.executeTransactionAwait { dependencies.realmInstance.write {
// Lets delete the chunk and start a new one // Lets delete the chunk and start a new one
ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { ChunkEntity.findLastForwardChunkOfThread(this, roomId, rootThreadEventId)?.let {
deleteAndClearThreadEvents(it)
Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..") Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..")
} }
val threadChunk = it.createObject<ChunkEntity>().apply { val threadChunk = copyToRealm(ChunkEntity().apply {
Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId") Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId")
this.rootThreadEventId = rootThreadEventId this.rootThreadEventId = rootThreadEventId
this.isLastForwardThread = true this.isLastForwardThread = true
} }
)
if (threadChunk.isValid()) { if (threadChunk.isValid()) {
RoomEntity.where(it, roomId).findFirst()?.addIfNecessary(threadChunk) RoomEntity.where(this, roomId).first().find()?.addIfNecessary(threadChunk)
} }
} }
} }
@ -311,9 +320,10 @@ internal class LoadTimelineStrategy constructor(
/** /**
* Clear any existing thread chunk. * Clear any existing thread chunk.
*/ */
private suspend fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) { private suspend fun clearThreadChunkEntity(rootThreadEventId: String) {
realm.executeTransactionAwait { dependencies.realmInstance.write {
ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { ChunkEntity.findLastForwardChunkOfThread(this, roomId, rootThreadEventId)?.let {
deleteAndClearThreadEvents(it)
Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..") Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..")
} }
} }
@ -323,9 +333,11 @@ internal class LoadTimelineStrategy constructor(
return timelineChunk?.hasReachedLastForward().orFalse() return timelineChunk?.hasReachedLastForward().orFalse()
} }
private fun RealmResults<ChunkEntity>.createTimelineChunk(): TimelineChunk? { private suspend fun RealmResults<ChunkEntity>.createTimelineChunk(): TimelineChunk? {
val realm = dependencies.realmInstance.getRealm()
return firstOrNull()?.let { return firstOrNull()?.let {
return TimelineChunk( return TimelineChunk(
timelineScope = dependencies.timelineScope,
chunkEntity = it, chunkEntity = it,
timelineSettings = dependencies.timelineSettings, timelineSettings = dependencies.timelineSettings,
roomId = roomId, roomId = roomId,
@ -333,7 +345,7 @@ internal class LoadTimelineStrategy constructor(
fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask, fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask,
eventDecryptor = dependencies.eventDecryptor, eventDecryptor = dependencies.eventDecryptor,
paginationTask = dependencies.paginationTask, paginationTask = dependencies.paginationTask,
realmConfiguration = dependencies.realmConfiguration, realm = realm,
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask, fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
timelineEventMapper = dependencies.timelineEventMapper, timelineEventMapper = dependencies.timelineEventMapper,
uiEchoManager = uiEchoManager, uiEchoManager = uiEchoManager,

View File

@ -16,78 +16,59 @@
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm import kotlinx.coroutines.CoroutineScope
import io.realm.RealmChangeListener import kotlinx.coroutines.flow.flatMapConcat
import io.realm.RealmList import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import java.util.concurrent.atomic.AtomicReference
internal interface SendingEventsDataSource { internal interface SendingEventsDataSource {
fun start()
fun stop()
fun buildSendingEvents(): List<TimelineEvent> fun buildSendingEvents(): List<TimelineEvent>
} }
internal class RealmSendingEventsDataSource( internal class RealmSendingEventsDataSource(
private val roomId: String, private val roomId: String,
private val realm: AtomicReference<Realm>, private val realmInstance: RealmInstance,
private val timelineScope: CoroutineScope,
private val uiEchoManager: UIEchoManager, private val uiEchoManager: UIEchoManager,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val onEventsUpdated: (Boolean) -> Unit private val onEventsUpdated: (Boolean) -> Unit
) : SendingEventsDataSource { ) : SendingEventsDataSource {
private var roomEntity: RoomEntity? = null private var sendingTimelineEvents: List<TimelineEventEntity>? = null
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events -> init {
if (events.isValid) { start()
}
private fun start() {
realmInstance.getRealmFlow().flatMapConcat { realm ->
RoomEntity.where(realm, roomId = roomId).first().asFlow()
}.onEach { change ->
val events = change.obj?.sendingTimelineEvents.orEmpty()
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
updateFrozenResults(events) sendingTimelineEvents = events
onEventsUpdated(false) onEventsUpdated(false)
} }.launchIn(timelineScope)
}
override fun start() {
val safeRealm = realm.get()
roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst()
sendingTimelineEvents = roomEntity?.sendingTimelineEvents
sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener)
updateFrozenResults(sendingTimelineEvents)
}
override fun stop() {
sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener)
updateFrozenResults(null)
sendingTimelineEvents = null
roomEntity = null
}
private fun updateFrozenResults(sendingEvents: RealmList<TimelineEventEntity>?) {
// Makes sure to close the previous frozen realm
if (frozenSendingTimelineEvents?.isValid == true) {
frozenSendingTimelineEvents?.realm?.close()
}
frozenSendingTimelineEvents = sendingEvents?.freeze()
} }
override fun buildSendingEvents(): List<TimelineEvent> { override fun buildSendingEvents(): List<TimelineEvent> {
val builtSendingEvents = mutableListOf<TimelineEvent>() val builtSendingEvents = mutableListOf<TimelineEvent>()
uiEchoManager.getInMemorySendingEvents() uiEchoManager.getInMemorySendingEvents()
.addWithUiEcho(builtSendingEvents) .addWithUiEcho(builtSendingEvents)
if (frozenSendingTimelineEvents?.isValid == true) {
frozenSendingTimelineEvents sendingTimelineEvents
?.filter { timelineEvent -> ?.filter { timelineEvent ->
builtSendingEvents.none { it.eventId == timelineEvent.eventId } builtSendingEvents.none { it.eventId == timelineEvent.eventId }
} }
?.map { ?.map {
timelineEventMapper.map(it) timelineEventMapper.map(it)
}?.addWithUiEcho(builtSendingEvents) }?.addWithUiEcho(builtSendingEvents)
}
return builtSendingEvents return builtSendingEvents
} }

View File

@ -16,16 +16,23 @@
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.OrderedCollectionChangeSet import io.realm.kotlin.TypedRealm
import io.realm.OrderedRealmCollectionChangeListener import io.realm.kotlin.ext.asFlow
import io.realm.RealmConfiguration import io.realm.kotlin.notifications.DeletedObject
import io.realm.RealmObjectChangeListener import io.realm.kotlin.notifications.InitialObject
import io.realm.RealmQuery import io.realm.kotlin.notifications.InitialResults
import io.realm.RealmResults import io.realm.kotlin.notifications.ListChangeSet
import io.realm.Sort import io.realm.kotlin.notifications.ObjectChange
import io.realm.kotlin.addChangeListener import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.removeChangeListener import io.realm.kotlin.notifications.UpdatedObject
import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
@ -39,6 +46,7 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.whereChunkId
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
@ -52,6 +60,7 @@ import java.util.concurrent.atomic.AtomicBoolean
* It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
*/ */
internal class TimelineChunk( internal class TimelineChunk(
private val timelineScope: CoroutineScope,
private val chunkEntity: ChunkEntity, private val chunkEntity: ChunkEntity,
private val timelineSettings: TimelineSettings, private val timelineSettings: TimelineSettings,
private val roomId: String, private val roomId: String,
@ -59,7 +68,7 @@ internal class TimelineChunk(
private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val fetchThreadTimelineTask: FetchThreadTimelineTask,
private val eventDecryptor: TimelineEventDecryptor, private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val realmConfiguration: RealmConfiguration, private val realm: TypedRealm,
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val uiEchoManager: UIEchoManager?, private val uiEchoManager: UIEchoManager?,
@ -76,39 +85,8 @@ internal class TimelineChunk(
private var prevChunkLatch: CompletableDeferred<Unit>? = null private var prevChunkLatch: CompletableDeferred<Unit>? = null
private var nextChunkLatch: CompletableDeferred<Unit>? = null private var nextChunkLatch: CompletableDeferred<Unit>? = null
private val chunkObjectListener = RealmObjectChangeListener<ChunkEntity> { _, changeSet -> private var timelineEventEntities: RealmResults<TimelineEventEntity> =
if (changeSet == null) return@RealmObjectChangeListener chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId).find()
if (changeSet.isDeleted.orFalse()) {
return@RealmObjectChangeListener
}
Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet.changedFields?.joinToString(",")}")
if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) {
isLastForward.set(chunkEntity.isLastForward)
}
if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_BACKWARD)) {
isLastBackward.set(chunkEntity.isLastBackward)
}
if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) {
nextChunk = createTimelineChunk(chunkEntity.nextChunk).also {
it?.prevChunk = this
}
nextChunkLatch?.complete(Unit)
}
if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) {
prevChunk = createTimelineChunk(chunkEntity.prevChunk).also {
it?.nextChunk = this
}
prevChunkLatch?.complete(Unit)
}
}
private val timelineEventsChangeListener =
OrderedRealmCollectionChangeListener { results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet ->
Timber.v("on timeline events chunk update")
handleDatabaseChangeSet(results, changeSet)
}
private var timelineEventEntities: RealmResults<TimelineEventEntity> = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId)
private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList()) private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList())
private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>()) private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>())
@ -116,8 +94,13 @@ internal class TimelineChunk(
private var prevChunk: TimelineChunk? = null private var prevChunk: TimelineChunk? = null
init { init {
timelineEventEntities.addChangeListener(timelineEventsChangeListener) timelineEventEntities.asFlow()
chunkEntity.addChangeListener(chunkObjectListener) .onEach(::handleDatabaseChangeSet)
.launchIn(timelineScope)
chunkEntity.asFlow()
.onEach(::handleChunkObjectChange)
.launchIn(timelineScope)
} }
fun hasReachedLastForward(): Boolean { fun hasReachedLastForward(): Boolean {
@ -326,8 +309,6 @@ internal class TimelineChunk(
nextChunkLatch?.cancel() nextChunkLatch?.cancel()
prevChunk = null prevChunk = null
prevChunkLatch?.cancel() prevChunkLatch?.cancel()
chunkEntity.removeChangeListener(chunkObjectListener)
timelineEventEntities.removeChangeListener(timelineEventsChangeListener)
} }
/** /**
@ -337,12 +318,11 @@ internal class TimelineChunk(
*/ */
private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage { private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage() val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
val baseQuery = timelineEventEntities.where() val baseQuery = chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId)
val timelineEvents = baseQuery val timelineEvents = baseQuery
.offsets(direction, count, displayIndex) .offsets(direction, count, displayIndex)
.findAll() .find()
.orEmpty()
if (timelineEvents.isEmpty()) return LoadedFromStorage() if (timelineEvents.isEmpty()) return LoadedFromStorage()
// Disabled due to the new fallback // Disabled due to the new fallback
@ -434,7 +414,7 @@ internal class TimelineChunk(
val loadMoreResult = try { val loadMoreResult = try {
if (token == null) { if (token == null) {
if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END
val lastKnownEventId = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId).firstOrNull()?.eventId val lastKnownEventId = chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId).first().find()?.eventId
?: return LoadMoreResult.FAILURE ?: return LoadMoreResult.FAILURE
val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count) val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult() fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
@ -485,64 +465,104 @@ internal class TimelineChunk(
return offset return offset
} }
private fun handleChunkObjectChange(chunkChanged: ObjectChange<ChunkEntity>) {
fun onChunkUpdated(updatedObject: UpdatedObject<ChunkEntity>) {
Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${updatedObject.changedFields.joinToString(",")}")
if (updatedObject.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) {
isLastForward.set(chunkEntity.isLastForward)
}
if (updatedObject.isFieldChanged(ChunkEntityFields.IS_LAST_BACKWARD)) {
isLastBackward.set(chunkEntity.isLastBackward)
}
if (updatedObject.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) {
nextChunk = createTimelineChunk(chunkEntity.nextChunk).also {
it?.prevChunk = this
}
nextChunkLatch?.complete(Unit)
}
if (updatedObject.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) {
prevChunk = createTimelineChunk(chunkEntity.prevChunk).also {
it?.nextChunk = this
}
prevChunkLatch?.complete(Unit)
}
}
when (chunkChanged) {
is InitialObject,
is DeletedObject -> return
is UpdatedObject -> onChunkUpdated(chunkChanged)
}
}
/** /**
* This method is responsible for managing insertions and updates of events on this chunk. * This method is responsible for managing insertions and updates of events on this chunk.
* *
*/ */
private fun handleDatabaseChangeSet(results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) { private fun handleDatabaseChangeSet(resultChanges: ResultsChange<TimelineEventEntity>) {
val insertions = changeSet.insertionRanges
for (range in insertions) {
if (!validateInsertion(range, results)) continue
val newItems = results
.subList(range.startIndex, range.startIndex + range.length)
.map { it.buildAndDecryptIfNeeded() }
builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) } fun validateInsertion(range: ListChangeSet.Range, results: RealmResults<TimelineEventEntity>): Boolean {
newItems.mapIndexed { index, timelineEvent -> // Insertion can only happen from LastForward chunk after a sync.
if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) { if (isLastForward.get()) {
isLastBackward.set(true) val firstBuiltEvent = builtEvents.firstOrNull()
if (firstBuiltEvent != null) {
val lastInsertion = results[range.startIndex + range.length - 1] ?: return false
if (firstBuiltEvent.displayIndex + 1 != lastInsertion.displayIndex) {
Timber.v("There is no continuation in the chunk, chunk is not fully loaded yet, skip insert.")
return false
}
} }
val correctedIndex = range.startIndex + index
builtEvents.add(correctedIndex, timelineEvent)
builtEventsIndexes[timelineEvent.eventId] = correctedIndex
} }
return true
} }
val modifications = changeSet.changeRanges
for (range in modifications) { fun handleUpdatedResults(updatedResults: UpdatedResults<TimelineEventEntity>) {
for (modificationIndex in (range.startIndex until range.startIndex + range.length)) { val results = updatedResults.list
val updatedEntity = results[modificationIndex] ?: continue val insertions = updatedResults.insertionRanges
val builtEventIndex = builtEventsIndexes[updatedEntity.eventId] ?: continue for (range in insertions) {
try { if (!validateInsertion(range, results)) continue
builtEvents[builtEventIndex] = updatedEntity.buildAndDecryptIfNeeded() val newItems = results
} catch (failure: Throwable) { .subList(range.startIndex, range.startIndex + range.length)
Timber.v("Fail to update items at index: $modificationIndex") .map { it.buildAndDecryptIfNeeded() }
builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) }
newItems.mapIndexed { index, timelineEvent ->
if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) {
isLastBackward.set(true)
}
val correctedIndex = range.startIndex + index
builtEvents.add(correctedIndex, timelineEvent)
builtEventsIndexes[timelineEvent.eventId] = correctedIndex
} }
} }
val modifications = updatedResults.changeRanges
for (range in modifications) {
for (modificationIndex in (range.startIndex until range.startIndex + range.length)) {
val updatedEntity = results[modificationIndex] ?: continue
val builtEventIndex = builtEventsIndexes[updatedEntity.eventId] ?: continue
try {
builtEvents[builtEventIndex] = updatedEntity.buildAndDecryptIfNeeded()
} catch (failure: Throwable) {
Timber.v("Fail to update items at index: $modificationIndex")
}
}
}
if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
onBuiltEvents(true)
}
val deletions = updatedResults.deletions
if (deletions.isNotEmpty()) {
onEventsDeleted()
}
} }
if (insertions.isNotEmpty() || modifications.isNotEmpty()) { when (resultChanges) {
onBuiltEvents(true) is InitialResults -> Unit
is UpdatedResults -> handleUpdatedResults(resultChanges)
} }
val deletions = changeSet.deletions
if (deletions.isNotEmpty()) {
onEventsDeleted()
}
}
private fun validateInsertion(range: OrderedCollectionChangeSet.Range, results: RealmResults<TimelineEventEntity>): Boolean {
// Insertion can only happen from LastForward chunk after a sync.
if (isLastForward.get()) {
val firstBuiltEvent = builtEvents.firstOrNull()
if (firstBuiltEvent != null) {
val lastInsertion = results[range.startIndex + range.length - 1] ?: return false
if (firstBuiltEvent.displayIndex + 1 != lastInsertion.displayIndex) {
Timber.v("There is no continuation in the chunk, chunk is not fully loaded yet, skip insert.")
return false
}
}
}
return true
} }
private fun getNextDisplayIndex(direction: Timeline.Direction): Int? { private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
@ -551,11 +571,15 @@ internal class TimelineChunk(
} }
return if (builtEvents.isEmpty()) { return if (builtEvents.isEmpty()) {
if (initialEventId != null) { if (initialEventId != null) {
timelineEventEntities.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId)
.query("eventId == $0", initialEventId)
.first()
.find()
?.displayIndex
} else if (direction == Timeline.Direction.BACKWARDS) { } else if (direction == Timeline.Direction.BACKWARDS) {
timelineEventEntities.first(null)?.displayIndex timelineEventEntities.firstOrNull()?.displayIndex
} else { } else {
timelineEventEntities.last(null)?.displayIndex timelineEventEntities.lastOrNull()?.displayIndex
} }
} else if (direction == Timeline.Direction.FORWARDS) { } else if (direction == Timeline.Direction.FORWARDS) {
builtEvents.first().displayIndex + 1 builtEvents.first().displayIndex + 1
@ -567,13 +591,14 @@ internal class TimelineChunk(
private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? { private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? {
if (chunkEntity == null) return null if (chunkEntity == null) return null
return TimelineChunk( return TimelineChunk(
timelineScope = timelineScope,
realm = realm,
chunkEntity = chunkEntity, chunkEntity = chunkEntity,
timelineSettings = timelineSettings, timelineSettings = timelineSettings,
roomId = roomId, roomId = roomId,
timelineId = timelineId, timelineId = timelineId,
eventDecryptor = eventDecryptor, eventDecryptor = eventDecryptor,
paginationTask = paginationTask, paginationTask = paginationTask,
realmConfiguration = realmConfiguration,
fetchThreadTimelineTask = fetchThreadTimelineTask, fetchThreadTimelineTask = fetchThreadTimelineTask,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
timelineEventMapper = timelineEventMapper, timelineEventMapper = timelineEventMapper,
@ -598,16 +623,14 @@ private fun RealmQuery<TimelineEventEntity>.offsets(
startDisplayIndex: Int startDisplayIndex: Int
): RealmQuery<TimelineEventEntity> { ): RealmQuery<TimelineEventEntity> {
return if (direction == Timeline.Direction.BACKWARDS) { return if (direction == Timeline.Direction.BACKWARDS) {
lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) query("displayIndex <= $0", startDisplayIndex)
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
limit(count.toLong()) .limit(count)
} else { } else {
greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) query("displayIndex >= $0", startDisplayIndex)
// We need to sort ascending first so limit works in the right direction .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) .limit(count)
limit(count.toLong()) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
// Result is expected to be sorted descending
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
} }
} }
@ -615,18 +638,11 @@ private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
} }
private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults<TimelineEventEntity> { private fun ChunkEntity.querySortedTimelineEvents(realm: TypedRealm, rootThreadEventId: String?): RealmQuery<TimelineEventEntity> {
return if (rootThreadEventId == null) { return if (rootThreadEventId == null) {
timelineEvents TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
} else { } else {
timelineEvents TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
.where() .query("root.rootThreadEventId == $0 OR root.eventId == $0", rootThreadEventId)
.beginGroup() }.sort("displayIndex", Sort.DESCENDING)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.or()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId)
.endGroup()
.findAll()
}
} }

View File

@ -15,8 +15,6 @@
*/ */
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@ -24,6 +22,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
@ -35,8 +34,7 @@ import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
internal class TimelineEventDecryptor @Inject constructor( internal class TimelineEventDecryptor @Inject constructor(
@SessionDatabase @SessionDatabase private val realmInstance: RealmInstance,
private val realmConfiguration: RealmConfiguration,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val threadsAwarenessHandler: ThreadsAwarenessHandler, private val threadsAwarenessHandler: ThreadsAwarenessHandler,
) { ) {
@ -97,58 +95,59 @@ internal class TimelineEventDecryptor @Inject constructor(
} }
} }
executor?.execute { executor?.execute {
Realm.getInstance(realmConfiguration).use { realm -> try {
try { processDecryptRequest(request)
processDecryptRequest(request, realm) } catch (e: InterruptedException) {
} catch (e: InterruptedException) { Timber.i("Decryption got interrupted")
Timber.i("Decryption got interrupted")
}
} }
} }
} }
private fun threadAwareNonEncryptedEvents(request: DecryptionRequest, realm: Realm) { private fun threadAwareNonEncryptedEvents(request: DecryptionRequest) {
val event = request.event val event = request.event
realm.executeTransaction { realmInstance.blockingWrite {
val eventId = event.eventId ?: return@executeTransaction val eventId = event.eventId ?: return@blockingWrite
val eventEntity = EventEntity val eventEntity = EventEntity
.where(it, eventId = eventId) .where(this, eventId = eventId)
.findFirst() .first()
.find()
val decryptedEvent = eventEntity?.asDomain() val decryptedEvent = eventEntity?.asDomain()
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity) threadsAwarenessHandler.makeEventThreadAware(this, event.roomId, decryptedEvent, eventEntity)
} }
} }
private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) { private fun processDecryptRequest(request: DecryptionRequest) {
val event = request.event val event = request.event
val timelineId = request.timelineId val timelineId = request.timelineId
if (!request.event.isEncrypted()) { if (!request.event.isEncrypted()) {
// Here we have requested a decryption to an event that is not encrypted // Here we have requested a decryption to an event that is not encrypted
// We will simply make this event thread aware // We will simply make this event thread aware
threadAwareNonEncryptedEvents(request, realm) threadAwareNonEncryptedEvents(request)
return return
} }
try { try {
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) } val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
Timber.v("Successfully decrypted event ${event.eventId}") Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction { realmInstance.blockingWrite {
val eventId = event.eventId ?: return@executeTransaction val eventId = event.eventId ?: return@blockingWrite
val eventEntity = EventEntity val eventEntity = EventEntity
.where(it, eventId = eventId) .where(this, eventId = eventId)
.findFirst() .first()
.find()
eventEntity?.setDecryptionResult(result) eventEntity?.setDecryptionResult(result)
val decryptedEvent = eventEntity?.asDomain() val decryptedEvent = eventEntity?.asDomain()
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity) threadsAwarenessHandler.makeEventThreadAware(this, event.roomId, decryptedEvent, eventEntity)
} }
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}") Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) { if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) {
// Keep track of unknown sessions to automatically try to decrypt on new session // Keep track of unknown sessions to automatically try to decrypt on new session
realm.executeTransaction { realmInstance.blockingWrite {
EventEntity.where(it, eventId = event.eventId ?: "") EventEntity.where(this, eventId = event.eventId ?: "")
.findFirst() .first()
.find()
?.let { ?.let {
it.decryptionErrorCode = e.errorType.name it.decryptionErrorCode = e.errorType.name
it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription

View File

@ -16,15 +16,15 @@
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy import dagger.Lazy
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.isValid import io.realm.kotlin.ext.isValid
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addIfNecessary
import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addStateEvent
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
@ -41,9 +41,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.extensions.realm
import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.StreamEventsManager
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -52,7 +50,7 @@ import javax.inject.Inject
* Insert Chunk in DB, and eventually link next and previous chunk in db. * Insert Chunk in DB, and eventually link next and previous chunk in db.
*/ */
internal class TokenChunkEventPersistor @Inject constructor( internal class TokenChunkEventPersistor @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val realmInstance: RealmInstance,
@UserId private val userId: String, @UserId private val userId: String,
private val lightweightSettingsStorage: LightweightSettingsStorage, private val lightweightSettingsStorage: LightweightSettingsStorage,
private val liveEventManager: Lazy<StreamEventsManager>, private val liveEventManager: Lazy<StreamEventsManager>,
@ -70,8 +68,8 @@ internal class TokenChunkEventPersistor @Inject constructor(
roomId: String, roomId: String,
direction: PaginationDirection direction: PaginationDirection
): Result { ): Result {
monarchy realmInstance
.awaitTransaction { realm -> .write {
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
val nextToken: String? val nextToken: String?
@ -83,16 +81,16 @@ internal class TokenChunkEventPersistor @Inject constructor(
nextToken = receivedChunk.start nextToken = receivedChunk.start
prevToken = receivedChunk.end prevToken = receivedChunk.end
} }
val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) val existingChunk = ChunkEntity.find(this, roomId, prevToken = prevToken, nextToken = nextToken)
if (existingChunk != null) { if (existingChunk != null) {
Timber.v("This chunk is already in the db, return.") Timber.v("This chunk is already in the db, return.")
return@awaitTransaction return@write
} }
// Creates links in both directions // Creates links in both directions
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val prevChunk = ChunkEntity.find(this, roomId, nextToken = prevToken)
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken) val nextChunk = ChunkEntity.find(this, roomId, prevToken = nextToken)
val currentChunk = ChunkEntity.create(realm, prevToken = prevToken, nextToken = nextToken).apply { val currentChunk = ChunkEntity.create(this, prevToken = prevToken, nextToken = nextToken).apply {
this.nextChunk = nextChunk this.nextChunk = nextChunk
this.prevChunk = prevChunk this.prevChunk = prevChunk
} }
@ -100,9 +98,9 @@ internal class TokenChunkEventPersistor @Inject constructor(
prevChunk?.nextChunk = currentChunk prevChunk?.nextChunk = currentChunk
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
handleReachEnd(roomId, direction, currentChunk) handleReachEnd(this, roomId, direction, currentChunk)
} else { } else {
handlePagination(realm, roomId, direction, receivedChunk, currentChunk) handlePagination(this, roomId, direction, receivedChunk, currentChunk)
} }
} }
@ -117,12 +115,11 @@ internal class TokenChunkEventPersistor @Inject constructor(
} }
} }
private fun handleReachEnd(roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { private fun handleReachEnd(realm: MutableRealm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
Timber.v("Reach end of $roomId in $direction") Timber.v("Reach end of $roomId in $direction")
if (direction == PaginationDirection.FORWARDS) { if (direction == PaginationDirection.FORWARDS) {
// We should keep the lastForward chunk unique, the one from sync, so make an unidirectional link. // We should keep the lastForward chunk unique, the one from sync, so make an unidirectional link.
// This will allow us to get live events from sync even from a permalink but won't make the link in the opposite. // This will allow us to get live events from sync even from a permalink but won't make the link in the opposite.
val realm = currentChunk.realm
currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
} else { } else {
currentChunk.isLastBackward = true currentChunk.isLastBackward = true
@ -130,7 +127,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
} }
private fun handlePagination( private fun handlePagination(
realm: Realm, realm: MutableRealm,
roomId: String, roomId: String,
direction: PaginationDirection, direction: PaginationDirection,
receivedChunk: TokenChunkEvent, receivedChunk: TokenChunkEvent,
@ -169,6 +166,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
} }
liveEventManager.get().dispatchPaginatedEventReceived(event, roomId) liveEventManager.get().dispatchPaginatedEventReceived(event, roomId)
currentChunk.addTimelineEvent( currentChunk.addTimelineEvent(
realm = realm,
roomId = roomId, roomId = roomId,
eventEntity = eventEntity, eventEntity = eventEntity,
direction = direction, direction = direction,
@ -186,7 +184,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
} }
} }
if (currentChunk.isValid()) { if (currentChunk.isValid()) {
RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) RoomEntity.where(realm, roomId).first().find()?.addIfNecessary(currentChunk)
} }
if (lightweightSettingsStorage.areThreadMessagesEnabled()) { if (lightweightSettingsStorage.areThreadMessagesEnabled()) {

View File

@ -16,7 +16,8 @@
package org.matrix.android.sdk.internal.session.room.tombstone package org.matrix.android.sdk.internal.session.room.tombstone
import io.realm.Realm import io.realm.kotlin.MutableRealm
import io.realm.kotlin.UpdatePolicy
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -30,17 +31,18 @@ import javax.inject.Inject
internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor { internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor {
override suspend fun process(realm: Realm, event: Event) { override fun process(realm: MutableRealm, event: Event) {
if (event.roomId == null) return if (event.roomId == null) return
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>() val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
if (createRoomContent?.replacementRoomId == null) return if (createRoomContent?.replacementRoomId == null) return
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst() val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).first().find()
?: RoomSummaryEntity(event.roomId) ?: RoomSummaryEntity()
predecessorRoomSummary.roomId = event.roomId
if (predecessorRoomSummary.versioningState == VersioningState.NONE) { if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED
} }
realm.insertOrUpdate(predecessorRoomSummary) realm.copyToRealm(predecessorRoomSummary, updatePolicy = UpdatePolicy.ALL)
} }
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {

View File

@ -16,12 +16,12 @@
package org.matrix.android.sdk.internal.session.room.version package org.matrix.android.sdk.internal.session.room.version
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
@ -41,8 +41,7 @@ internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params,
internal class DefaultRoomVersionUpgradeTask @Inject constructor( internal class DefaultRoomVersionUpgradeTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver, private val globalErrorReceiver: GlobalErrorReceiver,
@SessionDatabase @SessionDatabase private val realmInstance: RealmInstance,
private val realmConfiguration: RealmConfiguration
) : RoomVersionUpgradeTask { ) : RoomVersionUpgradeTask {
override suspend fun execute(params: RoomVersionUpgradeTask.Params): String { override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
@ -55,10 +54,8 @@ internal class DefaultRoomVersionUpgradeTask @Inject constructor(
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
tryOrNull { tryOrNull {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
realm.where(RoomSummaryEntity::class.java) RoomSummaryEntity.where(realm, replacementRoomId, listOf(Membership.JOIN))
.equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
} }
} }
return replacementRoomId return replacementRoomId

View File

@ -16,14 +16,14 @@
package org.matrix.android.sdk.internal.session.space package org.matrix.android.sdk.internal.session.space
import io.realm.RealmConfiguration
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.JoinSpaceResult
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
@ -41,8 +41,7 @@ internal interface JoinSpaceTask : Task<JoinSpaceTask.Params, JoinSpaceResult> {
internal class DefaultJoinSpaceTask @Inject constructor( internal class DefaultJoinSpaceTask @Inject constructor(
private val joinRoomTask: JoinRoomTask, private val joinRoomTask: JoinRoomTask,
@SessionDatabase @SessionDatabase private val realmInstance: RealmInstance,
private val realmConfiguration: RealmConfiguration,
private val roomSummaryDataSource: RoomSummaryDataSource private val roomSummaryDataSource: RoomSummaryDataSource
) : JoinSpaceTask { ) : JoinSpaceTask {
@ -64,16 +63,16 @@ internal class DefaultJoinSpaceTask @Inject constructor(
Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...") Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
try { try {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm -> awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(2L)) { realm ->
realm.where(RoomSummaryEntity::class.java) realm.query(RoomSummaryEntity::class)
.apply { .let {
if (params.roomIdOrAlias.startsWith("!")) { if (params.roomIdOrAlias.startsWith("!")) {
equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias) it.query("roomId == $0", params.roomIdOrAlias)
} else { } else {
equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias) it.query("canonicalAlias == $0", params.roomIdOrAlias)
} }
} }
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) .process("membershipStr", listOf(Membership.JOIN))
} }
} catch (exception: TimeoutCancellationException) { } catch (exception: TimeoutCancellationException) {
Timber.w("## Space: > Error created with timeout") Timber.w("## Space: > Error created with timeout")

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.session.sync package org.matrix.android.sdk.internal.session.sync
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.pushrules.RuleScope
import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.InitialSyncStep
@ -35,7 +34,6 @@ import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
import org.matrix.android.sdk.internal.util.awaitTransaction
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis

View File

@ -195,16 +195,14 @@ internal class RoomSyncHandler @Inject constructor(
aggregator aggregator
) )
} }
realm.insertOrUpdate(roomEntities)
reporter?.reportProgress(index + 1F) reporter?.reportProgress(index + 1F)
} }
} }
} else { } else {
// No need to split // No need to split
val rooms = handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) { handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) {
handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator) handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator)
} }
realm.insertOrUpdate(rooms)
} }
} }

View File

@ -364,15 +364,10 @@ internal class ThreadsAwarenessHandler @Inject constructor(
return updateEventEntity(event, eventEntity, eventPayload, messageTextContent) return updateEventEntity(event, eventEntity, eventPayload, messageTextContent)
} }
private fun eventThatRelatesTo(realm: TypedRealm, currentEventId: String, rootThreadEventId: String): List<EventEntity>? { private fun eventThatRelatesTo(realm: TypedRealm, currentEventId: String, rootThreadEventId: String): List<EventEntity> {
val threadList = realm.where<EventEntity>() val threadList = realm.query(EventEntity::class)
.beginGroup() .query("rootThreadEventId == $0 OR eventId == $0", rootThreadEventId)
.equalTo(EventEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) .find()
.or()
.equalTo(EventEntityFields.EVENT_ID, rootThreadEventId)
.endGroup()
.and()
.findAll()
cacheEventRootId.add(rootThreadEventId) cacheEventRootId.add(rootThreadEventId)
return threadList.filter { return threadList.filter {
it.asDomain().getRelationContentForType(RelationType.THREAD)?.inReplyTo?.eventId == currentEventId it.asDomain().getRelationContentForType(RelationType.THREAD)?.inReplyTo?.eventId == currentEventId

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.user.accountdata package org.matrix.android.sdk.internal.session.user.accountdata
import io.realm.Realm import org.matrix.android.sdk.internal.database.RealmInstance
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.getDirectRooms import org.matrix.android.sdk.internal.database.query.getDirectRooms
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
@ -25,20 +24,17 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMess
import javax.inject.Inject import javax.inject.Inject
internal class DirectChatsHelper @Inject constructor( internal class DirectChatsHelper @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration @SessionDatabase private val realmInstance: RealmInstance,
) { ) {
/** /**
* @return a map of userId <-> list of roomId * @return a map of userId <-> list of roomId
*/ */
fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent { fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent {
return Realm.getInstance(realmConfiguration).use { realm -> val realm = realmInstance.getBlockingRealm()
// Makes sure we have the latest realm updates, this is important as we sent this information to the server. return RoomSummaryEntity.getDirectRooms(realm)
realm.refresh() .asSequence()
RoomSummaryEntity.getDirectRooms(realm) .filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
.asSequence() .groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
.filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
}
} }
} }

View File

@ -25,7 +25,8 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@ -35,7 +36,7 @@ import javax.inject.Inject
internal class WidgetFactory @Inject constructor( internal class WidgetFactory @Inject constructor(
private val userDataSource: UserDataSource, private val userDataSource: UserDataSource,
private val realmSessionProvider: RealmSessionProvider, @SessionDatabase private val realmInstance: RealmInstance,
private val displayNameResolver: DisplayNameResolver, private val displayNameResolver: DisplayNameResolver,
private val urlResolver: ContentUrlResolver, private val urlResolver: ContentUrlResolver,
@UserId private val userId: String @UserId private val userId: String
@ -49,16 +50,15 @@ internal class WidgetFactory @Inject constructor(
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) { val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
null null
} else { } else {
realmSessionProvider.withRealm { val realm = realmInstance.getBlockingRealm()
val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId) val roomMemberHelper = RoomMemberHelper(realm, widgetEvent.roomId)
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId) val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
SenderInfo( SenderInfo(
userId = widgetEvent.senderId, userId = widgetEvent.senderId,
displayName = roomMemberSummaryEntity?.displayName, displayName = roomMemberSummaryEntity?.displayName,
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName), isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
avatarUrl = roomMemberSummaryEntity?.avatarUrl avatarUrl = roomMemberSummaryEntity?.avatarUrl
) )
}
} }
val isAddedByMe = widgetEvent.senderId == userId val isAddedByMe = widgetEvent.senderId == userId
return Widget( return Widget(