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
import io.realm.RealmConfiguration
import io.realm.kotlin.RealmConfiguration
/**
* 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
import androidx.annotation.MainThread
import io.realm.RealmConfiguration
import io.realm.kotlin.RealmConfiguration
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.data.SessionParams

View File

@ -16,14 +16,12 @@
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.internal.database.RealmInstance
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.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.util.fetchCopied
import javax.inject.Inject
/**
@ -31,18 +29,17 @@ import javax.inject.Inject
* in the session DB, this class encapsulate this functionality.
*/
internal class CryptoSessionInfoProvider @Inject constructor(
@SessionDatabase private val monarchy: Monarchy
@SessionDatabase private val realmInstance: RealmInstance,
) {
fun isRoomEncrypted(roomId: String): Boolean {
// 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
val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.isEmpty(EventEntityFields.STATE_KEY)
.findFirst()
}
return encryptionEvent != null
val realm = realmInstance.getBlockingRealm()
return EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.query("stateKey == ''")
.first()
.find() != null
}
/**
@ -50,14 +47,11 @@ internal class CryptoSessionInfoProvider @Inject constructor(
* @param allActive if true return joined as well as invited, if false, only joined
*/
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
var userIds: List<String> = emptyList()
monarchy.doWithRealm { realm ->
userIds = if (allActive) {
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
} else {
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
}
val realm = realmInstance.getBlockingRealm()
return if (allActive) {
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
} else {
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 androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm
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.userEntityQueries
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.RoomMemberSummaryEntityFields
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.queryIn
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionDatabase
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
@SessionDatabase
@Inject lateinit var sessionRealmConfiguration: RealmConfiguration
@Inject lateinit var sessionRealmInstance: RealmInstance
@UserId
@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?) {
Timber.d("## CrossSigning - Updating shields for impacted rooms...")
awaitTransaction(sessionRealmConfiguration) { sessionRealm ->
sessionRealmInstance.write {
val cryptoRealm = cryptoRealmInstance.getBlockingRealm()
sessionRealm.where(RoomMemberSummaryEntity::class.java)
.`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray())
.distinct(RoomMemberSummaryEntityFields.ROOM_ID)
.findAll()
query(RoomMemberSummaryEntity::class)
.queryIn("userId", userList)
.distinct("roomId")
.find()
.map { it.roomId }
.also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") }
.forEach { roomId ->
RoomSummaryEntity.where(sessionRealm, roomId)
.equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true)
.findFirst()
RoomSummaryEntity.where(this, roomId)
.query("isEncrypted == true")
.first()
.find()
?.let { roomSummary ->
Timber.v("## CrossSigning - Check shield state for room $roomId")
val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds()
val allActiveRoomMembers = RoomMemberHelper(this, roomId).getActiveRoomMemberIds()
try {
val updatedTrust = computeRoomShield(
myCrossSigningInfo,

View File

@ -16,16 +16,16 @@
package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.launch
import io.realm.kotlin.query.RealmResults
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Mutex
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.model.EventEntity
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.di.SessionDatabase
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
@ -33,68 +33,54 @@ import timber.log.Timber
import javax.inject.Inject
internal class EventInsertLiveObserver @Inject constructor(
@SessionDatabase realmConfiguration: RealmConfiguration,
@SessionDatabase realmInstance: RealmInstance,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
) :
RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
RealmLiveEntityObserver<EventInsertEntity>(realmInstance, coroutineDispatchers.io) {
private val lock = Mutex()
override val query = Monarchy.Query {
it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
init {
realmInstance.getRealmFlow().flatMapConcat { realm ->
realm.query(EventInsertEntity::class, "canBeProcessed == true").asFlow()
}.onEach { resultChange ->
onChange(resultChange.list)
}.launchIn(observerScope)
}
override fun onChange(results: RealmResults<EventInsertEntity>) {
observerScope.launch {
lock.withLock {
if (!results.isLoaded || results.isEmpty()) {
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 suspend fun onChange(results: RealmResults<EventInsertEntity>) {
fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
return processors.any {
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
}
}
}
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
return processors.any {
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
lock.withLock {
if (results.isEmpty()) {
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.MutableRealm
import io.realm.kotlin.dynamic.DynamicMutableRealm
import io.realm.kotlin.dynamic.DynamicMutableRealmObject
import io.realm.kotlin.dynamic.DynamicRealmObject
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?) {
if (deleteable == null) return
delete(deleteable)

View File

@ -16,59 +16,24 @@
package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmChangeListener
import io.realm.RealmConfiguration
import io.realm.RealmModel
import io.realm.RealmResults
import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher
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.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 abstract class RealmLiveEntityObserver<T : RealmModel>(protected val realmConfiguration: RealmConfiguration) :
LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmInstance: RealmInstance, coroutineDispatcher: CoroutineDispatcher) :
LiveEntityObserver {
private companion object {
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)
}
}
}
protected val observerScope = CoroutineScope(SupervisorJob() + coroutineDispatcher)
override fun onSessionStopped(session: Session) {
if (isStarted.compareAndSet(true, false)) {
BACKGROUND_HANDLER.post {
results.getAndSet(null).removeAllChangeListeners()
backgroundRealm.getAndSet(null).also {
it.close()
}
observerScope.coroutineContext.cancelChildren()
}
}
observerScope.coroutineContext.cancelChildren()
}
override fun onClearCache(session: Session) {

View File

@ -16,53 +16,10 @@
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 kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
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(
realmInstance: RealmInstance,
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
import io.realm.DynamicRealm
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 io.realm.kotlin.migration.AutomaticSchemaMigration
import javax.inject.Inject
internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
internal class RealmSessionStoreMigration @Inject constructor() : MatrixAutomaticSchemaMigration(
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) {
if (oldVersion < 1) MigrateSessionTo001(realm).perform()
if (oldVersion < 2) MigrateSessionTo002(realm).perform()
if (oldVersion < 3) MigrateSessionTo003(realm).perform()
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()
override fun doMigrate(oldVersion: Long, migrationContext: AutomaticSchemaMigration.MigrationContext) {
if (oldVersion < 36L) {
// Don't bother with old migrations we force a clear cache here
migrationContext.newRealm.deleteAll()
}
}
}

View File

@ -16,21 +16,15 @@
package org.matrix.android.sdk.internal.database
import android.content.Context
import androidx.core.content.edit
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import io.realm.kotlin.RealmConfiguration
import org.matrix.android.sdk.internal.database.model.SESSION_REALM_SCHEMA
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.UserMd5
import org.matrix.android.sdk.internal.session.SessionModule
import timber.log.Timber
import java.io.File
import javax.inject.Inject
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
private const val REALM_NAME = "disk_store.realm"
/**
@ -44,61 +38,19 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File,
@SessionId val sessionId: 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 {
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
if (shouldClearRealm) {
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)
val realmConfiguration = RealmConfiguration.Builder(SESSION_REALM_SCHEMA)
.directory(directory.path)
.name(REALM_NAME)
.apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
}
.allowWritesOnUiThread(true)
.modules(SessionRealmModule())
.schemaVersion(realmSessionStoreMigration.schemaVersion)
.migration(realmSessionStoreMigration)
.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
}
// 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.database.mapper.asDomain
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.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.ReadReceiptsSummaryEntity
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.TimelineEventEntityFields
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.findLastForwardChunkOfRoom
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.whereChunkId
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import timber.log.Timber
@ -47,14 +45,11 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
} else {
val stateKey = stateEvent.stateKey ?: return
val type = stateEvent.type
val pastStateEvent = stateEvents.where()
.equalTo(EventEntityFields.ROOM_ID, roomId)
.equalTo(EventEntityFields.STATE_KEY, stateKey)
.equalTo(CurrentStateEventEntityFields.TYPE, type)
.findFirst()
if (pastStateEvent != null) {
stateEvents.remove(pastStateEvent)
val indexOfStateEvent = stateEvents.indexOfFirst {
it.roomId == roomId && it.stateKey == stateKey && it.type == type
}
if (indexOfStateEvent != -1) {
stateEvents.removeAt(indexOfStateEvent)
}
stateEvents.add(stateEvent)
}
@ -72,7 +67,7 @@ internal fun ChunkEntity.addTimelineEvent(
if (timelineEvents.find(eventId) != null) {
return null
}
val displayIndex = nextDisplayIndex(direction)
val displayIndex = nextDisplayIndex(realm, direction)
val localId = TimelineEventEntity.nextId(realm)
val senderId = eventEntity.sender ?: ""
@ -149,13 +144,17 @@ private fun handleReadReceipts(realm: MutableRealm, roomId: String, eventEntity:
return readReceiptsSummaryEntity
}
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
internal fun ChunkEntity.nextDisplayIndex(realm: TypedRealm, direction: PaginationDirection): Int {
return when (direction) {
PaginationDirection.FORWARDS -> {
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
(TimelineEventEntity.whereChunkId(realm, chunkId)
.max("displayIndex", Int::class)
.find() ?: 0) + 1
}
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
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.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.isRedacted
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.ReadReceiptEntity
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.findIncludingEvent
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereChunkId
import org.matrix.android.sdk.internal.database.query.whereRoomId
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
private typealias Summary = Pair<Int, TimelineEventEntity>?
@ -62,7 +61,7 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
val latestEventInThread = threadSummary.second
// If this is a thread message, find its root event if exists
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent(realm) else eventEntity
rootThreadEvent?.markEventAsRoot(
inThreadMessages = inThreadMessages,
@ -80,11 +79,12 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
* Finds the root event of the the current thread event message.
* Returns the EventEntity or null if the root event do not exist
*/
internal fun EventEntity.findRootThreadEvent(): EventEntity? =
rootThreadEventId?.let {
internal fun EventEntity.findRootThreadEvent(realm: TypedRealm): EventEntity? =
rootThreadEventId?.let { eventId ->
EventEntity
.where(realm, it)
.findFirst()
.where(realm, eventId)
.first()
.find()
}
/**
@ -122,15 +122,15 @@ internal fun EventEntity.threadSummaryInThread(realm: TypedRealm, rootThreadEven
// Iterate the chunk until we find our latest event
while (result == null) {
result = findLatestSortedChunkEvent(chunk, rootThreadEventId)
result = findLatestSortedChunkEvent(realm, chunk, rootThreadEventId)
chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break
}
if (result == null && chunkEntity != null) {
// Find latest event from our current chunk
result = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId)
result = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
} else if (result != null && chunkEntity != null) {
val currentChunkLatestEvent = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId)
val currentChunkLatestEvent = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
result = findMostRecentEvent(result, currentChunkLatestEvent)
}
@ -185,37 +185,41 @@ private fun findMostRecentEvent(result: TimelineEventEntity, currentChunkLatestE
/**
* Find the latest event of the current chunk.
*/
private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull {
it.root?.rootThreadEventId == rootThreadEventId
}
private fun findLatestSortedChunkEvent(realm: TypedRealm, chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
TimelineEventEntity.whereChunkId(realm, chunk.chunkId)
.sort("displayIndex", Sort.DESCENDING)
.find()
.firstOrNull {
it.root?.rootThreadEventId == rootThreadEventId
}
/**
* Find all TimelineEventEntity that are root threads for the specified room.
* @param realm the realm instance
* @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
.whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false)
.sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING)
.query("root.isRootThread == true")
.query("ownedByThreadChunk == false")
.sort("root.originServerTs", Sort.DESCENDING)
/**
* 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 {
EventAnnotationsSummaryEntity
.where(realm, roomId, eventId = it.eventId)
.findFirst()
.first()
.find()
?.editSummary
?.editions
?.lastOrNull()
?.eventId
?.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(
lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
?: "(edited)"
@ -230,15 +234,11 @@ internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: Stri
* @param realm the realm instance
* @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
.whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
.beginGroup()
.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()
.query("root.isRootThread == true")
.process("root.threadNotificationStateStr", listOf(ThreadNotificationState.NEW_MESSAGE, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE))
/**
* 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 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
.whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.equalTo(TimelineEventEntityFields.ROOT.SENDER, senderId)
.findFirst()
?.let { true }
?: false
.query("root.rootThreadEventId == $0", rootThreadEventId)
.query("root.sender == $0", senderId)
.first()
.find() != null
/**
* 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 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
.whereRoomId(realm, roomId = roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.equalTo(TimelineEventEntityFields.ROOT.SENDER, userId)
.findAll()
.firstOrNull { isUserMentioned(userId, it) }
?.let { true }
?: false
.query("root.rootThreadEventId == $0", rootThreadEventId)
.query("root.sender == $0", userId)
.find()
.firstOrNull { isUserMentioned(userId, it) } != null
/**
* 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)
.findFirst()
.first()
.find()
?.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
* 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 readReceiptChunk = ChunkEntity
.findIncludingEvent(realm, readReceipt) ?: return
val readReceiptChunkTimelineEvents = readReceiptChunk
.timelineEvents
.where()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll() ?: return
val readReceiptChunkTimelineEvents = TimelineEventEntity.whereChunkId(realm, chunkId = readReceiptChunk.chunkId)
.sort("displayIndex", Sort.ASCENDING)
.find() ?: return
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
@ -357,7 +352,7 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
rootThreadEventId = eventId,
senderId = currentUserId
)
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()
val rootThreadEventEntity = EventEntity.where(realm, eventId).first().find()
if (isUserParticipating) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE

View File

@ -16,7 +16,6 @@
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.presence.UserPresenceEntity
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.
*/
@RealmModule(
library = true,
classes = [
ChunkEntity::class,
EventEntity::class,
EventInsertEntity::class,
TimelineEventEntity::class,
FilterEntity::class,
ReadReceiptEntity::class,
RoomEntity::class,
RoomSummaryEntity::class,
RoomTagEntity::class,
SyncEntity::class,
PendingThreePidEntity::class,
UserEntity::class,
IgnoredUserEntity::class,
BreadcrumbsEntity::class,
UserThreePidEntity::class,
EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class,
EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::class,
PushRuleEntity::class,
PushConditionEntity::class,
PreviewUrlCacheEntity::class,
PusherEntity::class,
PusherDataEntity::class,
ReadReceiptsSummaryEntity::class,
ReadMarkerEntity::class,
UserDraftsEntity::class,
DraftEntity::class,
HomeServerCapabilitiesEntity::class,
RoomMemberSummaryEntity::class,
CurrentStateEventEntity::class,
UserAccountDataEntity::class,
ScalarTokenEntity::class,
WellknownIntegrationManagerConfigEntity::class,
RoomAccountDataEntity::class,
SpaceChildSummaryEntity::class,
SpaceParentSummaryEntity::class,
UserPresenceEntity::class,
ThreadSummaryEntity::class
]
internal val SESSION_REALM_SCHEMA = setOf(
ChunkEntity::class,
EventEntity::class,
EventInsertEntity::class,
TimelineEventEntity::class,
FilterEntity::class,
ReadReceiptEntity::class,
RoomEntity::class,
RoomSummaryEntity::class,
RoomTagEntity::class,
SyncEntity::class,
PendingThreePidEntity::class,
UserEntity::class,
IgnoredUserEntity::class,
BreadcrumbsEntity::class,
UserThreePidEntity::class,
EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class,
EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::class,
PushRuleEntity::class,
PushConditionEntity::class,
PreviewUrlCacheEntity::class,
PusherEntity::class,
PusherDataEntity::class,
ReadReceiptsSummaryEntity::class,
ReadMarkerEntity::class,
UserDraftsEntity::class,
DraftEntity::class,
HomeServerCapabilitiesEntity::class,
RoomMemberSummaryEntity::class,
CurrentStateEventEntity::class,
UserAccountDataEntity::class,
ScalarTokenEntity::class,
WellknownIntegrationManagerConfigEntity::class,
RoomAccountDataEntity::class,
SpaceChildSummaryEntity::class,
SpaceParentSummaryEntity::class,
UserPresenceEntity::class,
ThreadSummaryEntity::class
)
internal class SessionRealmModule

View File

@ -16,20 +16,19 @@
package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery
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> {
val query = realm.where<ReferencesAggregatedSummaryEntity>()
query.equalTo(ReferencesAggregatedSummaryEntityFields.EVENT_ID, eventId)
return query
internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: TypedRealm, eventId: String): RealmQuery<ReferencesAggregatedSummaryEntity> {
return realm.query(ReferencesAggregatedSummaryEntity::class)
.query("eventId == $0", eventId)
}
internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, txID: String): ReferencesAggregatedSummaryEntity {
return realm.createObject(ReferencesAggregatedSummaryEntity::class.java).apply {
internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: MutableRealm, txID: String): ReferencesAggregatedSummaryEntity {
val entity = ReferencesAggregatedSummaryEntity().apply {
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.query.RealmQuery
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.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
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> {
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? {
val roomSummary = realm.query(RoomSummaryEntity::class)
.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.query.RealmQuery
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.types.ObjectId
import io.realm.kotlin.types.RealmList
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
@ -60,6 +61,14 @@ internal fun TimelineEventEntity.Companion.whereRoomId(
.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(
realm: TypedRealm,
senderMembershipEventId: String

View File

@ -16,42 +16,40 @@
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.internal.database.RealmInstance
internal class RealmDebugTools(
private val realmConfiguration: RealmConfiguration
private val realmInstance: RealmInstance
) {
/**
* Get info about the DB.
*/
fun getInfo(baseName: String): String {
val realm = realmInstance.getBlockingRealm()
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) {
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")
}
Realm.getInstance(realmConfiguration).use { realm ->
// Check if we have data
separator()
separator()
append("\n$baseName Realm is empty: ${realm.isEmpty}")
var total = 0L
val maxNameLength = realmConfiguration.realmObjectClasses.maxOf { it.simpleName.length }
realmConfiguration.realmObjectClasses.forEach { modelClazz ->
val count = realm.where(modelClazz).count()
total += count
append("\n$baseName Realm - count ${modelClazz.simpleName.padEnd(maxNameLength)} : $count")
}
separator()
append("\n$baseName Realm - total count: $total")
separator()
separator()
// Check if we have data
separator()
separator()
var total = 0L
val maxNameLength = realmConfiguration.schema.maxOf { it.simpleName?.length ?: 0 }
realmConfiguration.schema.forEach { modelClazz ->
val count = realm.query(modelClazz).count().find()
total += count
append("\n$baseName Realm - count ${modelClazz.simpleName?.padEnd(maxNameLength)} : $count")
}
separator()
append("\n$baseName Realm - total count: $total")
separator()
separator()
}
}

View File

@ -16,26 +16,29 @@
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.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
internal class DefaultDebugService @Inject constructor(
// @AuthDatabase private val realmConfigurationAuth: RealmConfiguration,
// @GlobalDatabase private val realmConfigurationGlobal: RealmConfiguration,
@AuthDatabase private val authRealmInstance: RealmInstance,
@GlobalDatabase private val globalRealmInstance: RealmInstance,
private val sessionManager: SessionManager,
) : DebugService {
override fun getAllRealmConfigurations(): List<RealmConfiguration> {
return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty()
// realmConfigurationAuth +
// realmConfigurationGlobal
return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() +
authRealmInstance.realmConfiguration + globalRealmInstance.realmConfiguration
}
override fun getDbUsageInfo() = buildString {
//append(RealmDebugTools(realmConfigurationAuth).getInfo("Auth"))
//append(RealmDebugTools(realmConfigurationGlobal).getInfo("Global"))
append(RealmDebugTools(authRealmInstance).getInfo("Auth"))
append(RealmDebugTools(globalRealmInstance).getInfo("Global"))
append(sessionManager.getLastSession()?.getDbUsageInfo())
}
}

View File

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

View File

@ -16,7 +16,7 @@
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.internal.database.model.EventInsertType
@ -24,7 +24,7 @@ internal interface EventInsertLiveProcessor {
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.

View File

@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.session
import android.content.Context
import android.os.Build
import com.zhuinden.monarchy.Monarchy
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.realm.RealmConfiguration
import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import okhttp3.OkHttpClient
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.HomeServerConnectionConfig
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.RedactEventTask
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.di.Authenticated
import org.matrix.android.sdk.internal.di.CacheDirectory
@ -202,10 +202,16 @@ internal abstract class SessionModule {
@Provides
@SessionDatabase
@SessionScope
fun providesMonarchy(@SessionDatabase realmConfiguration: RealmConfiguration): Monarchy {
return Monarchy.Builder()
.setRealmConfiguration(realmConfiguration)
.build()
fun providesRealmInstance(
@SessionDatabase realmConfiguration: RealmConfiguration,
@SessionCoroutineScope coroutineScope: CoroutineScope,
matrixCoroutineDispatchers: MatrixCoroutineDispatchers
): RealmInstance {
return RealmInstance(
coroutineScope = coroutineScope,
realmConfiguration = realmConfiguration,
coroutineDispatcher = matrixCoroutineDispatchers.io
)
}
@JvmStatic
@ -367,10 +373,6 @@ internal abstract class SessionModule {
@IntoSet
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
@Binds
@IntoSet
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
@Binds
@IntoSet
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.Module
import dagger.Provides
import io.realm.RealmConfiguration
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
@Module
@ -31,8 +31,8 @@ internal abstract class CacheModule {
@JvmStatic
@Provides
@SessionDatabase
fun providesClearCacheTask(@SessionDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration)
fun providesClearCacheTask(@SessionDatabase realmInstance: RealmInstance): ClearCacheTask {
return RealmKotlinClearCacheTask(realmInstance)
}
}

View File

@ -16,24 +16,13 @@
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.awaitTransaction
import org.matrix.android.sdk.internal.database.deleteAll
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
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 {
override suspend fun execute(params: Unit) {

View File

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

View File

@ -16,9 +16,6 @@
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.auth.SessionParamsStore
import org.matrix.android.sdk.internal.crypto.CryptoModule
@ -47,7 +44,7 @@ internal class CleanupSession @Inject constructor(
@SessionFilesDirectory private val sessionFiles: File,
@SessionDownloadsDirectory private val sessionCache: File,
private val realmKeysUtils: RealmKeysUtils,
@SessionDatabase private val realmSessionConfiguration: RealmConfiguration,
@SessionDatabase private val sessionRealmInstance: RealmInstance,
@CryptoDatabase private val cryptoRealmInstance: RealmInstance,
@UserMd5 private val userMd5: String
) {
@ -61,8 +58,8 @@ internal class CleanupSession @Inject constructor(
}
suspend fun cleanup() {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
Timber.d("Realm instance ($sessionRealmCount)")
val sessionNumberOfActiveVersions = sessionRealmInstance.getRealm().getNumberOfActiveVersions()
Timber.d("Realm active sessions ($sessionNumberOfActiveVersions)")
Timber.d("Cleanup: release session...")
sessionManager.releaseSession(sessionId)
@ -80,33 +77,12 @@ internal class CleanupSession @Inject constructor(
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
// Wait for all the Realm instance to be released properly. Closing Realm instance is async.
// After that we can safely delete the Realm files
waitRealmRelease()
Timber.d("Closing databases")
sessionRealmInstance.close()
cryptoRealmInstance.close()
Timber.d("Cleanup: clear file system")
sessionFiles.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
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.session.crypto.verification.VerificationState
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.EventInsertType
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.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
@ -95,9 +95,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
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
val roomId = event.roomId
event.eventId ?: return
if (roomId == null) {
Timber.w("Event has no room id ${event.eventId}")
return
@ -114,10 +115,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId).first().find()
?.let {
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll()
?.forEach { tet -> tet.annotations = it }
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId).find()
.forEach { tet -> tet.annotations = it }
}
}
@ -213,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// }
}
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
when (eventToPrune.type) {
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 fun handleReplace(
realm: Realm,
realm: MutableRealm,
event: Event,
content: MessageContent,
roomId: String,
@ -285,7 +286,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val newContent = content.newContent ?: return
// 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) {
// We do not know yet about the edited event
} else if (editedEvent.sender != event.senderId) {
@ -302,16 +303,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (existingSummary == null) {
Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
// create the edit summary
eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
eventAnnotationsSummaryEntity.editSummary = realm.copyToRealm(EditAggregatedSummaryEntity())
.also { editSummary ->
editSummary.editions.add(
EditionOfEvent(
senderId = event.senderId ?: "",
eventId = event.eventId,
content = ContentMapper.map(newContent),
timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0,
isLocalEcho = isLocalEcho
)
EditionOfEvent().apply {
this.senderId = event.senderId ?: ""
this.eventId = event.eventId
this.content = ContentMapper.map(newContent)
this.timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0
this.isLocalEcho = isLocalEcho
}
)
}
} else {
@ -334,18 +335,18 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} else {
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
existingSummary.editions.add(
EditionOfEvent(
senderId = event.senderId ?: "",
eventId = event.eventId,
content = ContentMapper.map(newContent),
timestamp = if (isLocalEcho) {
clock.epochMillis()
} else {
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
event.originServerTs ?: clock.epochMillis()
},
isLocalEcho = isLocalEcho
)
EditionOfEvent().apply {
this.senderId = event.senderId ?: ""
this.eventId = event.eventId
this.content = ContentMapper.map(newContent)
this.timestamp = if (isLocalEcho) {
clock.epochMillis()
} else {
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
event.originServerTs ?: clock.epochMillis()
}
this.isLocalEcho = isLocalEcho
}
)
}
}
@ -357,9 +358,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (!isLocalEcho) {
val replaceEvent = TimelineEventEntity
.where(realm, roomId, eventId)
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false)
.findFirst()
handleThreadSummaryEdition(editedEvent, replaceEvent, existingSummary?.editions)
.query("ownedByThreadChunk == false")
.first()
.find()
handleThreadSummaryEdition(realm, editedEvent, replaceEvent, existingSummary?.editions)
}
}
@ -370,13 +372,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* @param editions list of edition of event
*/
private fun handleThreadSummaryEdition(
realm: TypedRealm,
editedEvent: EventEntity?,
replaceEvent: TimelineEventEntity?,
editions: List<EditionOfEvent>?
) {
replaceEvent ?: return
editedEvent ?: return
editedEvent.findRootThreadEvent()?.apply {
editedEvent.findRootThreadEvent(realm)?.apply {
val threadSummaryEventId = threadSummaryLatestMessage?.eventId
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
@ -393,7 +396,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
private fun handleInitialAggregatedRelations(
realm: Realm,
realm: MutableRealm,
event: Event,
roomId: String,
aggregation: AggregatedAnnotation
@ -402,10 +405,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
aggregation.chunk?.forEach {
if (it.type == EventType.REACTION) {
val eventId = event.eventId ?: ""
val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).first().find()
if (existing == null) {
val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
val sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
sum.key = it.key
sum.firstTimestamp = event.originServerTs
?: 0 // TODO how to maintain order?
@ -420,7 +423,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
private fun handleReaction(
realm: Realm,
realm: MutableRealm,
event: Event,
roomId: String,
isLocalEcho: Boolean
@ -434,7 +437,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
if (RelationType.ANNOTATION == content.relatesTo?.type) {
val reaction = content.relatesTo.key
val relatedEventID = content.relatesTo.eventId
val reactionEventId = event.eventId
val reactionEventId = event.eventId ?: return
Timber.v("Reaction $reactionEventId relates to $relatedEventID")
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventID)
@ -442,14 +445,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val txId = event.unsignedData?.transactionId
if (isLocalEcho && txId.isNullOrBlank()) {
Timber.w("Received a local echo with no transaction ID")
return
}
if (sum == null) {
sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
sum.key = reaction
sum.firstTimestamp = event.originServerTs ?: 0
if (isLocalEcho) {
Timber.v("Adding local echo reaction")
sum.sourceLocalEcho.add(txId)
sum.sourceLocalEcho.add(txId!!)
sum.count = 1
} else {
Timber.v("Adding synced reaction")
@ -471,7 +475,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
sum.count += 1
if (isLocalEcho) {
Timber.v("Adding local echo reaction")
sum.sourceLocalEcho.add(txId)
sum.sourceLocalEcho.add(txId!!)
} else {
Timber.v("Adding synced reaction")
sum.sourceEvents.add(reactionEventId)
@ -490,12 +494,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* Called when an event is deleted.
*/
private fun handleRedactionOfReplace(
realm: Realm,
realm: MutableRealm,
redacted: EventEntity,
relatedEventId: String
) {
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) {
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
return
@ -506,11 +510,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
return
}
// Need to remove this event from the edition list
sourceToDiscard.deleteFromRealm()
realm.delete(sourceToDiscard)
}
private fun handleReactionRedact(
realm: Realm,
realm: MutableRealm,
eventToPrune: EventEntity
) {
Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
@ -520,11 +524,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
val reactionKey = reactionContent.relatesTo.key
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) {
summary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey)
.findFirst()?.let { aggregation ->
summary.reactionsSummary
.firstOrNull {
it.key == reactionKey
}?.let { aggregation ->
Timber.v("Find summary for key with ${aggregation.sourceEvents.size} known reactions (count:${aggregation.count})")
Timber.v("Known reactions ${aggregation.sourceEvents.joinToString(",")}")
if (aggregation.sourceEvents.contains(eventToPrune.eventId)) {
@ -538,7 +543,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
if (aggregation.count == 0) {
// delete!
aggregation.deleteFromRealm()
realm.delete(aggregation)
}
} else {
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 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 {
liveLocationAggregationProcessor.handleBeaconLocationData(
realm,

View File

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

View File

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

View File

@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import androidx.work.ExistingWorkPolicy
import io.realm.Realm
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.session.events.model.Event
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.
* @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) {
return false
}
@ -130,7 +132,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
* @return true if it has been processed, false if ignored.
*/
fun handleBeaconLocationData(
realm: Realm,
realm: MutableRealm,
event: Event,
content: MessageBeaconLocationDataContent,
roomId: String,
@ -180,11 +182,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also {
it.add(eventId)
}
aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray())
aggregatedSummary.relatedEventIds = realmListOf(*updatedEventIds.toTypedArray())
}
private fun deactivateAllPreviousBeacons(
realm: Realm,
realm: MutableRealm,
roomId: String,
userId: String,
currentEventId: String,

View File

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

View File

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

View File

@ -16,7 +16,8 @@
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.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
@ -30,15 +31,16 @@ import javax.inject.Inject
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 predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
?: RoomSummaryEntity(predecessorRoomId)
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).first().find()
?: RoomSummaryEntity()
predecessorRoomSummary.roomId = predecessorRoomId
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
predecessorRoomSummary.isHiddenFromUser = true
realm.insertOrUpdate(predecessorRoomSummary)
realm.copyToRealm(predecessorRoomSummary, updatePolicy = UpdatePolicy.ALL)
}
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
import io.realm.Realm
import io.realm.kotlin.deleteFromRealm
import io.realm.kotlin.MutableRealm
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -41,12 +40,12 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
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())) {
return
}
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst()
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).first().find()
?: return
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
@ -54,11 +53,11 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
if (liveSummary != null) {
Timber.d("deleting live summary with id: ${liveSummary.eventId}")
liveSummary.deleteFromRealm()
realm.delete(liveSummary)
val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId)
if (annotationsSummary != null) {
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
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.RealmInstance
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.di.SessionDatabase
@ -36,7 +35,7 @@ internal interface RedactLiveLocationShareTask : Task<RedactLiveLocationShareTas
}
internal class DefaultRedactLiveLocationShareTask @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration,
@SessionDatabase private val realmInstance: RealmInstance,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor,
) : RedactLiveLocationShareTask {
@ -60,9 +59,9 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor(
}
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
return awaitTransaction(realmConfiguration) { realm ->
return realmInstance.write {
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm,
realm = this,
eventId = beaconInfoEventId
)
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()

View File

@ -16,22 +16,20 @@
package org.matrix.android.sdk.internal.session.room.membership.joining
import io.realm.RealmConfiguration
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.identity.model.SignInvitationResult
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.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.awaitTransaction
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.network.GlobalErrorReceiver
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.membership.RoomChangeMembershipStateDataSource
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(
private val roomAPI: RoomAPI,
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val coroutineDispatcher: MatrixCoroutineDispatchers,
@SessionDatabase private val realmInstance: RealmInstance,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
private val globalErrorReceiver: GlobalErrorReceiver,
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)
val roomId = joinRoomResponse.roomId
try {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
realm.where(RoomSummaryEntity::class.java)
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
RoomSummaryEntity.where(realm, roomId = roomId)
.process("membershipStr", listOf(Membership.JOIN))
}
} catch (exception: TimeoutCancellationException) {
throw JoinRoomFailure.JoinedWithTimeout
}
awaitTransaction(realmConfiguration) {
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
realmInstance.write {
RoomSummaryEntity.where(this, roomId).first().find()?.lastActivityTime = clock.epochMillis()
}
setReadMarkers(roomId)
}

View File

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

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.room.read
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.kotlin.TypedRealm
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.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.RoomFullyReadHandler
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 timber.log.Timber
import javax.inject.Inject
@ -65,9 +63,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) {
val realm = realmInstance.getRealm()
val markers = mutableMapOf<String, String>()
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) {
latestSyncedEventId
} else {
@ -79,7 +78,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
params.readReceiptEventId
}
if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy.realmConfiguration, params.roomId, fullyReadEventId)) {
if (fullyReadEventId != null && !isReadMarkerMoreRecent(realm, params.roomId, fullyReadEventId)) {
if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
Timber.w("Can't set read marker for local event $fullyReadEventId")
} else {
@ -87,7 +86,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
}
}
if (readReceiptEventId != null &&
!isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId)) {
!isEventRead(realm, userId, params.roomId, readReceiptEventId)) {
if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event $readReceiptEventId")
} else {
@ -116,25 +115,23 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
}
}
private fun latestSyncedEventId(roomId: String): String? =
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
}
private suspend fun latestSyncedEventId(realm: TypedRealm, roomId: String): String? =
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
monarchy.awaitTransaction { realm ->
realmInstance.write {
val readMarkerId = markers[READ_MARKER]
val readReceiptId = markers[READ_RECEIPT]
if (readMarkerId != null) {
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
roomFullyReadHandler.handle(this, roomId, FullyReadContent(readMarkerId))
}
if (readReceiptId != null) {
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
}
if (shouldUpdateRoomSummary) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@awaitTransaction
val roomSummary = RoomSummaryEntity.where(this, roomId).first().find()
?: return@write
roomSummary.notificationCount = 0
roomSummary.highlightCount = 0
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.internal.SessionManager
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.session.SessionComponent
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 cryptoService: CryptoService
@Inject lateinit var cancelSendTracker: CancelSendTracker
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
override fun injectWith(injector: SessionComponent) {
injector.inject(this)

View File

@ -17,14 +17,14 @@
package org.matrix.android.sdk.internal.session.room.threads.local
import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import androidx.lifecycle.asLiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
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.timeline.TimelineEvent
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.findAllThreadsForRoomId
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.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.util.awaitTransaction
internal class DefaultThreadsLocalService @AssistedInject constructor(
@Assisted private val roomId: String,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy,
@SessionDatabase private val realmInstance: RealmInstance,
private val timelineEventMapper: TimelineEventMapper,
) : ThreadsLocalService {
@ -50,56 +49,52 @@ internal class DefaultThreadsLocalService @AssistedInject constructor(
}
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
return monarchy.findAllMappedWithChanges(
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
{ timelineEventMapper.map(it) }
)
return realmInstance.queryList(timelineEventMapper::map) {
TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId)
}.asLiveData()
}
override fun getMarkedThreadNotifications(): List<TimelineEvent> {
return monarchy.fetchAllMappedSync(
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
{ timelineEventMapper.map(it) }
)
val realm = realmInstance.getBlockingRealm()
return TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(realm, roomId = roomId)
.find()
.map(timelineEventMapper::map)
}
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
return monarchy.findAllMappedWithChanges(
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
{ timelineEventMapper.map(it) }
)
return realmInstance.queryList(timelineEventMapper::map) {
TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId)
}.asLiveData()
}
override fun getAllThreads(): List<TimelineEvent> {
return monarchy.fetchAllMappedSync(
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
{ timelineEventMapper.map(it) }
)
val realm = realmInstance.getBlockingRealm()
return TimelineEventEntity.findAllThreadsForRoomId(realm, roomId = roomId)
.find()
.map(timelineEventMapper::map)
}
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use {
TimelineEventEntity.isUserParticipatingInThread(
realm = it,
roomId = roomId,
rootThreadEventId = rootThreadEventId,
senderId = userId
)
}
val realm = realmInstance.getBlockingRealm()
return TimelineEventEntity.isUserParticipatingInThread(
realm = realm,
roomId = roomId,
rootThreadEventId = rootThreadEventId,
senderId = userId
)
}
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
return Realm.getInstance(monarchy.realmConfiguration).use {
threads.mapEventsWithEdition(it, roomId)
}
val realm = realmInstance.getBlockingRealm()
return threads.mapEventsWithEdition(realm, roomId)
}
override suspend fun markThreadAsRead(rootThreadEventId: String) {
monarchy.awaitTransaction {
realmInstance.write {
EventEntity.where(
realm = it,
realm = this,
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.RealmConfiguration
import io.realm.kotlin.TypedRealm
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
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.TimelineSettings
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.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
@ -57,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference
internal class DefaultTimeline(
private val roomId: String,
private val initialEventId: String?,
private val realmConfiguration: RealmConfiguration,
private val realmInstance: RealmInstance,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val readReceiptHandler: ReadReceiptHandler,
private val settings: TimelineSettings,
@ -86,7 +88,6 @@ internal class DefaultTimeline(
private val forwardState = AtomicReference(Timeline.PaginationState())
private val backwardState = AtomicReference(Timeline.PaginationState())
private val backgroundRealm = AtomicReference<Realm>()
private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher()
private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher)
private val sequencer = SemaphoreCoroutineSequencer()
@ -96,11 +97,11 @@ internal class DefaultTimeline(
private var rootThreadEventId: String? = null
private val strategyDependencies = LoadTimelineStrategy.Dependencies(
timelineScope = timelineScope,
timelineSettings = settings,
realm = backgroundRealm,
realmInstance = realmInstance,
eventDecryptor = eventDecryptor,
paginationTask = paginationTask,
realmConfiguration = realmConfiguration,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
fetchThreadTimelineTask = fetchThreadTimelineTask,
getContextOfEventTask = getEventTask,
@ -151,9 +152,7 @@ internal class DefaultTimeline(
isFromThreadTimeline = rootThreadEventId != null
this@DefaultTimeline.rootThreadEventId = rootThreadEventId
// /
val realm = Realm.getInstance(realmConfiguration)
ensureReadReceiptAreLoaded(realm)
backgroundRealm.set(realm)
ensureReadReceiptAreLoaded()
listenToPostSnapshotSignals()
openAround(initialEventId, rootThreadEventId)
postSnapshot()
@ -168,7 +167,6 @@ internal class DefaultTimeline(
sequencer.post {
if (isStarted.compareAndSet(true, false)) {
strategy.onStop()
backgroundRealm.get().closeQuietly()
}
}
}
@ -408,14 +406,14 @@ internal class DefaultTimeline(
}
}
private fun ensureReadReceiptAreLoaded(realm: Realm) {
private fun ensureReadReceiptAreLoaded() {
readReceiptHandler.getContentFromInitSync(roomId)
?.also {
Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
}
?.let { readReceiptContent ->
realm.executeTransactionAsync {
readReceiptHandler.handle(it, roomId, readReceiptContent, false, null)
realmInstance.asyncWrite {
readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
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.settings.LightweightSettingsStorage
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.di.SessionDatabase
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(
@Assisted private val roomId: String,
@SessionDatabase private val monarchy: Monarchy,
@SessionDatabase private val realmInstance: RealmInstance,
private val timelineInput: TimelineInput,
private val contextOfEventTask: GetContextOfEventTask,
private val eventDecryptor: TimelineEventDecryptor,
@ -67,7 +68,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
roomId = roomId,
initialEventId = eventId,
settings = settings,
realmConfiguration = monarchy.realmConfiguration,
realmInstance = realmInstance,
coroutineDispatchers = coroutineDispatchers,
paginationTask = paginationTask,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,

View File

@ -16,15 +16,16 @@
package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmResults
import io.realm.kotlin.createObject
import io.realm.kotlin.executeTransactionAwait
import io.realm.kotlin.isValid
import io.realm.kotlin.TypedRealm
import io.realm.kotlin.ext.isValid
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.query.RealmResults
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.extensions.orFalse
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.TimelineSettings
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.mapper.TimelineEventMapper
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.deleteAndClearThreadEvents
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.util.time.Clock
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.
@ -89,11 +89,11 @@ internal class LoadTimelineStrategy constructor(
}
data class Dependencies(
val timelineScope: CoroutineScope,
val timelineSettings: TimelineSettings,
val realm: AtomicReference<Realm>,
val realmInstance: RealmInstance,
val eventDecryptor: TimelineEventDecryptor,
val paginationTask: PaginationTask,
val realmConfiguration: RealmConfiguration,
val fetchThreadTimelineTask: FetchThreadTimelineTask,
val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
val getContextOfEventTask: GetContextOfEventTask,
@ -112,20 +112,27 @@ internal class LoadTimelineStrategy constructor(
private var getContextLatch: CompletableDeferred<Unit>? = null
private var chunkEntity: RealmResults<ChunkEntity>? = 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
// or when there is a gap in the timeline.
val shouldRebuildChunk = changeSet.insertions.isNotEmpty()
if (shouldRebuildChunk) {
timelineChunk?.close(closeNext = true, closePrev = true)
timelineChunk = chunkEntity?.createTimelineChunk()
// If we are waiting for a result of get context, post completion
getContextLatch?.complete(Unit)
// If we have a gap, just tell the timeline about it.
if (timelineChunk?.hasReachedLastForward().orFalse()) {
dependencies.onLimitedTimeline()
private suspend fun onChunkResultsChanged(resultsChange: ResultsChange<ChunkEntity>) {
suspend fun onUpdates(updatedResults: UpdatedResults<ChunkEntity>) {
val shouldRebuildChunk = updatedResults.insertions.isNotEmpty()
if (shouldRebuildChunk) {
timelineChunk?.close(closeNext = true, closePrev = true)
timelineChunk = chunkEntity?.createTimelineChunk()
// If we are waiting for a result of get context, post completion
getContextLatch?.complete(Unit)
// If we have a gap, just tell the timeline about it.
if (timelineChunk?.hasReachedLastForward().orFalse()) {
dependencies.onLimitedTimeline()
}
}
}
when (resultsChange) {
is InitialResults -> Unit
is UpdatedResults -> onUpdates(resultsChange)
}
}
private val uiEchoManagerListener = object : UIEchoManager.Listener {
@ -165,7 +172,8 @@ internal class LoadTimelineStrategy constructor(
private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
roomId = roomId,
realm = dependencies.realm,
timelineScope = dependencies.timelineScope,
realmInstance = dependencies.realmInstance,
uiEchoManager = uiEchoManager,
timelineEventMapper = dependencies.timelineEventMapper,
onEventsUpdated = dependencies.onEventsUpdated
@ -180,10 +188,11 @@ internal class LoadTimelineStrategy constructor(
suspend fun onStart() {
dependencies.eventDecryptor.start()
dependencies.timelineInput.listeners.add(timelineInputListener)
val realm = dependencies.realm.get()
sendingEventsDataSource.start()
val realm = dependencies.realmInstance.getRealm()
chunkEntity = getChunkEntity(realm).also {
it.addChangeListener(chunkEntityListener)
it.asFlow().onEach {
}.launchIn(dependencies.timelineScope)
timelineChunk = it.createTimelineChunk()
}
@ -195,14 +204,12 @@ internal class LoadTimelineStrategy constructor(
suspend fun onStop() {
dependencies.eventDecryptor.destroy()
dependencies.timelineInput.listeners.remove(timelineInputListener)
chunkEntity?.removeChangeListener(chunkEntityListener)
sendingEventsDataSource.stop()
timelineChunk?.close(closeNext = true, closePrev = true)
getContextLatch?.cancel()
chunkEntity = null
timelineChunk = null
if (mode is Mode.Thread) {
clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId)
clearThreadChunkEntity(mode.rootThreadEventId)
}
if (dependencies.timelineSettings.useLiveSenderInfo) {
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) {
is Mode.Live -> {
ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
.findAll()
.query("isLastForward == true")
.find()
}
is Mode.Permalink -> {
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
}
is Mode.Thread -> {
recreateThreadChunkEntity(realm, mode.rootThreadEventId)
recreateThreadChunkEntity(mode.rootThreadEventId)
ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, mode.rootThreadEventId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
.findAll()
.query("rootThreadEventId == $0", mode.rootThreadEventId)
.query("isLastForwardThread == true")
.find()
}
}
}
@ -291,19 +298,21 @@ internal class LoadTimelineStrategy constructor(
* Clear any existing thread chunk entity and create a new one, with the
* rootThreadEventId included.
*/
private suspend fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) {
realm.executeTransactionAwait {
private suspend fun recreateThreadChunkEntity(rootThreadEventId: String) {
dependencies.realmInstance.write {
// 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..")
}
val threadChunk = it.createObject<ChunkEntity>().apply {
val threadChunk = copyToRealm(ChunkEntity().apply {
Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId")
this.rootThreadEventId = rootThreadEventId
this.isLastForwardThread = true
}
)
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.
*/
private suspend fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) {
realm.executeTransactionAwait {
ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let {
private suspend fun clearThreadChunkEntity(rootThreadEventId: String) {
dependencies.realmInstance.write {
ChunkEntity.findLastForwardChunkOfThread(this, roomId, rootThreadEventId)?.let {
deleteAndClearThreadEvents(it)
Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..")
}
}
@ -323,9 +333,11 @@ internal class LoadTimelineStrategy constructor(
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 TimelineChunk(
timelineScope = dependencies.timelineScope,
chunkEntity = it,
timelineSettings = dependencies.timelineSettings,
roomId = roomId,
@ -333,7 +345,7 @@ internal class LoadTimelineStrategy constructor(
fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask,
eventDecryptor = dependencies.eventDecryptor,
paginationTask = dependencies.paginationTask,
realmConfiguration = dependencies.realmConfiguration,
realm = realm,
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
timelineEventMapper = dependencies.timelineEventMapper,
uiEchoManager = uiEchoManager,

View File

@ -16,78 +16,59 @@
package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmChangeListener
import io.realm.RealmList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flatMapConcat
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.internal.database.RealmInstance
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.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where
import java.util.concurrent.atomic.AtomicReference
internal interface SendingEventsDataSource {
fun start()
fun stop()
fun buildSendingEvents(): List<TimelineEvent>
}
internal class RealmSendingEventsDataSource(
private val roomId: String,
private val realm: AtomicReference<Realm>,
private val realmInstance: RealmInstance,
private val timelineScope: CoroutineScope,
private val uiEchoManager: UIEchoManager,
private val timelineEventMapper: TimelineEventMapper,
private val onEventsUpdated: (Boolean) -> Unit
) : SendingEventsDataSource {
private var roomEntity: RoomEntity? = null
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var sendingTimelineEvents: List<TimelineEventEntity>? = null
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
if (events.isValid) {
init {
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 })
updateFrozenResults(events)
sendingTimelineEvents = events
onEventsUpdated(false)
}
}
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()
}.launchIn(timelineScope)
}
override fun buildSendingEvents(): List<TimelineEvent> {
val builtSendingEvents = mutableListOf<TimelineEvent>()
uiEchoManager.getInMemorySendingEvents()
.addWithUiEcho(builtSendingEvents)
if (frozenSendingTimelineEvents?.isValid == true) {
frozenSendingTimelineEvents
?.filter { timelineEvent ->
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
}
?.map {
timelineEventMapper.map(it)
}?.addWithUiEcho(builtSendingEvents)
}
sendingTimelineEvents
?.filter { timelineEvent ->
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
}
?.map {
timelineEventMapper.map(it)
}?.addWithUiEcho(builtSendingEvents)
return builtSendingEvents
}

View File

@ -16,16 +16,23 @@
package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.RealmConfiguration
import io.realm.RealmObjectChangeListener
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.addChangeListener
import io.realm.kotlin.removeChangeListener
import io.realm.kotlin.TypedRealm
import io.realm.kotlin.ext.asFlow
import io.realm.kotlin.notifications.DeletedObject
import io.realm.kotlin.notifications.InitialObject
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.ListChangeSet
import io.realm.kotlin.notifications.ObjectChange
import io.realm.kotlin.notifications.ResultsChange
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.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.tryOrNull
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.TimelineEventEntity
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.FetchThreadTimelineTask
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.
*/
internal class TimelineChunk(
private val timelineScope: CoroutineScope,
private val chunkEntity: ChunkEntity,
private val timelineSettings: TimelineSettings,
private val roomId: String,
@ -59,7 +68,7 @@ internal class TimelineChunk(
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask,
private val realmConfiguration: RealmConfiguration,
private val realm: TypedRealm,
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
private val timelineEventMapper: TimelineEventMapper,
private val uiEchoManager: UIEchoManager?,
@ -76,39 +85,8 @@ internal class TimelineChunk(
private var prevChunkLatch: CompletableDeferred<Unit>? = null
private var nextChunkLatch: CompletableDeferred<Unit>? = null
private val chunkObjectListener = RealmObjectChangeListener<ChunkEntity> { _, changeSet ->
if (changeSet == null) return@RealmObjectChangeListener
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 var timelineEventEntities: RealmResults<TimelineEventEntity> =
chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId).find()
private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList())
private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>())
@ -116,8 +94,13 @@ internal class TimelineChunk(
private var prevChunk: TimelineChunk? = null
init {
timelineEventEntities.addChangeListener(timelineEventsChangeListener)
chunkEntity.addChangeListener(chunkObjectListener)
timelineEventEntities.asFlow()
.onEach(::handleDatabaseChangeSet)
.launchIn(timelineScope)
chunkEntity.asFlow()
.onEach(::handleChunkObjectChange)
.launchIn(timelineScope)
}
fun hasReachedLastForward(): Boolean {
@ -326,8 +309,6 @@ internal class TimelineChunk(
nextChunkLatch?.cancel()
prevChunk = null
prevChunkLatch?.cancel()
chunkEntity.removeChangeListener(chunkObjectListener)
timelineEventEntities.removeChangeListener(timelineEventsChangeListener)
}
/**
@ -337,12 +318,11 @@ internal class TimelineChunk(
*/
private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
val baseQuery = timelineEventEntities.where()
val baseQuery = chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId)
val timelineEvents = baseQuery
.offsets(direction, count, displayIndex)
.findAll()
.orEmpty()
.find()
if (timelineEvents.isEmpty()) return LoadedFromStorage()
// Disabled due to the new fallback
@ -434,7 +414,7 @@ internal class TimelineChunk(
val loadMoreResult = try {
if (token == null) {
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
val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
@ -485,64 +465,104 @@ internal class TimelineChunk(
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.
*
*/
private fun handleDatabaseChangeSet(results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) {
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() }
private fun handleDatabaseChangeSet(resultChanges: ResultsChange<TimelineEventEntity>) {
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)
fun validateInsertion(range: ListChangeSet.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
}
}
val correctedIndex = range.startIndex + index
builtEvents.add(correctedIndex, timelineEvent)
builtEventsIndexes[timelineEvent.eventId] = correctedIndex
}
return true
}
val modifications = changeSet.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")
fun handleUpdatedResults(updatedResults: UpdatedResults<TimelineEventEntity>) {
val results = updatedResults.list
val insertions = updatedResults.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) }
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()) {
onBuiltEvents(true)
when (resultChanges) {
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? {
@ -551,11 +571,15 @@ internal class TimelineChunk(
}
return if (builtEvents.isEmpty()) {
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) {
timelineEventEntities.first(null)?.displayIndex
timelineEventEntities.firstOrNull()?.displayIndex
} else {
timelineEventEntities.last(null)?.displayIndex
timelineEventEntities.lastOrNull()?.displayIndex
}
} else if (direction == Timeline.Direction.FORWARDS) {
builtEvents.first().displayIndex + 1
@ -567,13 +591,14 @@ internal class TimelineChunk(
private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? {
if (chunkEntity == null) return null
return TimelineChunk(
timelineScope = timelineScope,
realm = realm,
chunkEntity = chunkEntity,
timelineSettings = timelineSettings,
roomId = roomId,
timelineId = timelineId,
eventDecryptor = eventDecryptor,
paginationTask = paginationTask,
realmConfiguration = realmConfiguration,
fetchThreadTimelineTask = fetchThreadTimelineTask,
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
timelineEventMapper = timelineEventMapper,
@ -598,16 +623,14 @@ private fun RealmQuery<TimelineEventEntity>.offsets(
startDisplayIndex: Int
): RealmQuery<TimelineEventEntity> {
return if (direction == Timeline.Direction.BACKWARDS) {
lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
limit(count.toLong())
query("displayIndex <= $0", startDisplayIndex)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
.limit(count)
} else {
greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
// We need to sort ascending first so limit works in the right direction
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
limit(count.toLong())
// Result is expected to be sorted descending
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
query("displayIndex >= $0", startDisplayIndex)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.limit(count)
.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
}
private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults<TimelineEventEntity> {
private fun ChunkEntity.querySortedTimelineEvents(realm: TypedRealm, rootThreadEventId: String?): RealmQuery<TimelineEventEntity> {
return if (rootThreadEventId == null) {
timelineEvents
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
} else {
timelineEvents
.where()
.beginGroup()
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.or()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId)
.endGroup()
.findAll()
}
TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
.query("root.rootThreadEventId == $0 OR root.eventId == $0", rootThreadEventId)
}.sort("displayIndex", Sort.DESCENDING)
}

View File

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

View File

@ -16,15 +16,15 @@
package org.matrix.android.sdk.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
import io.realm.Realm
import io.realm.kotlin.isValid
import io.realm.kotlin.MutableRealm
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.toModel
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.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.addStateEvent
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.di.SessionDatabase
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.util.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
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.
*/
internal class TokenChunkEventPersistor @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
@SessionDatabase private val realmInstance: RealmInstance,
@UserId private val userId: String,
private val lightweightSettingsStorage: LightweightSettingsStorage,
private val liveEventManager: Lazy<StreamEventsManager>,
@ -70,8 +68,8 @@ internal class TokenChunkEventPersistor @Inject constructor(
roomId: String,
direction: PaginationDirection
): Result {
monarchy
.awaitTransaction { realm ->
realmInstance
.write {
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
val nextToken: String?
@ -83,16 +81,16 @@ internal class TokenChunkEventPersistor @Inject constructor(
nextToken = receivedChunk.start
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) {
Timber.v("This chunk is already in the db, return.")
return@awaitTransaction
return@write
}
// Creates links in both directions
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
val currentChunk = ChunkEntity.create(realm, prevToken = prevToken, nextToken = nextToken).apply {
val prevChunk = ChunkEntity.find(this, roomId, nextToken = prevToken)
val nextChunk = ChunkEntity.find(this, roomId, prevToken = nextToken)
val currentChunk = ChunkEntity.create(this, prevToken = prevToken, nextToken = nextToken).apply {
this.nextChunk = nextChunk
this.prevChunk = prevChunk
}
@ -100,9 +98,9 @@ internal class TokenChunkEventPersistor @Inject constructor(
prevChunk?.nextChunk = currentChunk
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
handleReachEnd(roomId, direction, currentChunk)
handleReachEnd(this, roomId, direction, currentChunk)
} 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")
if (direction == PaginationDirection.FORWARDS) {
// 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.
val realm = currentChunk.realm
currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
} else {
currentChunk.isLastBackward = true
@ -130,7 +127,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
}
private fun handlePagination(
realm: Realm,
realm: MutableRealm,
roomId: String,
direction: PaginationDirection,
receivedChunk: TokenChunkEvent,
@ -169,6 +166,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
}
liveEventManager.get().dispatchPaginatedEventReceived(event, roomId)
currentChunk.addTimelineEvent(
realm = realm,
roomId = roomId,
eventEntity = eventEntity,
direction = direction,
@ -186,7 +184,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
}
}
if (currentChunk.isValid()) {
RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk)
RoomEntity.where(realm, roomId).first().find()?.addIfNecessary(currentChunk)
}
if (lightweightSettingsStorage.areThreadMessagesEnabled()) {

View File

@ -16,7 +16,8 @@
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.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
@ -30,17 +31,18 @@ import javax.inject.Inject
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
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
if (createRoomContent?.replacementRoomId == null) return
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst()
?: RoomSummaryEntity(event.roomId)
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).first().find()
?: RoomSummaryEntity()
predecessorRoomSummary.roomId = event.roomId
if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
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 {

View File

@ -16,12 +16,12 @@
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.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.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.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
@ -41,8 +41,7 @@ internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params,
internal class DefaultRoomVersionUpgradeTask @Inject constructor(
private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
@SessionDatabase
private val realmConfiguration: RealmConfiguration
@SessionDatabase private val realmInstance: RealmInstance,
) : RoomVersionUpgradeTask {
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)
tryOrNull {
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
realm.where(RoomSummaryEntity::class.java)
.equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
RoomSummaryEntity.where(realm, replacementRoomId, listOf(Membership.JOIN))
}
}
return replacementRoomId

View File

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

View File

@ -16,7 +16,6 @@
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.RuleScope
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.UserAccountDataSyncHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
import org.matrix.android.sdk.internal.util.awaitTransaction
import timber.log.Timber
import javax.inject.Inject
import kotlin.system.measureTimeMillis

View File

@ -195,16 +195,14 @@ internal class RoomSyncHandler @Inject constructor(
aggregator
)
}
realm.insertOrUpdate(roomEntities)
reporter?.reportProgress(index + 1F)
}
}
} else {
// 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)
}
realm.insertOrUpdate(rooms)
}
}

View File

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

View File

@ -16,8 +16,7 @@
package org.matrix.android.sdk.internal.session.user.accountdata
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.getDirectRooms
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
internal class DirectChatsHelper @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration
@SessionDatabase private val realmInstance: RealmInstance,
) {
/**
* @return a map of userId <-> list of roomId
*/
fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent {
return Realm.getInstance(realmConfiguration).use { realm ->
// Makes sure we have the latest realm updates, this is important as we sent this information to the server.
realm.refresh()
RoomSummaryEntity.getDirectRooms(realm)
.asSequence()
.filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
}
val realm = realmInstance.getBlockingRealm()
return RoomSummaryEntity.getDirectRooms(realm)
.asSequence()
.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.WidgetType
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.session.displayname.DisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@ -35,7 +36,7 @@ import javax.inject.Inject
internal class WidgetFactory @Inject constructor(
private val userDataSource: UserDataSource,
private val realmSessionProvider: RealmSessionProvider,
@SessionDatabase private val realmInstance: RealmInstance,
private val displayNameResolver: DisplayNameResolver,
private val urlResolver: ContentUrlResolver,
@UserId private val userId: String
@ -49,16 +50,15 @@ internal class WidgetFactory @Inject constructor(
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
null
} else {
realmSessionProvider.withRealm {
val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId)
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
SenderInfo(
userId = widgetEvent.senderId,
displayName = roomMemberSummaryEntity?.displayName,
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
avatarUrl = roomMemberSummaryEntity?.avatarUrl
)
}
val realm = realmInstance.getBlockingRealm()
val roomMemberHelper = RoomMemberHelper(realm, widgetEvent.roomId)
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
SenderInfo(
userId = widgetEvent.senderId,
displayName = roomMemberSummaryEntity?.displayName,
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
avatarUrl = roomMemberSummaryEntity?.avatarUrl
)
}
val isAddedByMe = widgetEvent.senderId == userId
return Widget(