Realm-kotlin: handle a bunch of classes on session db (timeline, eventinsertentity and some more tasks)
This commit is contained in:
parent
6edea43ab4
commit
6379c199ea
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user