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
|
package org.matrix.android.sdk.api.debug
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful methods to access to some private data managed by the SDK.
|
* Useful methods to access to some private data managed by the SDK.
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package org.matrix.android.sdk.api.session
|
package org.matrix.android.sdk.api.session
|
||||||
|
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
@ -16,14 +16,12 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.whereType
|
import org.matrix.android.sdk.internal.database.query.whereType
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,18 +29,17 @@ import javax.inject.Inject
|
|||||||
* in the session DB, this class encapsulate this functionality.
|
* in the session DB, this class encapsulate this functionality.
|
||||||
*/
|
*/
|
||||||
internal class CryptoSessionInfoProvider @Inject constructor(
|
internal class CryptoSessionInfoProvider @Inject constructor(
|
||||||
@SessionDatabase private val monarchy: Monarchy
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun isRoomEncrypted(roomId: String): Boolean {
|
fun isRoomEncrypted(roomId: String): Boolean {
|
||||||
// We look at the presence at any m.room.encryption state event no matter if it's
|
// We look at the presence at any m.room.encryption state event no matter if it's
|
||||||
// the latest one or if it is well formed
|
// the latest one or if it is well formed
|
||||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
val realm = realmInstance.getBlockingRealm()
|
||||||
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
return EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||||
.isEmpty(EventEntityFields.STATE_KEY)
|
.query("stateKey == ''")
|
||||||
.findFirst()
|
.first()
|
||||||
}
|
.find() != null
|
||||||
return encryptionEvent != null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,14 +47,11 @@ internal class CryptoSessionInfoProvider @Inject constructor(
|
|||||||
* @param allActive if true return joined as well as invited, if false, only joined
|
* @param allActive if true return joined as well as invited, if false, only joined
|
||||||
*/
|
*/
|
||||||
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
|
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
|
||||||
var userIds: List<String> = emptyList()
|
val realm = realmInstance.getBlockingRealm()
|
||||||
monarchy.doWithRealm { realm ->
|
return if (allActive) {
|
||||||
userIds = if (allActive) {
|
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
||||||
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
} else {
|
||||||
} else {
|
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
||||||
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return userIds
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.kotlin.MutableRealm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.TypedRealm
|
import io.realm.kotlin.TypedRealm
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
@ -38,12 +37,10 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
|
|||||||
import org.matrix.android.sdk.internal.crypto.store.db.query.crossSigningInfoEntityQueries
|
import org.matrix.android.sdk.internal.crypto.store.db.query.crossSigningInfoEntityQueries
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.query.userEntityQueries
|
import org.matrix.android.sdk.internal.crypto.store.db.query.userEntityQueries
|
||||||
import org.matrix.android.sdk.internal.database.RealmInstance
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.database.queryIn
|
||||||
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
@ -76,7 +73,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
|
|||||||
@Inject lateinit var cryptoRealmInstance: RealmInstance
|
@Inject lateinit var cryptoRealmInstance: RealmInstance
|
||||||
|
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
@Inject lateinit var sessionRealmConfiguration: RealmConfiguration
|
@Inject lateinit var sessionRealmInstance: RealmInstance
|
||||||
|
|
||||||
@UserId
|
@UserId
|
||||||
@Inject lateinit var myUserId: String
|
@Inject lateinit var myUserId: String
|
||||||
@ -205,21 +202,22 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
|
|||||||
|
|
||||||
private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) {
|
private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) {
|
||||||
Timber.d("## CrossSigning - Updating shields for impacted rooms...")
|
Timber.d("## CrossSigning - Updating shields for impacted rooms...")
|
||||||
awaitTransaction(sessionRealmConfiguration) { sessionRealm ->
|
sessionRealmInstance.write {
|
||||||
val cryptoRealm = cryptoRealmInstance.getBlockingRealm()
|
val cryptoRealm = cryptoRealmInstance.getBlockingRealm()
|
||||||
sessionRealm.where(RoomMemberSummaryEntity::class.java)
|
query(RoomMemberSummaryEntity::class)
|
||||||
.`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray())
|
.queryIn("userId", userList)
|
||||||
.distinct(RoomMemberSummaryEntityFields.ROOM_ID)
|
.distinct("roomId")
|
||||||
.findAll()
|
.find()
|
||||||
.map { it.roomId }
|
.map { it.roomId }
|
||||||
.also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") }
|
.also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") }
|
||||||
.forEach { roomId ->
|
.forEach { roomId ->
|
||||||
RoomSummaryEntity.where(sessionRealm, roomId)
|
RoomSummaryEntity.where(this, roomId)
|
||||||
.equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true)
|
.query("isEncrypted == true")
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
?.let { roomSummary ->
|
?.let { roomSummary ->
|
||||||
Timber.v("## CrossSigning - Check shield state for room $roomId")
|
Timber.v("## CrossSigning - Check shield state for room $roomId")
|
||||||
val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds()
|
val allActiveRoomMembers = RoomMemberHelper(this, roomId).getActiveRoomMemberIds()
|
||||||
try {
|
try {
|
||||||
val updatedTrust = computeRoomShield(
|
val updatedTrust = computeRoomShield(
|
||||||
myCrossSigningInfo,
|
myCrossSigningInfo,
|
||||||
|
@ -16,16 +16,16 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import io.realm.kotlin.query.RealmResults
|
||||||
import io.realm.RealmConfiguration
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import io.realm.RealmResults
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
@ -33,68 +33,54 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class EventInsertLiveObserver @Inject constructor(
|
internal class EventInsertLiveObserver @Inject constructor(
|
||||||
@SessionDatabase realmConfiguration: RealmConfiguration,
|
@SessionDatabase realmInstance: RealmInstance,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
|
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
|
||||||
) :
|
) :
|
||||||
RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
|
RealmLiveEntityObserver<EventInsertEntity>(realmInstance, coroutineDispatchers.io) {
|
||||||
|
|
||||||
private val lock = Mutex()
|
private val lock = Mutex()
|
||||||
|
|
||||||
override val query = Monarchy.Query {
|
init {
|
||||||
it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
|
realmInstance.getRealmFlow().flatMapConcat { realm ->
|
||||||
|
realm.query(EventInsertEntity::class, "canBeProcessed == true").asFlow()
|
||||||
|
}.onEach { resultChange ->
|
||||||
|
onChange(resultChange.list)
|
||||||
|
}.launchIn(observerScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChange(results: RealmResults<EventInsertEntity>) {
|
private suspend fun onChange(results: RealmResults<EventInsertEntity>) {
|
||||||
observerScope.launch {
|
fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
|
||||||
lock.withLock {
|
return processors.any {
|
||||||
if (!results.isLoaded || results.isEmpty()) {
|
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
|
||||||
return@withLock
|
|
||||||
}
|
|
||||||
val idsToDeleteAfterProcess = ArrayList<String>()
|
|
||||||
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
|
|
||||||
Timber.v("EventInsertEntity updated with ${results.size} results in db")
|
|
||||||
results.forEach {
|
|
||||||
if (shouldProcess(it)) {
|
|
||||||
// don't use copy from realm over there
|
|
||||||
val copiedEvent = EventInsertEntity(
|
|
||||||
eventId = it.eventId,
|
|
||||||
eventType = it.eventType
|
|
||||||
).apply {
|
|
||||||
insertType = it.insertType
|
|
||||||
}
|
|
||||||
filteredEvents.add(copiedEvent)
|
|
||||||
}
|
|
||||||
idsToDeleteAfterProcess.add(it.eventId)
|
|
||||||
}
|
|
||||||
awaitTransaction(realmConfiguration) { realm ->
|
|
||||||
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
|
|
||||||
filteredEvents.forEach { eventInsert ->
|
|
||||||
val eventId = eventInsert.eventId
|
|
||||||
val event = EventEntity.where(realm, eventId).findFirst()
|
|
||||||
if (event == null) {
|
|
||||||
Timber.v("Event $eventId not found")
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val domainEvent = event.asDomain()
|
|
||||||
processors.filter {
|
|
||||||
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
|
||||||
}.forEach {
|
|
||||||
it.process(realm, domainEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
realm.where(EventInsertEntity::class.java)
|
|
||||||
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
|
|
||||||
.findAll()
|
|
||||||
.deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
processors.forEach { it.onPostProcess() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
|
lock.withLock {
|
||||||
return processors.any {
|
if (results.isEmpty()) {
|
||||||
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
|
return@withLock
|
||||||
|
}
|
||||||
|
Timber.v("EventInsertEntity updated with ${results.size} results in db")
|
||||||
|
realmInstance.write { ->
|
||||||
|
results
|
||||||
|
.filter(::shouldProcess)
|
||||||
|
.forEach { eventInsert ->
|
||||||
|
val eventId = eventInsert.eventId
|
||||||
|
val event = EventEntity.where(this, eventId).first().find()
|
||||||
|
if (event == null) {
|
||||||
|
Timber.v("Event $eventId not found")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val domainEvent = event.asDomain()
|
||||||
|
processors.filter {
|
||||||
|
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
||||||
|
}.forEach {
|
||||||
|
it.process(this, domainEvent)
|
||||||
|
}
|
||||||
|
deleteNullable(findLatest(eventInsert))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processors.forEach { it.onPostProcess() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database
|
|||||||
|
|
||||||
import io.realm.kotlin.Deleteable
|
import io.realm.kotlin.Deleteable
|
||||||
import io.realm.kotlin.MutableRealm
|
import io.realm.kotlin.MutableRealm
|
||||||
|
import io.realm.kotlin.dynamic.DynamicMutableRealm
|
||||||
import io.realm.kotlin.dynamic.DynamicMutableRealmObject
|
import io.realm.kotlin.dynamic.DynamicMutableRealmObject
|
||||||
import io.realm.kotlin.dynamic.DynamicRealmObject
|
import io.realm.kotlin.dynamic.DynamicRealmObject
|
||||||
import io.realm.kotlin.migration.AutomaticSchemaMigration
|
import io.realm.kotlin.migration.AutomaticSchemaMigration
|
||||||
@ -115,6 +116,14 @@ internal fun MutableRealm.deleteAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun DynamicMutableRealm.deleteAll() {
|
||||||
|
configuration.schema.mapNotNull {
|
||||||
|
it.simpleName
|
||||||
|
}.forEach { className ->
|
||||||
|
delete(query(className).find())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun MutableRealm.deleteNullable(deleteable: Deleteable?) {
|
internal fun MutableRealm.deleteNullable(deleteable: Deleteable?) {
|
||||||
if (deleteable == null) return
|
if (deleteable == null) return
|
||||||
delete(deleteable)
|
delete(deleteable)
|
||||||
|
@ -16,59 +16,24 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import io.realm.kotlin.types.RealmObject
|
||||||
import io.realm.Realm
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import io.realm.RealmChangeListener
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmModel
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.cancelChildren
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.util.createBackgroundHandler
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
internal interface LiveEntityObserver : SessionLifecycleObserver
|
internal interface LiveEntityObserver : SessionLifecycleObserver
|
||||||
|
|
||||||
internal abstract class RealmLiveEntityObserver<T : RealmModel>(protected val realmConfiguration: RealmConfiguration) :
|
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmInstance: RealmInstance, coroutineDispatcher: CoroutineDispatcher) :
|
||||||
LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
|
LiveEntityObserver {
|
||||||
|
|
||||||
private companion object {
|
protected val observerScope = CoroutineScope(SupervisorJob() + coroutineDispatcher)
|
||||||
val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-LIVE_ENTITY_BACKGROUND")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val observerScope = CoroutineScope(SupervisorJob() + BACKGROUND_HANDLER.asCoroutineDispatcher())
|
|
||||||
protected abstract val query: Monarchy.Query<T>
|
|
||||||
private val isStarted = AtomicBoolean(false)
|
|
||||||
private val backgroundRealm = AtomicReference<Realm>()
|
|
||||||
private lateinit var results: AtomicReference<RealmResults<T>>
|
|
||||||
|
|
||||||
override fun onSessionStarted(session: Session) {
|
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
|
||||||
backgroundRealm.set(realm)
|
|
||||||
val queryResults = query.createQuery(realm).findAll()
|
|
||||||
queryResults.addChangeListener(this)
|
|
||||||
results = AtomicReference(queryResults)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSessionStopped(session: Session) {
|
override fun onSessionStopped(session: Session) {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
observerScope.coroutineContext.cancelChildren()
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
results.getAndSet(null).removeAllChangeListeners()
|
|
||||||
backgroundRealm.getAndSet(null).also {
|
|
||||||
it.close()
|
|
||||||
}
|
|
||||||
observerScope.coroutineContext.cancelChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClearCache(session: Session) {
|
override fun onClearCache(session: Session) {
|
||||||
|
@ -16,53 +16,10 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmChangeListener
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import io.realm.kotlin.types.RealmObject
|
import io.realm.kotlin.types.RealmObject
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
internal suspend fun <T> awaitNotEmptyResult(
|
|
||||||
realmConfiguration: RealmConfiguration,
|
|
||||||
timeoutMillis: Long,
|
|
||||||
builder: (Realm) -> RealmQuery<T>
|
|
||||||
) {
|
|
||||||
withTimeout(timeoutMillis) {
|
|
||||||
// Confine Realm interaction to a single thread with Looper.
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val latch = CompletableDeferred<Unit>()
|
|
||||||
|
|
||||||
Realm.getInstance(realmConfiguration).use { realm ->
|
|
||||||
val result = builder(realm).findAllAsync()
|
|
||||||
|
|
||||||
val listener = object : RealmChangeListener<RealmResults<T>> {
|
|
||||||
override fun onChange(it: RealmResults<T>) {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
result.removeChangeListener(this)
|
|
||||||
latch.complete(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addChangeListener(listener)
|
|
||||||
try {
|
|
||||||
latch.await()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
result.removeChangeListener(listener)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun <T : RealmObject> awaitNotEmptyResult(
|
internal suspend fun <T : RealmObject> awaitNotEmptyResult(
|
||||||
realmInstance: RealmInstance,
|
realmInstance: RealmInstance,
|
||||||
timeoutMillis: Long,
|
timeoutMillis: Long,
|
||||||
|
@ -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
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import io.realm.DynamicRealm
|
import io.realm.kotlin.migration.AutomaticSchemaMigration
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo001
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo002
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo003
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo004
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo005
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo006
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo007
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo008
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo009
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo010
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo011
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo012
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo013
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo014
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo015
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo016
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo017
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo018
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo019
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo020
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
|
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
|
|
||||||
import org.matrix.android.sdk.internal.util.Normalizer
|
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RealmSessionStoreMigration @Inject constructor(
|
internal class RealmSessionStoreMigration @Inject constructor() : MatrixAutomaticSchemaMigration(
|
||||||
private val normalizer: Normalizer
|
|
||||||
) : MatrixRealmMigration(
|
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 35L,
|
schemaVersion = 36L,
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
|
||||||
* Avoids Realm throwing when multiple instances of the migration are set.
|
|
||||||
*/
|
|
||||||
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
|
||||||
override fun hashCode() = 1000
|
|
||||||
|
|
||||||
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
|
override fun doMigrate(oldVersion: Long, migrationContext: AutomaticSchemaMigration.MigrationContext) {
|
||||||
if (oldVersion < 1) MigrateSessionTo001(realm).perform()
|
if (oldVersion < 36L) {
|
||||||
if (oldVersion < 2) MigrateSessionTo002(realm).perform()
|
// Don't bother with old migrations we force a clear cache here
|
||||||
if (oldVersion < 3) MigrateSessionTo003(realm).perform()
|
migrationContext.newRealm.deleteAll()
|
||||||
if (oldVersion < 4) MigrateSessionTo004(realm).perform()
|
}
|
||||||
if (oldVersion < 5) MigrateSessionTo005(realm).perform()
|
|
||||||
if (oldVersion < 6) MigrateSessionTo006(realm).perform()
|
|
||||||
if (oldVersion < 7) MigrateSessionTo007(realm).perform()
|
|
||||||
if (oldVersion < 8) MigrateSessionTo008(realm).perform()
|
|
||||||
if (oldVersion < 9) MigrateSessionTo009(realm).perform()
|
|
||||||
if (oldVersion < 10) MigrateSessionTo010(realm).perform()
|
|
||||||
if (oldVersion < 11) MigrateSessionTo011(realm).perform()
|
|
||||||
if (oldVersion < 12) MigrateSessionTo012(realm).perform()
|
|
||||||
if (oldVersion < 13) MigrateSessionTo013(realm).perform()
|
|
||||||
if (oldVersion < 14) MigrateSessionTo014(realm).perform()
|
|
||||||
if (oldVersion < 15) MigrateSessionTo015(realm).perform()
|
|
||||||
if (oldVersion < 16) MigrateSessionTo016(realm).perform()
|
|
||||||
if (oldVersion < 17) MigrateSessionTo017(realm).perform()
|
|
||||||
if (oldVersion < 18) MigrateSessionTo018(realm).perform()
|
|
||||||
if (oldVersion < 19) MigrateSessionTo019(realm, normalizer).perform()
|
|
||||||
if (oldVersion < 20) MigrateSessionTo020(realm).perform()
|
|
||||||
if (oldVersion < 21) MigrateSessionTo021(realm).perform()
|
|
||||||
if (oldVersion < 22) MigrateSessionTo022(realm).perform()
|
|
||||||
if (oldVersion < 23) MigrateSessionTo023(realm).perform()
|
|
||||||
if (oldVersion < 24) MigrateSessionTo024(realm).perform()
|
|
||||||
if (oldVersion < 25) MigrateSessionTo025(realm).perform()
|
|
||||||
if (oldVersion < 26) MigrateSessionTo026(realm).perform()
|
|
||||||
if (oldVersion < 27) MigrateSessionTo027(realm).perform()
|
|
||||||
if (oldVersion < 28) MigrateSessionTo028(realm).perform()
|
|
||||||
if (oldVersion < 29) MigrateSessionTo029(realm).perform()
|
|
||||||
if (oldVersion < 30) MigrateSessionTo030(realm).perform()
|
|
||||||
if (oldVersion < 31) MigrateSessionTo031(realm).perform()
|
|
||||||
if (oldVersion < 32) MigrateSessionTo032(realm).perform()
|
|
||||||
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
|
|
||||||
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
|
||||||
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,15 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import android.content.Context
|
import io.realm.kotlin.RealmConfiguration
|
||||||
import androidx.core.content.edit
|
import org.matrix.android.sdk.internal.database.model.SESSION_REALM_SCHEMA
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.BuildConfig
|
|
||||||
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.UserMd5
|
import org.matrix.android.sdk.internal.di.UserMd5
|
||||||
import org.matrix.android.sdk.internal.session.SessionModule
|
import org.matrix.android.sdk.internal.session.SessionModule
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
|
|
||||||
private const val REALM_NAME = "disk_store.realm"
|
private const val REALM_NAME = "disk_store.realm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,61 +38,19 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||||||
@SessionFilesDirectory val directory: File,
|
@SessionFilesDirectory val directory: File,
|
||||||
@SessionId val sessionId: String,
|
@SessionId val sessionId: String,
|
||||||
@UserMd5 val userMd5: String,
|
@UserMd5 val userMd5: String,
|
||||||
context: Context
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Keep legacy preferences name for compatibility reason
|
|
||||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
fun create(): RealmConfiguration {
|
fun create(): RealmConfiguration {
|
||||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
val realmConfiguration = RealmConfiguration.Builder(SESSION_REALM_SCHEMA)
|
||||||
if (shouldClearRealm) {
|
.directory(directory.path)
|
||||||
Timber.e("************************************************************")
|
|
||||||
Timber.e("The realm file session was corrupted and couldn't be loaded.")
|
|
||||||
Timber.e("The file has been deleted to recover.")
|
|
||||||
Timber.e("************************************************************")
|
|
||||||
deleteRealmFiles()
|
|
||||||
}
|
|
||||||
sharedPreferences.edit {
|
|
||||||
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
|
||||||
.compactOnLaunch(RealmCompactOnLaunch())
|
|
||||||
.directory(directory)
|
|
||||||
.name(REALM_NAME)
|
.name(REALM_NAME)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.allowWritesOnUiThread(true)
|
|
||||||
.modules(SessionRealmModule())
|
|
||||||
.schemaVersion(realmSessionStoreMigration.schemaVersion)
|
.schemaVersion(realmSessionStoreMigration.schemaVersion)
|
||||||
.migration(realmSessionStoreMigration)
|
.migration(realmSessionStoreMigration)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
|
||||||
Realm.getInstance(realmConfiguration).use {
|
|
||||||
Timber.v("Successfully create realm instance")
|
|
||||||
sharedPreferences.edit {
|
|
||||||
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return realmConfiguration
|
return realmConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all the realm files of the session
|
|
||||||
private fun deleteRealmFiles() {
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Timber.e("No op because it is a debug build")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listOf(REALM_NAME, "${REALM_NAME}.lock", "${REALM_NAME}.note", "${REALM_NAME}.management").forEach { file ->
|
|
||||||
try {
|
|
||||||
File(directory, file).deleteRecursively()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "Unable to delete files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,20 +24,18 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
|
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.cleanUp
|
import org.matrix.android.sdk.internal.database.model.cleanUp
|
||||||
import org.matrix.android.sdk.internal.database.query.find
|
import org.matrix.android.sdk.internal.database.query.find
|
||||||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.database.query.whereChunkId
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -47,14 +45,11 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
|
|||||||
} else {
|
} else {
|
||||||
val stateKey = stateEvent.stateKey ?: return
|
val stateKey = stateEvent.stateKey ?: return
|
||||||
val type = stateEvent.type
|
val type = stateEvent.type
|
||||||
val pastStateEvent = stateEvents.where()
|
val indexOfStateEvent = stateEvents.indexOfFirst {
|
||||||
.equalTo(EventEntityFields.ROOM_ID, roomId)
|
it.roomId == roomId && it.stateKey == stateKey && it.type == type
|
||||||
.equalTo(EventEntityFields.STATE_KEY, stateKey)
|
}
|
||||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
if (indexOfStateEvent != -1) {
|
||||||
.findFirst()
|
stateEvents.removeAt(indexOfStateEvent)
|
||||||
|
|
||||||
if (pastStateEvent != null) {
|
|
||||||
stateEvents.remove(pastStateEvent)
|
|
||||||
}
|
}
|
||||||
stateEvents.add(stateEvent)
|
stateEvents.add(stateEvent)
|
||||||
}
|
}
|
||||||
@ -72,7 +67,7 @@ internal fun ChunkEntity.addTimelineEvent(
|
|||||||
if (timelineEvents.find(eventId) != null) {
|
if (timelineEvents.find(eventId) != null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val displayIndex = nextDisplayIndex(direction)
|
val displayIndex = nextDisplayIndex(realm, direction)
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
val senderId = eventEntity.sender ?: ""
|
val senderId = eventEntity.sender ?: ""
|
||||||
|
|
||||||
@ -149,13 +144,17 @@ private fun handleReadReceipts(realm: MutableRealm, roomId: String, eventEntity:
|
|||||||
return readReceiptsSummaryEntity
|
return readReceiptsSummaryEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
|
internal fun ChunkEntity.nextDisplayIndex(realm: TypedRealm, direction: PaginationDirection): Int {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
PaginationDirection.FORWARDS -> {
|
PaginationDirection.FORWARDS -> {
|
||||||
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
|
(TimelineEventEntity.whereChunkId(realm, chunkId)
|
||||||
|
.max("displayIndex", Int::class)
|
||||||
|
.find() ?: 0) + 1
|
||||||
}
|
}
|
||||||
PaginationDirection.BACKWARDS -> {
|
PaginationDirection.BACKWARDS -> {
|
||||||
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
|
(TimelineEventEntity.whereChunkId(realm, chunkId)
|
||||||
|
.min("displayIndex", Int::class)
|
||||||
|
.find() ?: 0) - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,10 @@
|
|||||||
package org.matrix.android.sdk.internal.database.helper
|
package org.matrix.android.sdk.internal.database.helper
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.Sort
|
|
||||||
import io.realm.kotlin.MutableRealm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.TypedRealm
|
import io.realm.kotlin.TypedRealm
|
||||||
|
import io.realm.kotlin.query.RealmQuery
|
||||||
|
import io.realm.kotlin.query.Sort
|
||||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
import org.matrix.android.sdk.api.session.events.model.isRedacted
|
import org.matrix.android.sdk.api.session.events.model.isRedacted
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
@ -32,14 +31,14 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
|
|||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
|
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.find
|
import org.matrix.android.sdk.internal.database.query.find
|
||||||
import org.matrix.android.sdk.internal.database.query.findIncludingEvent
|
import org.matrix.android.sdk.internal.database.query.findIncludingEvent
|
||||||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.database.query.whereChunkId
|
||||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.extensions.realm
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private typealias Summary = Pair<Int, TimelineEventEntity>?
|
private typealias Summary = Pair<Int, TimelineEventEntity>?
|
||||||
@ -62,7 +61,7 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
|
|||||||
val latestEventInThread = threadSummary.second
|
val latestEventInThread = threadSummary.second
|
||||||
|
|
||||||
// If this is a thread message, find its root event if exists
|
// If this is a thread message, find its root event if exists
|
||||||
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity
|
val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent(realm) else eventEntity
|
||||||
|
|
||||||
rootThreadEvent?.markEventAsRoot(
|
rootThreadEvent?.markEventAsRoot(
|
||||||
inThreadMessages = inThreadMessages,
|
inThreadMessages = inThreadMessages,
|
||||||
@ -80,11 +79,12 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
|
|||||||
* Finds the root event of the the current thread event message.
|
* Finds the root event of the the current thread event message.
|
||||||
* Returns the EventEntity or null if the root event do not exist
|
* Returns the EventEntity or null if the root event do not exist
|
||||||
*/
|
*/
|
||||||
internal fun EventEntity.findRootThreadEvent(): EventEntity? =
|
internal fun EventEntity.findRootThreadEvent(realm: TypedRealm): EventEntity? =
|
||||||
rootThreadEventId?.let {
|
rootThreadEventId?.let { eventId ->
|
||||||
EventEntity
|
EventEntity
|
||||||
.where(realm, it)
|
.where(realm, eventId)
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,15 +122,15 @@ internal fun EventEntity.threadSummaryInThread(realm: TypedRealm, rootThreadEven
|
|||||||
|
|
||||||
// Iterate the chunk until we find our latest event
|
// Iterate the chunk until we find our latest event
|
||||||
while (result == null) {
|
while (result == null) {
|
||||||
result = findLatestSortedChunkEvent(chunk, rootThreadEventId)
|
result = findLatestSortedChunkEvent(realm, chunk, rootThreadEventId)
|
||||||
chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break
|
chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null && chunkEntity != null) {
|
if (result == null && chunkEntity != null) {
|
||||||
// Find latest event from our current chunk
|
// Find latest event from our current chunk
|
||||||
result = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId)
|
result = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
|
||||||
} else if (result != null && chunkEntity != null) {
|
} else if (result != null && chunkEntity != null) {
|
||||||
val currentChunkLatestEvent = findLatestSortedChunkEvent(chunkEntity, rootThreadEventId)
|
val currentChunkLatestEvent = findLatestSortedChunkEvent(realm, chunkEntity, rootThreadEventId)
|
||||||
result = findMostRecentEvent(result, currentChunkLatestEvent)
|
result = findMostRecentEvent(result, currentChunkLatestEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,37 +185,41 @@ private fun findMostRecentEvent(result: TimelineEventEntity, currentChunkLatestE
|
|||||||
/**
|
/**
|
||||||
* Find the latest event of the current chunk.
|
* Find the latest event of the current chunk.
|
||||||
*/
|
*/
|
||||||
private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
|
private fun findLatestSortedChunkEvent(realm: TypedRealm, chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
|
||||||
chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull {
|
TimelineEventEntity.whereChunkId(realm, chunk.chunkId)
|
||||||
it.root?.rootThreadEventId == rootThreadEventId
|
.sort("displayIndex", Sort.DESCENDING)
|
||||||
}
|
.find()
|
||||||
|
.firstOrNull {
|
||||||
|
it.root?.rootThreadEventId == rootThreadEventId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all TimelineEventEntity that are root threads for the specified room.
|
* Find all TimelineEventEntity that are root threads for the specified room.
|
||||||
* @param realm the realm instance
|
* @param realm the realm instance
|
||||||
* @param roomId The room that all stored root threads will be returned
|
* @param roomId The room that all stored root threads will be returned
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: TypedRealm, roomId: String): RealmQuery<TimelineEventEntity> =
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.whereRoomId(realm, roomId = roomId)
|
.whereRoomId(realm, roomId = roomId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
|
.query("root.isRootThread == true")
|
||||||
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false)
|
.query("ownedByThreadChunk == false")
|
||||||
.sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING)
|
.sort("root.originServerTs", Sort.DESCENDING)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement.
|
* Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement.
|
||||||
*/
|
*/
|
||||||
internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: String): List<TimelineEvent> =
|
internal fun List<TimelineEvent>.mapEventsWithEdition(realm: TypedRealm, roomId: String): List<TimelineEvent> =
|
||||||
this.map {
|
this.map {
|
||||||
EventAnnotationsSummaryEntity
|
EventAnnotationsSummaryEntity
|
||||||
.where(realm, roomId, eventId = it.eventId)
|
.where(realm, roomId, eventId = it.eventId)
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
?.editSummary
|
?.editSummary
|
||||||
?.editions
|
?.editions
|
||||||
?.lastOrNull()
|
?.lastOrNull()
|
||||||
?.eventId
|
?.eventId
|
||||||
?.let { editedEventId ->
|
?.let { editedEventId ->
|
||||||
TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent ->
|
TimelineEventEntity.where(realm, roomId, eventId = editedEventId).first().find()?.let { editedEvent ->
|
||||||
it.root.threadDetails = it.root.threadDetails?.copy(
|
it.root.threadDetails = it.root.threadDetails?.copy(
|
||||||
lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
|
lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
|
||||||
?: "(edited)"
|
?: "(edited)"
|
||||||
@ -230,15 +234,11 @@ internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: Stri
|
|||||||
* @param realm the realm instance
|
* @param realm the realm instance
|
||||||
* @param roomId The roomId that the user is currently in
|
* @param roomId The roomId that the user is currently in
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: TypedRealm, roomId: String): RealmQuery<TimelineEventEntity> =
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.whereRoomId(realm, roomId = roomId)
|
.whereRoomId(realm, roomId = roomId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
|
.query("root.isRootThread == true")
|
||||||
.beginGroup()
|
.process("root.threadNotificationStateStr", listOf(ThreadNotificationState.NEW_MESSAGE, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE))
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_MESSAGE.name)
|
|
||||||
.or()
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE.name)
|
|
||||||
.endGroup()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given user is participating in a current thread.
|
* Returns whether or not the given user is participating in a current thread.
|
||||||
@ -247,14 +247,13 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
|
|||||||
* @param rootThreadEventId the thread that the search will be done
|
* @param rootThreadEventId the thread that the search will be done
|
||||||
* @param senderId the user that will try to find participation
|
* @param senderId the user that will try to find participation
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Realm, roomId: String, rootThreadEventId: String, senderId: String): Boolean =
|
internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: TypedRealm, roomId: String, rootThreadEventId: String, senderId: String): Boolean =
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.whereRoomId(realm, roomId = roomId)
|
.whereRoomId(realm, roomId = roomId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
.query("root.rootThreadEventId == $0", rootThreadEventId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.SENDER, senderId)
|
.query("root.sender == $0", senderId)
|
||||||
.findFirst()
|
.first()
|
||||||
?.let { true }
|
.find() != null
|
||||||
?: false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given user is mentioned in a current thread.
|
* Returns whether or not the given user is mentioned in a current thread.
|
||||||
@ -263,22 +262,21 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re
|
|||||||
* @param rootThreadEventId the thread that the search will be done
|
* @param rootThreadEventId the thread that the search will be done
|
||||||
* @param userId the user that will try to find if there is a mention
|
* @param userId the user that will try to find if there is a mention
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm, roomId: String, rootThreadEventId: String, userId: String): Boolean =
|
internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: TypedRealm, roomId: String, rootThreadEventId: String, userId: String): Boolean =
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.whereRoomId(realm, roomId = roomId)
|
.whereRoomId(realm, roomId = roomId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
.query("root.rootThreadEventId == $0", rootThreadEventId)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.SENDER, userId)
|
.query("root.sender == $0", userId)
|
||||||
.findAll()
|
.find()
|
||||||
.firstOrNull { isUserMentioned(userId, it) }
|
.firstOrNull { isUserMentioned(userId, it) } != null
|
||||||
?.let { true }
|
|
||||||
?: false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the read receipt for the current user.
|
* Find the read receipt for the current user.
|
||||||
*/
|
*/
|
||||||
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? =
|
internal fun findMyReadReceipt(realm: TypedRealm, roomId: String, userId: String): String? =
|
||||||
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
|
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
?.eventId
|
?.eventId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -296,18 +294,15 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin
|
|||||||
* Important: It will work only with the latest chunk, while read marker will be changed
|
* Important: It will work only with the latest chunk, while read marker will be changed
|
||||||
* immediately so we should not display wrong notifications
|
* immediately so we should not display wrong notifications
|
||||||
*/
|
*/
|
||||||
internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) {
|
internal fun updateNotificationsNew(roomId: String, realm: MutableRealm, currentUserId: String) {
|
||||||
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return
|
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return
|
||||||
|
|
||||||
val readReceiptChunk = ChunkEntity
|
val readReceiptChunk = ChunkEntity
|
||||||
.findIncludingEvent(realm, readReceipt) ?: return
|
.findIncludingEvent(realm, readReceipt) ?: return
|
||||||
|
|
||||||
val readReceiptChunkTimelineEvents = readReceiptChunk
|
val readReceiptChunkTimelineEvents = TimelineEventEntity.whereChunkId(realm, chunkId = readReceiptChunk.chunkId)
|
||||||
.timelineEvents
|
.sort("displayIndex", Sort.ASCENDING)
|
||||||
.where()
|
.find() ?: return
|
||||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
|
||||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
|
||||||
.findAll() ?: return
|
|
||||||
|
|
||||||
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
|
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
|
||||||
|
|
||||||
@ -357,7 +352,7 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
|
|||||||
rootThreadEventId = eventId,
|
rootThreadEventId = eventId,
|
||||||
senderId = currentUserId
|
senderId = currentUserId
|
||||||
)
|
)
|
||||||
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()
|
val rootThreadEventEntity = EventEntity.where(realm, eventId).first().find()
|
||||||
|
|
||||||
if (isUserParticipating) {
|
if (isUserParticipating) {
|
||||||
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE
|
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.model
|
package org.matrix.android.sdk.internal.database.model
|
||||||
|
|
||||||
import io.realm.annotations.RealmModule
|
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||||
@ -24,52 +23,48 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||||||
/**
|
/**
|
||||||
* Realm module for Session.
|
* Realm module for Session.
|
||||||
*/
|
*/
|
||||||
@RealmModule(
|
internal val SESSION_REALM_SCHEMA = setOf(
|
||||||
library = true,
|
ChunkEntity::class,
|
||||||
classes = [
|
EventEntity::class,
|
||||||
ChunkEntity::class,
|
EventInsertEntity::class,
|
||||||
EventEntity::class,
|
TimelineEventEntity::class,
|
||||||
EventInsertEntity::class,
|
FilterEntity::class,
|
||||||
TimelineEventEntity::class,
|
ReadReceiptEntity::class,
|
||||||
FilterEntity::class,
|
RoomEntity::class,
|
||||||
ReadReceiptEntity::class,
|
RoomSummaryEntity::class,
|
||||||
RoomEntity::class,
|
RoomTagEntity::class,
|
||||||
RoomSummaryEntity::class,
|
SyncEntity::class,
|
||||||
RoomTagEntity::class,
|
PendingThreePidEntity::class,
|
||||||
SyncEntity::class,
|
UserEntity::class,
|
||||||
PendingThreePidEntity::class,
|
IgnoredUserEntity::class,
|
||||||
UserEntity::class,
|
BreadcrumbsEntity::class,
|
||||||
IgnoredUserEntity::class,
|
UserThreePidEntity::class,
|
||||||
BreadcrumbsEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
UserThreePidEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EventAnnotationsSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
EditionOfEvent::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
PollResponseAggregatedSummaryEntity::class,
|
||||||
EditionOfEvent::class,
|
LiveLocationShareAggregatedSummaryEntity::class,
|
||||||
PollResponseAggregatedSummaryEntity::class,
|
ReferencesAggregatedSummaryEntity::class,
|
||||||
LiveLocationShareAggregatedSummaryEntity::class,
|
PushRulesEntity::class,
|
||||||
ReferencesAggregatedSummaryEntity::class,
|
PushRuleEntity::class,
|
||||||
PushRulesEntity::class,
|
PushConditionEntity::class,
|
||||||
PushRuleEntity::class,
|
PreviewUrlCacheEntity::class,
|
||||||
PushConditionEntity::class,
|
PusherEntity::class,
|
||||||
PreviewUrlCacheEntity::class,
|
PusherDataEntity::class,
|
||||||
PusherEntity::class,
|
ReadReceiptsSummaryEntity::class,
|
||||||
PusherDataEntity::class,
|
ReadMarkerEntity::class,
|
||||||
ReadReceiptsSummaryEntity::class,
|
UserDraftsEntity::class,
|
||||||
ReadMarkerEntity::class,
|
DraftEntity::class,
|
||||||
UserDraftsEntity::class,
|
HomeServerCapabilitiesEntity::class,
|
||||||
DraftEntity::class,
|
RoomMemberSummaryEntity::class,
|
||||||
HomeServerCapabilitiesEntity::class,
|
CurrentStateEventEntity::class,
|
||||||
RoomMemberSummaryEntity::class,
|
UserAccountDataEntity::class,
|
||||||
CurrentStateEventEntity::class,
|
ScalarTokenEntity::class,
|
||||||
UserAccountDataEntity::class,
|
WellknownIntegrationManagerConfigEntity::class,
|
||||||
ScalarTokenEntity::class,
|
RoomAccountDataEntity::class,
|
||||||
WellknownIntegrationManagerConfigEntity::class,
|
SpaceChildSummaryEntity::class,
|
||||||
RoomAccountDataEntity::class,
|
SpaceParentSummaryEntity::class,
|
||||||
SpaceChildSummaryEntity::class,
|
UserPresenceEntity::class,
|
||||||
SpaceParentSummaryEntity::class,
|
ThreadSummaryEntity::class
|
||||||
UserPresenceEntity::class,
|
|
||||||
ThreadSummaryEntity::class
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
internal class SessionRealmModule
|
|
||||||
|
@ -16,20 +16,19 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.query
|
package org.matrix.android.sdk.internal.database.query
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.RealmQuery
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.query.RealmQuery
|
||||||
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntityFields
|
|
||||||
|
|
||||||
internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<ReferencesAggregatedSummaryEntity> {
|
internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: TypedRealm, eventId: String): RealmQuery<ReferencesAggregatedSummaryEntity> {
|
||||||
val query = realm.where<ReferencesAggregatedSummaryEntity>()
|
return realm.query(ReferencesAggregatedSummaryEntity::class)
|
||||||
query.equalTo(ReferencesAggregatedSummaryEntityFields.EVENT_ID, eventId)
|
.query("eventId == $0", eventId)
|
||||||
return query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, txID: String): ReferencesAggregatedSummaryEntity {
|
internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: MutableRealm, txID: String): ReferencesAggregatedSummaryEntity {
|
||||||
return realm.createObject(ReferencesAggregatedSummaryEntity::class.java).apply {
|
val entity = ReferencesAggregatedSummaryEntity().apply {
|
||||||
this.eventId = txID
|
this.eventId = txID
|
||||||
}
|
}
|
||||||
|
return realm.copyToRealm(entity)
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,12 @@ import io.realm.kotlin.MutableRealm
|
|||||||
import io.realm.kotlin.TypedRealm
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.kotlin.query.RealmQuery
|
import io.realm.kotlin.query.RealmQuery
|
||||||
import io.realm.kotlin.query.RealmResults
|
import io.realm.kotlin.query.RealmResults
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.andIf
|
import org.matrix.android.sdk.internal.database.andIf
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||||
import org.matrix.android.sdk.internal.database.queryNotIn
|
import org.matrix.android.sdk.internal.database.queryNotIn
|
||||||
|
import org.matrix.android.sdk.internal.query.process
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
|
internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
|
||||||
return realm.query(RoomSummaryEntity::class)
|
return realm.query(RoomSummaryEntity::class)
|
||||||
@ -32,6 +34,12 @@ internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RoomSummaryEntity.Companion.where(realm: TypedRealm, roomId: String, memberships: List<Membership>): RealmQuery<RoomSummaryEntity> {
|
||||||
|
return realm.query(RoomSummaryEntity::class)
|
||||||
|
.query("roomId == $0", roomId)
|
||||||
|
.process("membershipStr", memberships)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.findByAlias(realm: TypedRealm, roomAlias: String): RoomSummaryEntity? {
|
internal fun RoomSummaryEntity.Companion.findByAlias(realm: TypedRealm, roomAlias: String): RoomSummaryEntity? {
|
||||||
val roomSummary = realm.query(RoomSummaryEntity::class)
|
val roomSummary = realm.query(RoomSummaryEntity::class)
|
||||||
.query("canonicalAlias == $0", roomAlias)
|
.query("canonicalAlias == $0", roomAlias)
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.query
|
|||||||
import io.realm.kotlin.TypedRealm
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.kotlin.query.RealmQuery
|
import io.realm.kotlin.query.RealmQuery
|
||||||
import io.realm.kotlin.query.RealmResults
|
import io.realm.kotlin.query.RealmResults
|
||||||
|
import io.realm.kotlin.types.ObjectId
|
||||||
import io.realm.kotlin.types.RealmList
|
import io.realm.kotlin.types.RealmList
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||||
@ -60,6 +61,14 @@ internal fun TimelineEventEntity.Companion.whereRoomId(
|
|||||||
.query("roomId == $0", roomId)
|
.query("roomId == $0", roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.whereChunkId(
|
||||||
|
realm: TypedRealm,
|
||||||
|
chunkId: ObjectId
|
||||||
|
): RealmQuery<TimelineEventEntity> {
|
||||||
|
return where(realm)
|
||||||
|
.query("chunkId == $0", chunkId)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(
|
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(
|
||||||
realm: TypedRealm,
|
realm: TypedRealm,
|
||||||
senderMembershipEventId: String
|
senderMembershipEventId: String
|
||||||
|
@ -16,42 +16,40 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.database.tools
|
package org.matrix.android.sdk.internal.database.tools
|
||||||
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
|
|
||||||
internal class RealmDebugTools(
|
internal class RealmDebugTools(
|
||||||
private val realmConfiguration: RealmConfiguration
|
private val realmInstance: RealmInstance
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Get info about the DB.
|
* Get info about the DB.
|
||||||
*/
|
*/
|
||||||
fun getInfo(baseName: String): String {
|
fun getInfo(baseName: String): String {
|
||||||
|
val realm = realmInstance.getBlockingRealm()
|
||||||
return buildString {
|
return buildString {
|
||||||
append("\n$baseName Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}")
|
val realmConfiguration = realmInstance.realmConfiguration
|
||||||
|
append("\n$baseName Realm located at : ${realmConfiguration.path}/${realmConfiguration.name}")
|
||||||
|
|
||||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||||
val key = realmConfiguration.encryptionKey.joinToString("") { byte -> "%02x".format(byte) }
|
val key = realmConfiguration.encryptionKey?.joinToString("") { byte -> "%02x".format(byte) }
|
||||||
append("\n$baseName Realm encryption key : $key")
|
append("\n$baseName Realm encryption key : $key")
|
||||||
}
|
}
|
||||||
|
|
||||||
Realm.getInstance(realmConfiguration).use { realm ->
|
// Check if we have data
|
||||||
// Check if we have data
|
separator()
|
||||||
separator()
|
separator()
|
||||||
separator()
|
var total = 0L
|
||||||
append("\n$baseName Realm is empty: ${realm.isEmpty}")
|
val maxNameLength = realmConfiguration.schema.maxOf { it.simpleName?.length ?: 0 }
|
||||||
var total = 0L
|
realmConfiguration.schema.forEach { modelClazz ->
|
||||||
val maxNameLength = realmConfiguration.realmObjectClasses.maxOf { it.simpleName.length }
|
val count = realm.query(modelClazz).count().find()
|
||||||
realmConfiguration.realmObjectClasses.forEach { modelClazz ->
|
total += count
|
||||||
val count = realm.where(modelClazz).count()
|
append("\n$baseName Realm - count ${modelClazz.simpleName?.padEnd(maxNameLength)} : $count")
|
||||||
total += count
|
|
||||||
append("\n$baseName Realm - count ${modelClazz.simpleName.padEnd(maxNameLength)} : $count")
|
|
||||||
}
|
|
||||||
separator()
|
|
||||||
append("\n$baseName Realm - total count: $total")
|
|
||||||
separator()
|
|
||||||
separator()
|
|
||||||
}
|
}
|
||||||
|
separator()
|
||||||
|
append("\n$baseName Realm - total count: $total")
|
||||||
|
separator()
|
||||||
|
separator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,26 +16,29 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.debug
|
package org.matrix.android.sdk.internal.debug
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
import org.matrix.android.sdk.api.debug.DebugService
|
import org.matrix.android.sdk.api.debug.DebugService
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
|
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
|
||||||
|
import org.matrix.android.sdk.internal.di.AuthDatabase
|
||||||
|
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultDebugService @Inject constructor(
|
internal class DefaultDebugService @Inject constructor(
|
||||||
// @AuthDatabase private val realmConfigurationAuth: RealmConfiguration,
|
@AuthDatabase private val authRealmInstance: RealmInstance,
|
||||||
// @GlobalDatabase private val realmConfigurationGlobal: RealmConfiguration,
|
@GlobalDatabase private val globalRealmInstance: RealmInstance,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionManager: SessionManager,
|
||||||
) : DebugService {
|
) : DebugService {
|
||||||
|
|
||||||
override fun getAllRealmConfigurations(): List<RealmConfiguration> {
|
override fun getAllRealmConfigurations(): List<RealmConfiguration> {
|
||||||
return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty()
|
return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() +
|
||||||
// realmConfigurationAuth +
|
authRealmInstance.realmConfiguration + globalRealmInstance.realmConfiguration
|
||||||
// realmConfigurationGlobal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDbUsageInfo() = buildString {
|
override fun getDbUsageInfo() = buildString {
|
||||||
//append(RealmDebugTools(realmConfigurationAuth).getInfo("Auth"))
|
append(RealmDebugTools(authRealmInstance).getInfo("Auth"))
|
||||||
//append(RealmDebugTools(realmConfigurationGlobal).getInfo("Global"))
|
append(RealmDebugTools(globalRealmInstance).getInfo("Global"))
|
||||||
append(sessionManager.getLastSession()?.getDbUsageInfo())
|
append(sessionManager.getLastSession()?.getDbUsageInfo())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session
|
|||||||
|
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -68,7 +68,11 @@ import org.matrix.android.sdk.api.util.appendParamToUrl
|
|||||||
import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
|
import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
|
||||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
|
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
|
||||||
|
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
|
||||||
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
|
import org.matrix.android.sdk.internal.di.IdentityDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
@ -82,10 +86,12 @@ internal class DefaultSession @Inject constructor(
|
|||||||
override val sessionParams: SessionParams,
|
override val sessionParams: SessionParams,
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val globalErrorHandler: GlobalErrorHandler,
|
private val globalErrorHandler: GlobalErrorHandler,
|
||||||
@SessionId
|
@SessionDatabase private val sessionRealmInstance: RealmInstance,
|
||||||
override val sessionId: String,
|
@CryptoDatabase private val cryptoRealmInstance: RealmInstance,
|
||||||
|
@IdentityDatabase private val identityRealmInstance: RealmInstance,
|
||||||
|
@ContentScannerDatabase private val contentScannerRealmInstance: RealmInstance,
|
||||||
|
@SessionId override val sessionId: String,
|
||||||
override val coroutineDispatchers: MatrixCoroutineDispatchers,
|
override val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
|
||||||
private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>,
|
private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>,
|
||||||
private val sessionListeners: SessionListeners,
|
private val sessionListeners: SessionListeners,
|
||||||
private val roomService: Lazy<RoomService>,
|
private val roomService: Lazy<RoomService>,
|
||||||
@ -258,18 +264,18 @@ internal class DefaultSession @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getDbUsageInfo() = buildString {
|
override fun getDbUsageInfo() = buildString {
|
||||||
append(RealmDebugTools(realmConfiguration).getInfo("Session"))
|
append(RealmDebugTools(sessionRealmInstance).getInfo("Session"))
|
||||||
//append(RealmDebugTools(realmConfigurationCrypto).getInfo("Crypto"))
|
append(RealmDebugTools(cryptoRealmInstance).getInfo("Crypto"))
|
||||||
//append(RealmDebugTools(realmConfigurationIdentity).getInfo("Identity"))
|
append(RealmDebugTools(identityRealmInstance).getInfo("Identity"))
|
||||||
//append(RealmDebugTools(realmConfigurationContentScanner).getInfo("ContentScanner"))
|
append(RealmDebugTools(contentScannerRealmInstance).getInfo("ContentScanner"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRealmConfigurations(): List<RealmConfiguration> {
|
override fun getRealmConfigurations(): List<RealmConfiguration> {
|
||||||
return listOf(
|
return listOf(
|
||||||
realmConfiguration,
|
sessionRealmInstance.realmConfiguration,
|
||||||
// realmConfigurationCrypto,
|
cryptoRealmInstance.realmConfiguration,
|
||||||
// realmConfigurationIdentity,
|
identityRealmInstance.realmConfiguration,
|
||||||
// realmConfigurationContentScanner,
|
contentScannerRealmInstance.realmConfiguration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session
|
package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ internal interface EventInsertLiveProcessor {
|
|||||||
|
|
||||||
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
|
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
|
||||||
|
|
||||||
suspend fun process(realm: Realm, event: Event)
|
fun process(realm: MutableRealm, event: Event)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after transaction.
|
* Called after transaction.
|
||||||
|
@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.session
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
@ -49,7 +49,7 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage
|
|||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
||||||
import org.matrix.android.sdk.internal.di.Authenticated
|
import org.matrix.android.sdk.internal.di.Authenticated
|
||||||
import org.matrix.android.sdk.internal.di.CacheDirectory
|
import org.matrix.android.sdk.internal.di.CacheDirectory
|
||||||
@ -202,10 +202,16 @@ internal abstract class SessionModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
@SessionScope
|
@SessionScope
|
||||||
fun providesMonarchy(@SessionDatabase realmConfiguration: RealmConfiguration): Monarchy {
|
fun providesRealmInstance(
|
||||||
return Monarchy.Builder()
|
@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
.setRealmConfiguration(realmConfiguration)
|
@SessionCoroutineScope coroutineScope: CoroutineScope,
|
||||||
.build()
|
matrixCoroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
|
): RealmInstance {
|
||||||
|
return RealmInstance(
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
realmConfiguration = realmConfiguration,
|
||||||
|
coroutineDispatcher = matrixCoroutineDispatchers.io
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -367,10 +373,6 @@ internal abstract class SessionModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
|
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
|
abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
|
||||||
|
@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.cache
|
|||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.api.session.cache.CacheService
|
import org.matrix.android.sdk.api.session.cache.CacheService
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ -31,8 +31,8 @@ internal abstract class CacheModule {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
fun providesClearCacheTask(@SessionDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
|
fun providesClearCacheTask(@SessionDatabase realmInstance: RealmInstance): ClearCacheTask {
|
||||||
return RealmClearCacheTask(realmConfiguration)
|
return RealmKotlinClearCacheTask(realmInstance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,24 +16,13 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.cache
|
package org.matrix.android.sdk.internal.session.cache
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.internal.database.RealmInstance
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.database.deleteAll
|
import org.matrix.android.sdk.internal.database.deleteAll
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface ClearCacheTask : Task<Unit, Unit>
|
internal interface ClearCacheTask : Task<Unit, Unit>
|
||||||
|
|
||||||
internal class RealmClearCacheTask @Inject constructor(private val realmConfiguration: RealmConfiguration) : ClearCacheTask {
|
|
||||||
|
|
||||||
override suspend fun execute(params: Unit) {
|
|
||||||
awaitTransaction(realmConfiguration) {
|
|
||||||
it.deleteAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class RealmKotlinClearCacheTask @Inject constructor(private val realmInstance: RealmInstance) : ClearCacheTask {
|
internal class RealmKotlinClearCacheTask @Inject constructor(private val realmInstance: RealmInstance) : ClearCacheTask {
|
||||||
|
|
||||||
override suspend fun execute(params: Unit) {
|
override suspend fun execute(params: Unit) {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.matrix.android.sdk.internal.session.call
|
package org.matrix.android.sdk.internal.session.call
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.MutableRealm
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
@ -54,7 +55,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
|
|||||||
return allowedTypes.contains(eventType)
|
return allowedTypes.contains(eventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
eventsToPostProcess.add(event)
|
eventsToPostProcess.add(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.cleanup
|
package org.matrix.android.sdk.internal.session.cleanup
|
||||||
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||||
import org.matrix.android.sdk.internal.crypto.CryptoModule
|
import org.matrix.android.sdk.internal.crypto.CryptoModule
|
||||||
@ -47,7 +44,7 @@ internal class CleanupSession @Inject constructor(
|
|||||||
@SessionFilesDirectory private val sessionFiles: File,
|
@SessionFilesDirectory private val sessionFiles: File,
|
||||||
@SessionDownloadsDirectory private val sessionCache: File,
|
@SessionDownloadsDirectory private val sessionCache: File,
|
||||||
private val realmKeysUtils: RealmKeysUtils,
|
private val realmKeysUtils: RealmKeysUtils,
|
||||||
@SessionDatabase private val realmSessionConfiguration: RealmConfiguration,
|
@SessionDatabase private val sessionRealmInstance: RealmInstance,
|
||||||
@CryptoDatabase private val cryptoRealmInstance: RealmInstance,
|
@CryptoDatabase private val cryptoRealmInstance: RealmInstance,
|
||||||
@UserMd5 private val userMd5: String
|
@UserMd5 private val userMd5: String
|
||||||
) {
|
) {
|
||||||
@ -61,8 +58,8 @@ internal class CleanupSession @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cleanup() {
|
suspend fun cleanup() {
|
||||||
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
val sessionNumberOfActiveVersions = sessionRealmInstance.getRealm().getNumberOfActiveVersions()
|
||||||
Timber.d("Realm instance ($sessionRealmCount)")
|
Timber.d("Realm active sessions ($sessionNumberOfActiveVersions)")
|
||||||
|
|
||||||
Timber.d("Cleanup: release session...")
|
Timber.d("Cleanup: release session...")
|
||||||
sessionManager.releaseSession(sessionId)
|
sessionManager.releaseSession(sessionId)
|
||||||
@ -80,33 +77,12 @@ internal class CleanupSession @Inject constructor(
|
|||||||
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
|
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
|
||||||
realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
|
realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
|
||||||
|
|
||||||
// Wait for all the Realm instance to be released properly. Closing Realm instance is async.
|
Timber.d("Closing databases")
|
||||||
// After that we can safely delete the Realm files
|
sessionRealmInstance.close()
|
||||||
waitRealmRelease()
|
|
||||||
cryptoRealmInstance.close()
|
cryptoRealmInstance.close()
|
||||||
|
|
||||||
Timber.d("Cleanup: clear file system")
|
Timber.d("Cleanup: clear file system")
|
||||||
sessionFiles.deleteRecursively()
|
sessionFiles.deleteRecursively()
|
||||||
sessionCache.deleteRecursively()
|
sessionCache.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun waitRealmRelease() {
|
|
||||||
var timeToWaitMillis = MAX_TIME_TO_WAIT_MILLIS
|
|
||||||
do {
|
|
||||||
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
|
||||||
if (sessionRealmCount > 0) {
|
|
||||||
Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms for all Realm instance to be closed ($sessionRealmCount)")
|
|
||||||
delay(TIME_TO_WAIT_MILLIS)
|
|
||||||
timeToWaitMillis -= TIME_TO_WAIT_MILLIS
|
|
||||||
} else {
|
|
||||||
Timber.d("Finished waiting for all Realm instance to be closed ($sessionRealmCount)")
|
|
||||||
timeToWaitMillis = 0
|
|
||||||
}
|
|
||||||
} while (timeToWaitMillis > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MAX_TIME_TO_WAIT_MILLIS = 10_000L
|
|
||||||
private const val TIME_TO_WAIT_MILLIS = 10L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.deleteFromRealm
|
import io.realm.kotlin.MutableRealm
|
||||||
|
import io.realm.kotlin.TypedRealm
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
|
||||||
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
||||||
@ -49,7 +50,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
|
|||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
@ -95,9 +95,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
return allowedTypes.contains(eventType)
|
return allowedTypes.contains(eventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
try { // Temporary catch, should be removed
|
try { // Temporary catch, should be removed
|
||||||
val roomId = event.roomId
|
val roomId = event.roomId
|
||||||
|
event.eventId ?: return
|
||||||
if (roomId == null) {
|
if (roomId == null) {
|
||||||
Timber.w("Event has no room id ${event.eventId}")
|
Timber.w("Event has no room id ${event.eventId}")
|
||||||
return
|
return
|
||||||
@ -114,10 +115,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
|
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
|
||||||
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
|
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
|
||||||
|
|
||||||
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
|
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId).first().find()
|
||||||
?.let {
|
?.let {
|
||||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll()
|
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId).find()
|
||||||
?.forEach { tet -> tet.annotations = it }
|
.forEach { tet -> tet.annotations = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
EventType.REDACTION -> {
|
EventType.REDACTION -> {
|
||||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).first().find() }
|
||||||
?: return
|
?: return
|
||||||
when (eventToPrune.type) {
|
when (eventToPrune.type) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
@ -273,7 +274,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e
|
private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e
|
||||||
|
|
||||||
private fun handleReplace(
|
private fun handleReplace(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event,
|
event: Event,
|
||||||
content: MessageContent,
|
content: MessageContent,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@ -285,7 +286,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
val newContent = content.newContent ?: return
|
val newContent = content.newContent ?: return
|
||||||
|
|
||||||
// Check that the sender is the same
|
// Check that the sender is the same
|
||||||
val editedEvent = EventEntity.where(realm, targetEventId).findFirst()
|
val editedEvent = EventEntity.where(realm, targetEventId).first().find()
|
||||||
if (editedEvent == null) {
|
if (editedEvent == null) {
|
||||||
// We do not know yet about the edited event
|
// We do not know yet about the edited event
|
||||||
} else if (editedEvent.sender != event.senderId) {
|
} else if (editedEvent.sender != event.senderId) {
|
||||||
@ -302,16 +303,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
if (existingSummary == null) {
|
if (existingSummary == null) {
|
||||||
Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
|
Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
|
||||||
// create the edit summary
|
// create the edit summary
|
||||||
eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
|
eventAnnotationsSummaryEntity.editSummary = realm.copyToRealm(EditAggregatedSummaryEntity())
|
||||||
.also { editSummary ->
|
.also { editSummary ->
|
||||||
editSummary.editions.add(
|
editSummary.editions.add(
|
||||||
EditionOfEvent(
|
EditionOfEvent().apply {
|
||||||
senderId = event.senderId ?: "",
|
this.senderId = event.senderId ?: ""
|
||||||
eventId = event.eventId,
|
this.eventId = event.eventId
|
||||||
content = ContentMapper.map(newContent),
|
this.content = ContentMapper.map(newContent)
|
||||||
timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0,
|
this.timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0
|
||||||
isLocalEcho = isLocalEcho
|
this.isLocalEcho = isLocalEcho
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -334,18 +335,18 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
|
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
|
||||||
existingSummary.editions.add(
|
existingSummary.editions.add(
|
||||||
EditionOfEvent(
|
EditionOfEvent().apply {
|
||||||
senderId = event.senderId ?: "",
|
this.senderId = event.senderId ?: ""
|
||||||
eventId = event.eventId,
|
this.eventId = event.eventId
|
||||||
content = ContentMapper.map(newContent),
|
this.content = ContentMapper.map(newContent)
|
||||||
timestamp = if (isLocalEcho) {
|
this.timestamp = if (isLocalEcho) {
|
||||||
clock.epochMillis()
|
clock.epochMillis()
|
||||||
} else {
|
} else {
|
||||||
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
|
// Do not take local echo originServerTs here, could mess up ordering (keep old ts)
|
||||||
event.originServerTs ?: clock.epochMillis()
|
event.originServerTs ?: clock.epochMillis()
|
||||||
},
|
}
|
||||||
isLocalEcho = isLocalEcho
|
this.isLocalEcho = isLocalEcho
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,9 +358,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
if (!isLocalEcho) {
|
if (!isLocalEcho) {
|
||||||
val replaceEvent = TimelineEventEntity
|
val replaceEvent = TimelineEventEntity
|
||||||
.where(realm, roomId, eventId)
|
.where(realm, roomId, eventId)
|
||||||
.equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false)
|
.query("ownedByThreadChunk == false")
|
||||||
.findFirst()
|
.first()
|
||||||
handleThreadSummaryEdition(editedEvent, replaceEvent, existingSummary?.editions)
|
.find()
|
||||||
|
handleThreadSummaryEdition(realm, editedEvent, replaceEvent, existingSummary?.editions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,13 +372,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
* @param editions list of edition of event
|
* @param editions list of edition of event
|
||||||
*/
|
*/
|
||||||
private fun handleThreadSummaryEdition(
|
private fun handleThreadSummaryEdition(
|
||||||
|
realm: TypedRealm,
|
||||||
editedEvent: EventEntity?,
|
editedEvent: EventEntity?,
|
||||||
replaceEvent: TimelineEventEntity?,
|
replaceEvent: TimelineEventEntity?,
|
||||||
editions: List<EditionOfEvent>?
|
editions: List<EditionOfEvent>?
|
||||||
) {
|
) {
|
||||||
replaceEvent ?: return
|
replaceEvent ?: return
|
||||||
editedEvent ?: return
|
editedEvent ?: return
|
||||||
editedEvent.findRootThreadEvent()?.apply {
|
editedEvent.findRootThreadEvent(realm)?.apply {
|
||||||
val threadSummaryEventId = threadSummaryLatestMessage?.eventId
|
val threadSummaryEventId = threadSummaryLatestMessage?.eventId
|
||||||
if (editedEvent.eventId == threadSummaryEventId || editions?.any { it.eventId == threadSummaryEventId } == true) {
|
if (editedEvent.eventId == threadSummaryEventId || editions?.any { it.eventId == threadSummaryEventId } == true) {
|
||||||
// The edition is for the latest event or for any event replaced, this is to handle multiple
|
// The edition is for the latest event or for any event replaced, this is to handle multiple
|
||||||
@ -393,7 +396,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitialAggregatedRelations(
|
private fun handleInitialAggregatedRelations(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event,
|
event: Event,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
aggregation: AggregatedAnnotation
|
aggregation: AggregatedAnnotation
|
||||||
@ -402,10 +405,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
aggregation.chunk?.forEach {
|
aggregation.chunk?.forEach {
|
||||||
if (it.type == EventType.REACTION) {
|
if (it.type == EventType.REACTION) {
|
||||||
val eventId = event.eventId ?: ""
|
val eventId = event.eventId ?: ""
|
||||||
val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).first().find()
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
||||||
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
val sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
|
||||||
sum.key = it.key
|
sum.key = it.key
|
||||||
sum.firstTimestamp = event.originServerTs
|
sum.firstTimestamp = event.originServerTs
|
||||||
?: 0 // TODO how to maintain order?
|
?: 0 // TODO how to maintain order?
|
||||||
@ -420,7 +423,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReaction(
|
private fun handleReaction(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event,
|
event: Event,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
isLocalEcho: Boolean
|
isLocalEcho: Boolean
|
||||||
@ -434,7 +437,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
||||||
val reaction = content.relatesTo.key
|
val reaction = content.relatesTo.key
|
||||||
val relatedEventID = content.relatesTo.eventId
|
val relatedEventID = content.relatesTo.eventId
|
||||||
val reactionEventId = event.eventId
|
val reactionEventId = event.eventId ?: return
|
||||||
Timber.v("Reaction $reactionEventId relates to $relatedEventID")
|
Timber.v("Reaction $reactionEventId relates to $relatedEventID")
|
||||||
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventID)
|
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventID)
|
||||||
|
|
||||||
@ -442,14 +445,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
if (isLocalEcho && txId.isNullOrBlank()) {
|
if (isLocalEcho && txId.isNullOrBlank()) {
|
||||||
Timber.w("Received a local echo with no transaction ID")
|
Timber.w("Received a local echo with no transaction ID")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (sum == null) {
|
if (sum == null) {
|
||||||
sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
sum = realm.copyToRealm(ReactionAggregatedSummaryEntity())
|
||||||
sum.key = reaction
|
sum.key = reaction
|
||||||
sum.firstTimestamp = event.originServerTs ?: 0
|
sum.firstTimestamp = event.originServerTs ?: 0
|
||||||
if (isLocalEcho) {
|
if (isLocalEcho) {
|
||||||
Timber.v("Adding local echo reaction")
|
Timber.v("Adding local echo reaction")
|
||||||
sum.sourceLocalEcho.add(txId)
|
sum.sourceLocalEcho.add(txId!!)
|
||||||
sum.count = 1
|
sum.count = 1
|
||||||
} else {
|
} else {
|
||||||
Timber.v("Adding synced reaction")
|
Timber.v("Adding synced reaction")
|
||||||
@ -471,7 +475,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
sum.count += 1
|
sum.count += 1
|
||||||
if (isLocalEcho) {
|
if (isLocalEcho) {
|
||||||
Timber.v("Adding local echo reaction")
|
Timber.v("Adding local echo reaction")
|
||||||
sum.sourceLocalEcho.add(txId)
|
sum.sourceLocalEcho.add(txId!!)
|
||||||
} else {
|
} else {
|
||||||
Timber.v("Adding synced reaction")
|
Timber.v("Adding synced reaction")
|
||||||
sum.sourceEvents.add(reactionEventId)
|
sum.sourceEvents.add(reactionEventId)
|
||||||
@ -490,12 +494,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
* Called when an event is deleted.
|
* Called when an event is deleted.
|
||||||
*/
|
*/
|
||||||
private fun handleRedactionOfReplace(
|
private fun handleRedactionOfReplace(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
redacted: EventEntity,
|
redacted: EventEntity,
|
||||||
relatedEventId: String
|
relatedEventId: String
|
||||||
) {
|
) {
|
||||||
Timber.d("Handle redaction of m.replace")
|
Timber.d("Handle redaction of m.replace")
|
||||||
val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst()
|
val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).first().find()
|
||||||
if (eventSummary == null) {
|
if (eventSummary == null) {
|
||||||
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
|
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
|
||||||
return
|
return
|
||||||
@ -506,11 +510,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Need to remove this event from the edition list
|
// Need to remove this event from the edition list
|
||||||
sourceToDiscard.deleteFromRealm()
|
realm.delete(sourceToDiscard)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReactionRedact(
|
private fun handleReactionRedact(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
eventToPrune: EventEntity
|
eventToPrune: EventEntity
|
||||||
) {
|
) {
|
||||||
Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
|
Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
|
||||||
@ -520,11 +524,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
|
|
||||||
val reactionKey = reactionContent.relatesTo.key
|
val reactionKey = reactionContent.relatesTo.key
|
||||||
Timber.v("REMOVE reaction for key $reactionKey")
|
Timber.v("REMOVE reaction for key $reactionKey")
|
||||||
val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).findFirst()
|
val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).first().find()
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
summary.reactionsSummary.where()
|
summary.reactionsSummary
|
||||||
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey)
|
.firstOrNull {
|
||||||
.findFirst()?.let { aggregation ->
|
it.key == reactionKey
|
||||||
|
}?.let { aggregation ->
|
||||||
Timber.v("Find summary for key with ${aggregation.sourceEvents.size} known reactions (count:${aggregation.count})")
|
Timber.v("Find summary for key with ${aggregation.sourceEvents.size} known reactions (count:${aggregation.count})")
|
||||||
Timber.v("Known reactions ${aggregation.sourceEvents.joinToString(",")}")
|
Timber.v("Known reactions ${aggregation.sourceEvents.joinToString(",")}")
|
||||||
if (aggregation.sourceEvents.contains(eventToPrune.eventId)) {
|
if (aggregation.sourceEvents.contains(eventToPrune.eventId)) {
|
||||||
@ -538,7 +543,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
if (aggregation.count == 0) {
|
if (aggregation.count == 0) {
|
||||||
// delete!
|
// delete!
|
||||||
aggregation.deleteFromRealm()
|
realm.delete(aggregation)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known")
|
Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known")
|
||||||
@ -549,7 +554,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) {
|
private fun handleVerification(realm: MutableRealm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) {
|
||||||
|
event.eventId ?: return
|
||||||
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
|
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
|
||||||
|
|
||||||
val verifSummary = eventSummary.referencesSummaryEntity
|
val verifSummary = eventSummary.referencesSummaryEntity
|
||||||
@ -597,7 +603,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBeaconLocationData(event: Event, realm: Realm, roomId: String, isLocalEcho: Boolean) {
|
private fun handleBeaconLocationData(event: Event, realm: MutableRealm, roomId: String, isLocalEcho: Boolean) {
|
||||||
event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
|
event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
|
||||||
liveLocationAggregationProcessor.handleBeaconLocationData(
|
liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
realm,
|
realm,
|
||||||
|
@ -16,14 +16,15 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.TypedRealm
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -35,29 +36,27 @@ internal interface RoomGetter {
|
|||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultRoomGetter @Inject constructor(
|
internal class DefaultRoomGetter @Inject constructor(
|
||||||
private val realmSessionProvider: RealmSessionProvider,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val roomFactory: RoomFactory
|
private val roomFactory: RoomFactory
|
||||||
) : RoomGetter {
|
) : RoomGetter {
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
return realmSessionProvider.withRealm { realm ->
|
val realm = realmInstance.getBlockingRealm()
|
||||||
createRoom(realm, roomId)
|
return createRoom(realm, roomId)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDirectRoomWith(otherUserId: String): String? {
|
override fun getDirectRoomWith(otherUserId: String): String? {
|
||||||
return realmSessionProvider.withRealm { realm ->
|
val realm = realmInstance.getBlockingRealm()
|
||||||
RoomSummaryEntity.where(realm)
|
return RoomSummaryEntity.where(realm)
|
||||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
.query("isDirect == true")
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
.process("membershipStr", listOf(Membership.JOIN))
|
||||||
.findAll()
|
.find()
|
||||||
.firstOrNull { dm -> dm.otherMemberIds.size == 1 && dm.otherMemberIds.first(null) == otherUserId }
|
.firstOrNull { dm -> dm.otherMemberIds.size == 1 && dm.otherMemberIds.first() == otherUserId }
|
||||||
?.roomId
|
?.roomId
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoom(realm: Realm, roomId: String): Room? {
|
private fun createRoom(realm: TypedRealm, roomId: String): Room? {
|
||||||
return RoomEntity.where(realm, roomId).findFirst()
|
return RoomEntity.where(realm, roomId).first().find()
|
||||||
?.let { roomFactory.create(roomId) }
|
?.let { roomFactory.create(roomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import com.squareup.moshi.JsonClass
|
|||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import org.matrix.android.sdk.api.util.md5
|
import org.matrix.android.sdk.api.util.md5
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.get
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
@ -53,7 +54,7 @@ internal class DeactivateLiveLocationShareWorker(context: Context, params: Worke
|
|||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
@Inject lateinit var realmConfiguration: RealmConfiguration
|
@Inject lateinit var realmInstance: RealmInstance
|
||||||
|
|
||||||
override fun injectWith(injector: SessionComponent) {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
@ -74,10 +75,10 @@ internal class DeactivateLiveLocationShareWorker(context: Context, params: Worke
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deactivateLiveLocationShare(params: Params) {
|
private suspend fun deactivateLiveLocationShare(params: Params) {
|
||||||
awaitTransaction(realmConfiguration) { realm ->
|
realmInstance.write {
|
||||||
Timber.d("deactivating live with id=${params.eventId}")
|
Timber.d("deactivating live with id=${params.eventId}")
|
||||||
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
|
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
|
||||||
realm = realm,
|
realm = this,
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
eventId = params.eventId
|
eventId = params.eventId
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
|
|||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
|
import io.realm.kotlin.MutableRealm
|
||||||
|
import io.realm.kotlin.ext.realmListOf
|
||||||
import org.matrix.android.sdk.api.extensions.orTrue
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
@ -50,7 +52,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
* Handle the content of a beacon info.
|
* Handle the content of a beacon info.
|
||||||
* @return true if it has been processed, false if ignored.
|
* @return true if it has been processed, false if ignored.
|
||||||
*/
|
*/
|
||||||
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean {
|
fun handleBeaconInfo(realm: MutableRealm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean {
|
||||||
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -130,7 +132,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
* @return true if it has been processed, false if ignored.
|
* @return true if it has been processed, false if ignored.
|
||||||
*/
|
*/
|
||||||
fun handleBeaconLocationData(
|
fun handleBeaconLocationData(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event,
|
event: Event,
|
||||||
content: MessageBeaconLocationDataContent,
|
content: MessageBeaconLocationDataContent,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
@ -180,11 +182,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
|||||||
val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also {
|
val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also {
|
||||||
it.add(eventId)
|
it.add(eventId)
|
||||||
}
|
}
|
||||||
aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray())
|
aggregatedSummary.relatedEventIds = realmListOf(*updatedEventIds.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deactivateAllPreviousBeacons(
|
private fun deactivateAllPreviousBeacons(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
currentEventId: String,
|
currentEventId: String,
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room.aggregation.poll
|
package org.matrix.android.sdk.internal.session.room.aggregation.poll
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.MutableRealm
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
@ -45,7 +46,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor {
|
class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor {
|
||||||
|
|
||||||
override fun handlePollStartEvent(realm: Realm, event: Event): Boolean {
|
override fun handlePollStartEvent(realm: MutableRealm, event: Event): Boolean {
|
||||||
val content = event.getClearContent()?.toModel<MessagePollContent>()
|
val content = event.getClearContent()?.toModel<MessagePollContent>()
|
||||||
if (content?.relatesTo?.type != RelationType.REPLACE) {
|
if (content?.relatesTo?.type != RelationType.REPLACE) {
|
||||||
return false
|
return false
|
||||||
@ -74,10 +75,11 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handlePollResponseEvent(session: Session, realm: Realm, event: Event): Boolean {
|
override fun handlePollResponseEvent(session: Session, realm: MutableRealm, event: Event): Boolean {
|
||||||
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
|
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
|
||||||
val roomId = event.roomId ?: return false
|
val roomId = event.roomId ?: return false
|
||||||
val senderId = event.senderId ?: return false
|
val senderId = event.senderId ?: return false
|
||||||
|
event.eventId ?: return false
|
||||||
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
|
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
|
||||||
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
|
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId)
|
||||||
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
||||||
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
||||||
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
@ -153,12 +155,13 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean {
|
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: MutableRealm, event: Event): Boolean {
|
||||||
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
|
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
|
||||||
val roomId = event.roomId ?: return false
|
val roomId = event.roomId ?: return false
|
||||||
val pollEventId = content.relatesTo?.eventId ?: return false
|
val pollEventId = content.relatesTo?.eventId ?: return false
|
||||||
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
|
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
|
||||||
val isPollOwner = pollOwnerId == event.senderId
|
val isPollOwner = pollOwnerId == event.senderId
|
||||||
|
event.eventId ?: return false
|
||||||
|
|
||||||
if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) {
|
if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) {
|
||||||
return false
|
return false
|
||||||
@ -170,7 +173,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
|||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
aggregatedPollSummaryEntity.closedTime = event.originServerTs
|
aggregatedPollSummaryEntity.closedTime = event.originServerTs
|
||||||
|
|
||||||
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId)
|
||||||
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
||||||
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
||||||
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
@ -188,17 +191,17 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
|||||||
return pollEvent?.getLastMessageContent() as? MessagePollContent
|
return pollEvent?.getLastMessageContent() as? MessagePollContent
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAnnotationsSummaryEntity(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
|
private fun getAnnotationsSummaryEntity(realm: MutableRealm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
|
||||||
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).first().find()
|
||||||
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAggregatedPollSummaryEntity(
|
private fun getAggregatedPollSummaryEntity(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity
|
eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity
|
||||||
): PollResponseAggregatedSummaryEntity {
|
): PollResponseAggregatedSummaryEntity {
|
||||||
return eventAnnotationsSummaryEntity.pollResponseSummary
|
return eventAnnotationsSummaryEntity.pollResponseSummary
|
||||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
?: realm.copyToRealm(PollResponseAggregatedSummaryEntity()).also {
|
||||||
eventAnnotationsSummaryEntity.pollResponseSummary = it
|
eventAnnotationsSummaryEntity.pollResponseSummary = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.aggregation.poll
|
package org.matrix.android.sdk.internal.session.room.aggregation.poll
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
@ -28,7 +28,7 @@ interface PollAggregationProcessor {
|
|||||||
* Returns true if the event is aggregated.
|
* Returns true if the event is aggregated.
|
||||||
*/
|
*/
|
||||||
fun handlePollStartEvent(
|
fun handlePollStartEvent(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event
|
event: Event
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ interface PollAggregationProcessor {
|
|||||||
*/
|
*/
|
||||||
fun handlePollResponseEvent(
|
fun handlePollResponseEvent(
|
||||||
session: Session,
|
session: Session,
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event
|
event: Event
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ interface PollAggregationProcessor {
|
|||||||
fun handlePollEndEvent(
|
fun handlePollEndEvent(
|
||||||
session: Session,
|
session: Session,
|
||||||
powerLevelsHelper: PowerLevelsHelper,
|
powerLevelsHelper: PowerLevelsHelper,
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
event: Event
|
event: Event
|
||||||
): Boolean
|
): Boolean
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.create
|
package org.matrix.android.sdk.internal.session.room.create
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
|
import io.realm.kotlin.UpdatePolicy
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
@ -30,15 +31,16 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor {
|
internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor {
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
val createRoomContent = event.getClearContent().toModel<RoomCreateContent>()
|
val createRoomContent = event.getClearContent().toModel<RoomCreateContent>()
|
||||||
val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return
|
val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return
|
||||||
|
|
||||||
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
|
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).first().find()
|
||||||
?: RoomSummaryEntity(predecessorRoomId)
|
?: RoomSummaryEntity()
|
||||||
|
predecessorRoomSummary.roomId = predecessorRoomId
|
||||||
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
|
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
|
||||||
predecessorRoomSummary.isHiddenFromUser = true
|
predecessorRoomSummary.isHiddenFromUser = true
|
||||||
realm.insertOrUpdate(predecessorRoomSummary)
|
realm.copyToRealm(predecessorRoomSummary, updatePolicy = UpdatePolicy.ALL)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.location
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.deleteFromRealm
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
@ -41,12 +40,12 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
|
|||||||
return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO
|
return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) {
|
if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst()
|
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).first().find()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
|
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
|
||||||
@ -54,11 +53,11 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
|
|||||||
|
|
||||||
if (liveSummary != null) {
|
if (liveSummary != null) {
|
||||||
Timber.d("deleting live summary with id: ${liveSummary.eventId}")
|
Timber.d("deleting live summary with id: ${liveSummary.eventId}")
|
||||||
liveSummary.deleteFromRealm()
|
realm.delete(liveSummary)
|
||||||
val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId)
|
val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId)
|
||||||
if (annotationsSummary != null) {
|
if (annotationsSummary != null) {
|
||||||
Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}")
|
Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}")
|
||||||
annotationsSummary.deleteFromRealm()
|
realm.delete(annotationsSummary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.location
|
package org.matrix.android.sdk.internal.session.room.location
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.get
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
@ -36,7 +35,7 @@ internal interface RedactLiveLocationShareTask : Task<RedactLiveLocationShareTas
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultRedactLiveLocationShareTask @Inject constructor(
|
internal class DefaultRedactLiveLocationShareTask @Inject constructor(
|
||||||
@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val eventSenderProcessor: EventSenderProcessor,
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
) : RedactLiveLocationShareTask {
|
) : RedactLiveLocationShareTask {
|
||||||
@ -60,9 +59,9 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
|
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
|
||||||
return awaitTransaction(realmConfiguration) { realm ->
|
return realmInstance.write {
|
||||||
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
|
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
|
||||||
realm = realm,
|
realm = this,
|
||||||
eventId = beaconInfoEventId
|
eventId = beaconInfoEventId
|
||||||
)
|
)
|
||||||
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()
|
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()
|
||||||
|
@ -16,22 +16,20 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.membership.joining
|
package org.matrix.android.sdk.internal.session.room.membership.joining
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
|
import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
|
||||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||||
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
||||||
@ -52,9 +50,7 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
|||||||
internal class DefaultJoinRoomTask @Inject constructor(
|
internal class DefaultJoinRoomTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val readMarkersTask: SetReadMarkersTask,
|
private val readMarkersTask: SetReadMarkersTask,
|
||||||
@SessionDatabase
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
|
||||||
private val coroutineDispatcher: MatrixCoroutineDispatchers,
|
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
@ -85,16 +81,15 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
|||||||
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||||
val roomId = joinRoomResponse.roomId
|
val roomId = joinRoomResponse.roomId
|
||||||
try {
|
try {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
realm.where(RoomSummaryEntity::class.java)
|
RoomSummaryEntity.where(realm, roomId = roomId)
|
||||||
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
.process("membershipStr", listOf(Membership.JOIN))
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw JoinRoomFailure.JoinedWithTimeout
|
throw JoinRoomFailure.JoinedWithTimeout
|
||||||
}
|
}
|
||||||
awaitTransaction(realmConfiguration) {
|
realmInstance.write {
|
||||||
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
|
RoomSummaryEntity.where(this, roomId).first().find()?.lastActivityTime = clock.epochMillis()
|
||||||
}
|
}
|
||||||
setReadMarkers(roomId)
|
setReadMarkers(roomId)
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.prune
|
package org.matrix.android.sdk.internal.session.room.prune
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.deleteFromRealm
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
@ -47,22 +46,22 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||||||
return eventType == EventType.REDACTION
|
return eventType == EventType.REDACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
pruneEvent(realm, event)
|
pruneEvent(realm, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pruneEvent(realm: Realm, redactionEvent: Event) {
|
private fun pruneEvent(realm: MutableRealm, redactionEvent: Event) {
|
||||||
if (redactionEvent.redacts.isNullOrBlank()) {
|
if (redactionEvent.redacts.isNullOrBlank()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that we know this event
|
// Check that we know this event
|
||||||
EventEntity.where(realm, eventId = redactionEvent.eventId ?: "").findFirst() ?: return
|
EventEntity.where(realm, eventId = redactionEvent.eventId ?: "").first().find() ?: return
|
||||||
|
|
||||||
val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "")
|
val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "")
|
||||||
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
|
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
|
||||||
|
|
||||||
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
|
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).first().find()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val typeToPrune = eventToPrune.type
|
val typeToPrune = eventToPrune.type
|
||||||
@ -117,13 +116,13 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||||||
* with respect to redactions.
|
* with respect to redactions.
|
||||||
*/
|
*/
|
||||||
private fun handleTimelineThreadSummaryIfNeeded(
|
private fun handleTimelineThreadSummaryIfNeeded(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
eventToPrune: EventEntity,
|
eventToPrune: EventEntity,
|
||||||
isLocalEcho: Boolean,
|
isLocalEcho: Boolean,
|
||||||
) {
|
) {
|
||||||
if (eventToPrune.isThread() && !isLocalEcho) {
|
if (eventToPrune.isThread() && !isLocalEcho) {
|
||||||
val roomId = eventToPrune.roomId
|
val roomId = eventToPrune.roomId
|
||||||
val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return
|
val rootThreadEvent = eventToPrune.findRootThreadEvent(realm) ?: return
|
||||||
val rootThreadEventId = eventToPrune.rootThreadEventId ?: return
|
val rootThreadEventId = eventToPrune.rootThreadEventId ?: return
|
||||||
|
|
||||||
val inThreadMessages = countInThreadMessages(
|
val inThreadMessages = countInThreadMessages(
|
||||||
@ -139,8 +138,10 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
|||||||
rootThreadEvent.threadSummaryLatestMessage = null
|
rootThreadEvent.threadSummaryLatestMessage = null
|
||||||
ThreadSummaryEntity
|
ThreadSummaryEntity
|
||||||
.where(realm, roomId = roomId, rootThreadEventId)
|
.where(realm, roomId = roomId, rootThreadEventId)
|
||||||
.findFirst()
|
.first()
|
||||||
?.deleteFromRealm()
|
.find()?.let {
|
||||||
|
realm.delete(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.read
|
package org.matrix.android.sdk.internal.session.room.read
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.Realm
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.internal.database.RealmInstance
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
@ -34,7 +33,6 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
|
|||||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
|
||||||
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -65,9 +63,10 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
|
|||||||
) : SetReadMarkersTask {
|
) : SetReadMarkersTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SetReadMarkersTask.Params) {
|
override suspend fun execute(params: SetReadMarkersTask.Params) {
|
||||||
|
val realm = realmInstance.getRealm()
|
||||||
val markers = mutableMapOf<String, String>()
|
val markers = mutableMapOf<String, String>()
|
||||||
Timber.v("Execute set read marker with params: $params")
|
Timber.v("Execute set read marker with params: $params")
|
||||||
val latestSyncedEventId = latestSyncedEventId(params.roomId)
|
val latestSyncedEventId = latestSyncedEventId(realm, params.roomId)
|
||||||
val fullyReadEventId = if (params.forceReadMarker) {
|
val fullyReadEventId = if (params.forceReadMarker) {
|
||||||
latestSyncedEventId
|
latestSyncedEventId
|
||||||
} else {
|
} else {
|
||||||
@ -79,7 +78,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
|
|||||||
params.readReceiptEventId
|
params.readReceiptEventId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy.realmConfiguration, params.roomId, fullyReadEventId)) {
|
if (fullyReadEventId != null && !isReadMarkerMoreRecent(realm, params.roomId, fullyReadEventId)) {
|
||||||
if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
|
if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
|
||||||
Timber.w("Can't set read marker for local event $fullyReadEventId")
|
Timber.w("Can't set read marker for local event $fullyReadEventId")
|
||||||
} else {
|
} else {
|
||||||
@ -87,7 +86,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readReceiptEventId != null &&
|
if (readReceiptEventId != null &&
|
||||||
!isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId)) {
|
!isEventRead(realm, userId, params.roomId, readReceiptEventId)) {
|
||||||
if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
|
if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
|
||||||
Timber.w("Can't set read receipt for local event $readReceiptEventId")
|
Timber.w("Can't set read receipt for local event $readReceiptEventId")
|
||||||
} else {
|
} else {
|
||||||
@ -116,25 +115,23 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun latestSyncedEventId(roomId: String): String? =
|
private suspend fun latestSyncedEventId(realm: TypedRealm, roomId: String): String? =
|
||||||
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
|
||||||
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
|
private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
|
||||||
monarchy.awaitTransaction { realm ->
|
realmInstance.write {
|
||||||
val readMarkerId = markers[READ_MARKER]
|
val readMarkerId = markers[READ_MARKER]
|
||||||
val readReceiptId = markers[READ_RECEIPT]
|
val readReceiptId = markers[READ_RECEIPT]
|
||||||
if (readMarkerId != null) {
|
if (readMarkerId != null) {
|
||||||
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
|
roomFullyReadHandler.handle(this, roomId, FullyReadContent(readMarkerId))
|
||||||
}
|
}
|
||||||
if (readReceiptId != null) {
|
if (readReceiptId != null) {
|
||||||
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
|
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
|
||||||
readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
|
readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
|
||||||
}
|
}
|
||||||
if (shouldUpdateRoomSummary) {
|
if (shouldUpdateRoomSummary) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(this, roomId).first().find()
|
||||||
?: return@awaitTransaction
|
?: return@write
|
||||||
roomSummary.notificationCount = 0
|
roomSummary.notificationCount = 0
|
||||||
roomSummary.highlightCount = 0
|
roomSummary.highlightCount = 0
|
||||||
roomSummary.hasUnreadMessages = false
|
roomSummary.hasUnreadMessages = false
|
||||||
|
@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService
|
|||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
|
import org.matrix.android.sdk.internal.util.toMatrixErrorStr
|
||||||
@ -55,7 +56,6 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
|
|||||||
@Inject lateinit var sendEventTask: SendEventTask
|
@Inject lateinit var sendEventTask: SendEventTask
|
||||||
@Inject lateinit var cryptoService: CryptoService
|
@Inject lateinit var cryptoService: CryptoService
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
|
||||||
|
|
||||||
override fun injectWith(injector: SessionComponent) {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room.threads.local
|
package org.matrix.android.sdk.internal.session.room.threads.local
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import androidx.lifecycle.asLiveData
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.realm.Realm
|
|
||||||
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
|
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId
|
import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId
|
||||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||||
import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread
|
import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread
|
||||||
@ -35,12 +35,11 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
|
||||||
|
|
||||||
internal class DefaultThreadsLocalService @AssistedInject constructor(
|
internal class DefaultThreadsLocalService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
) : ThreadsLocalService {
|
) : ThreadsLocalService {
|
||||||
|
|
||||||
@ -50,56 +49,52 @@ internal class DefaultThreadsLocalService @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
|
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return realmInstance.queryList(timelineEventMapper::map) {
|
||||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId)
|
||||||
{ timelineEventMapper.map(it) }
|
}.asLiveData()
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMarkedThreadNotifications(): List<TimelineEvent> {
|
override fun getMarkedThreadNotifications(): List<TimelineEvent> {
|
||||||
return monarchy.fetchAllMappedSync(
|
val realm = realmInstance.getBlockingRealm()
|
||||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
return TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(realm, roomId = roomId)
|
||||||
{ timelineEventMapper.map(it) }
|
.find()
|
||||||
)
|
.map(timelineEventMapper::map)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
|
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return realmInstance.queryList(timelineEventMapper::map) {
|
||||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId)
|
||||||
{ timelineEventMapper.map(it) }
|
}.asLiveData()
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllThreads(): List<TimelineEvent> {
|
override fun getAllThreads(): List<TimelineEvent> {
|
||||||
return monarchy.fetchAllMappedSync(
|
val realm = realmInstance.getBlockingRealm()
|
||||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
return TimelineEventEntity.findAllThreadsForRoomId(realm, roomId = roomId)
|
||||||
{ timelineEventMapper.map(it) }
|
.find()
|
||||||
)
|
.map(timelineEventMapper::map)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
|
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
|
||||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
val realm = realmInstance.getBlockingRealm()
|
||||||
TimelineEventEntity.isUserParticipatingInThread(
|
return TimelineEventEntity.isUserParticipatingInThread(
|
||||||
realm = it,
|
realm = realm,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
rootThreadEventId = rootThreadEventId,
|
rootThreadEventId = rootThreadEventId,
|
||||||
senderId = userId
|
senderId = userId
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
|
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
|
||||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
val realm = realmInstance.getBlockingRealm()
|
||||||
threads.mapEventsWithEdition(it, roomId)
|
return threads.mapEventsWithEdition(realm, roomId)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun markThreadAsRead(rootThreadEventId: String) {
|
override suspend fun markThreadAsRead(rootThreadEventId: String) {
|
||||||
monarchy.awaitTransaction {
|
realmInstance.write {
|
||||||
EventEntity.where(
|
EventEntity.where(
|
||||||
realm = it,
|
realm = this,
|
||||||
eventId = rootThreadEventId
|
eventId = rootThreadEventId
|
||||||
).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
|
).first().find()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
|
|||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.kotlin.TypedRealm
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||||
@ -57,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference
|
|||||||
internal class DefaultTimeline(
|
internal class DefaultTimeline(
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val initialEventId: String?,
|
private val initialEventId: String?,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmInstance: RealmInstance,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val readReceiptHandler: ReadReceiptHandler,
|
private val readReceiptHandler: ReadReceiptHandler,
|
||||||
private val settings: TimelineSettings,
|
private val settings: TimelineSettings,
|
||||||
@ -86,7 +88,6 @@ internal class DefaultTimeline(
|
|||||||
private val forwardState = AtomicReference(Timeline.PaginationState())
|
private val forwardState = AtomicReference(Timeline.PaginationState())
|
||||||
private val backwardState = AtomicReference(Timeline.PaginationState())
|
private val backwardState = AtomicReference(Timeline.PaginationState())
|
||||||
|
|
||||||
private val backgroundRealm = AtomicReference<Realm>()
|
|
||||||
private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher()
|
private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher()
|
||||||
private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher)
|
private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher)
|
||||||
private val sequencer = SemaphoreCoroutineSequencer()
|
private val sequencer = SemaphoreCoroutineSequencer()
|
||||||
@ -96,11 +97,11 @@ internal class DefaultTimeline(
|
|||||||
private var rootThreadEventId: String? = null
|
private var rootThreadEventId: String? = null
|
||||||
|
|
||||||
private val strategyDependencies = LoadTimelineStrategy.Dependencies(
|
private val strategyDependencies = LoadTimelineStrategy.Dependencies(
|
||||||
|
timelineScope = timelineScope,
|
||||||
timelineSettings = settings,
|
timelineSettings = settings,
|
||||||
realm = backgroundRealm,
|
realmInstance = realmInstance,
|
||||||
eventDecryptor = eventDecryptor,
|
eventDecryptor = eventDecryptor,
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
realmConfiguration = realmConfiguration,
|
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
||||||
getContextOfEventTask = getEventTask,
|
getContextOfEventTask = getEventTask,
|
||||||
@ -151,9 +152,7 @@ internal class DefaultTimeline(
|
|||||||
isFromThreadTimeline = rootThreadEventId != null
|
isFromThreadTimeline = rootThreadEventId != null
|
||||||
this@DefaultTimeline.rootThreadEventId = rootThreadEventId
|
this@DefaultTimeline.rootThreadEventId = rootThreadEventId
|
||||||
// /
|
// /
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
ensureReadReceiptAreLoaded()
|
||||||
ensureReadReceiptAreLoaded(realm)
|
|
||||||
backgroundRealm.set(realm)
|
|
||||||
listenToPostSnapshotSignals()
|
listenToPostSnapshotSignals()
|
||||||
openAround(initialEventId, rootThreadEventId)
|
openAround(initialEventId, rootThreadEventId)
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
@ -168,7 +167,6 @@ internal class DefaultTimeline(
|
|||||||
sequencer.post {
|
sequencer.post {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
strategy.onStop()
|
strategy.onStop()
|
||||||
backgroundRealm.get().closeQuietly()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,14 +406,14 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureReadReceiptAreLoaded(realm: Realm) {
|
private fun ensureReadReceiptAreLoaded() {
|
||||||
readReceiptHandler.getContentFromInitSync(roomId)
|
readReceiptHandler.getContentFromInitSync(roomId)
|
||||||
?.also {
|
?.also {
|
||||||
Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
|
Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
|
||||||
}
|
}
|
||||||
?.let { readReceiptContent ->
|
?.let { readReceiptContent ->
|
||||||
realm.executeTransactionAsync {
|
realmInstance.asyncWrite {
|
||||||
readReceiptHandler.handle(it, roomId, readReceiptContent, false, null)
|
readReceiptHandler.handle(this, roomId, readReceiptContent, false, null)
|
||||||
readReceiptHandler.onContentFromInitSyncHandled(roomId)
|
readReceiptHandler.onContentFromInitSyncHandled(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||||
@ -39,7 +40,7 @@ import org.matrix.android.sdk.internal.util.time.Clock
|
|||||||
|
|
||||||
internal class DefaultTimelineService @AssistedInject constructor(
|
internal class DefaultTimelineService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val timelineInput: TimelineInput,
|
private val timelineInput: TimelineInput,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val eventDecryptor: TimelineEventDecryptor,
|
private val eventDecryptor: TimelineEventDecryptor,
|
||||||
@ -67,7 +68,7 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
|||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
initialEventId = eventId,
|
initialEventId = eventId,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
realmConfiguration = monarchy.realmConfiguration,
|
realmInstance = realmInstance,
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
|
@ -16,15 +16,16 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
import io.realm.kotlin.ext.isValid
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.notifications.InitialResults
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.notifications.ResultsChange
|
||||||
import io.realm.RealmResults
|
import io.realm.kotlin.notifications.UpdatedResults
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.query.RealmResults
|
||||||
import io.realm.kotlin.executeTransactionAwait
|
|
||||||
import io.realm.kotlin.isValid
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
@ -34,10 +35,10 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.helper.addIfNecessary
|
import org.matrix.android.sdk.internal.database.helper.addIfNecessary
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.deleteAndClearThreadEvents
|
import org.matrix.android.sdk.internal.database.model.deleteAndClearThreadEvents
|
||||||
import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
|
import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
|
||||||
@ -48,7 +49,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
|||||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy.
|
* This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy.
|
||||||
@ -89,11 +89,11 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Dependencies(
|
data class Dependencies(
|
||||||
|
val timelineScope: CoroutineScope,
|
||||||
val timelineSettings: TimelineSettings,
|
val timelineSettings: TimelineSettings,
|
||||||
val realm: AtomicReference<Realm>,
|
val realmInstance: RealmInstance,
|
||||||
val eventDecryptor: TimelineEventDecryptor,
|
val eventDecryptor: TimelineEventDecryptor,
|
||||||
val paginationTask: PaginationTask,
|
val paginationTask: PaginationTask,
|
||||||
val realmConfiguration: RealmConfiguration,
|
|
||||||
val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||||
val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
val getContextOfEventTask: GetContextOfEventTask,
|
val getContextOfEventTask: GetContextOfEventTask,
|
||||||
@ -112,20 +112,27 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
private var getContextLatch: CompletableDeferred<Unit>? = null
|
private var getContextLatch: CompletableDeferred<Unit>? = null
|
||||||
private var chunkEntity: RealmResults<ChunkEntity>? = null
|
private var chunkEntity: RealmResults<ChunkEntity>? = null
|
||||||
private var timelineChunk: TimelineChunk? = null
|
private var timelineChunk: TimelineChunk? = null
|
||||||
private val chunkEntityListener = OrderedRealmCollectionChangeListener { _: RealmResults<ChunkEntity>, changeSet: OrderedCollectionChangeSet ->
|
|
||||||
// Can be call either when you open a permalink on an unknown event
|
private suspend fun onChunkResultsChanged(resultsChange: ResultsChange<ChunkEntity>) {
|
||||||
// or when there is a gap in the timeline.
|
|
||||||
val shouldRebuildChunk = changeSet.insertions.isNotEmpty()
|
suspend fun onUpdates(updatedResults: UpdatedResults<ChunkEntity>) {
|
||||||
if (shouldRebuildChunk) {
|
val shouldRebuildChunk = updatedResults.insertions.isNotEmpty()
|
||||||
timelineChunk?.close(closeNext = true, closePrev = true)
|
if (shouldRebuildChunk) {
|
||||||
timelineChunk = chunkEntity?.createTimelineChunk()
|
timelineChunk?.close(closeNext = true, closePrev = true)
|
||||||
// If we are waiting for a result of get context, post completion
|
timelineChunk = chunkEntity?.createTimelineChunk()
|
||||||
getContextLatch?.complete(Unit)
|
// If we are waiting for a result of get context, post completion
|
||||||
// If we have a gap, just tell the timeline about it.
|
getContextLatch?.complete(Unit)
|
||||||
if (timelineChunk?.hasReachedLastForward().orFalse()) {
|
// If we have a gap, just tell the timeline about it.
|
||||||
dependencies.onLimitedTimeline()
|
if (timelineChunk?.hasReachedLastForward().orFalse()) {
|
||||||
|
dependencies.onLimitedTimeline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (resultsChange) {
|
||||||
|
is InitialResults -> Unit
|
||||||
|
is UpdatedResults -> onUpdates(resultsChange)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uiEchoManagerListener = object : UIEchoManager.Listener {
|
private val uiEchoManagerListener = object : UIEchoManager.Listener {
|
||||||
@ -165,7 +172,8 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
|
private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
|
||||||
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
|
private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
realm = dependencies.realm,
|
timelineScope = dependencies.timelineScope,
|
||||||
|
realmInstance = dependencies.realmInstance,
|
||||||
uiEchoManager = uiEchoManager,
|
uiEchoManager = uiEchoManager,
|
||||||
timelineEventMapper = dependencies.timelineEventMapper,
|
timelineEventMapper = dependencies.timelineEventMapper,
|
||||||
onEventsUpdated = dependencies.onEventsUpdated
|
onEventsUpdated = dependencies.onEventsUpdated
|
||||||
@ -180,10 +188,11 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
suspend fun onStart() {
|
suspend fun onStart() {
|
||||||
dependencies.eventDecryptor.start()
|
dependencies.eventDecryptor.start()
|
||||||
dependencies.timelineInput.listeners.add(timelineInputListener)
|
dependencies.timelineInput.listeners.add(timelineInputListener)
|
||||||
val realm = dependencies.realm.get()
|
val realm = dependencies.realmInstance.getRealm()
|
||||||
sendingEventsDataSource.start()
|
|
||||||
chunkEntity = getChunkEntity(realm).also {
|
chunkEntity = getChunkEntity(realm).also {
|
||||||
it.addChangeListener(chunkEntityListener)
|
it.asFlow().onEach {
|
||||||
|
|
||||||
|
}.launchIn(dependencies.timelineScope)
|
||||||
timelineChunk = it.createTimelineChunk()
|
timelineChunk = it.createTimelineChunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,14 +204,12 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
suspend fun onStop() {
|
suspend fun onStop() {
|
||||||
dependencies.eventDecryptor.destroy()
|
dependencies.eventDecryptor.destroy()
|
||||||
dependencies.timelineInput.listeners.remove(timelineInputListener)
|
dependencies.timelineInput.listeners.remove(timelineInputListener)
|
||||||
chunkEntity?.removeChangeListener(chunkEntityListener)
|
|
||||||
sendingEventsDataSource.stop()
|
|
||||||
timelineChunk?.close(closeNext = true, closePrev = true)
|
timelineChunk?.close(closeNext = true, closePrev = true)
|
||||||
getContextLatch?.cancel()
|
getContextLatch?.cancel()
|
||||||
chunkEntity = null
|
chunkEntity = null
|
||||||
timelineChunk = null
|
timelineChunk = null
|
||||||
if (mode is Mode.Thread) {
|
if (mode is Mode.Thread) {
|
||||||
clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId)
|
clearThreadChunkEntity(mode.rootThreadEventId)
|
||||||
}
|
}
|
||||||
if (dependencies.timelineSettings.useLiveSenderInfo) {
|
if (dependencies.timelineSettings.useLiveSenderInfo) {
|
||||||
liveRoomStateListener.stop()
|
liveRoomStateListener.stop()
|
||||||
@ -267,22 +274,22 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> {
|
private suspend fun getChunkEntity(realm: TypedRealm): RealmResults<ChunkEntity> {
|
||||||
return when (mode) {
|
return when (mode) {
|
||||||
is Mode.Live -> {
|
is Mode.Live -> {
|
||||||
ChunkEntity.where(realm, roomId)
|
ChunkEntity.where(realm, roomId)
|
||||||
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
|
.query("isLastForward == true")
|
||||||
.findAll()
|
.find()
|
||||||
}
|
}
|
||||||
is Mode.Permalink -> {
|
is Mode.Permalink -> {
|
||||||
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
|
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
|
||||||
}
|
}
|
||||||
is Mode.Thread -> {
|
is Mode.Thread -> {
|
||||||
recreateThreadChunkEntity(realm, mode.rootThreadEventId)
|
recreateThreadChunkEntity(mode.rootThreadEventId)
|
||||||
ChunkEntity.where(realm, roomId)
|
ChunkEntity.where(realm, roomId)
|
||||||
.equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, mode.rootThreadEventId)
|
.query("rootThreadEventId == $0", mode.rootThreadEventId)
|
||||||
.equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
|
.query("isLastForwardThread == true")
|
||||||
.findAll()
|
.find()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,19 +298,21 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
* Clear any existing thread chunk entity and create a new one, with the
|
* Clear any existing thread chunk entity and create a new one, with the
|
||||||
* rootThreadEventId included.
|
* rootThreadEventId included.
|
||||||
*/
|
*/
|
||||||
private suspend fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) {
|
private suspend fun recreateThreadChunkEntity(rootThreadEventId: String) {
|
||||||
realm.executeTransactionAwait {
|
dependencies.realmInstance.write {
|
||||||
// Lets delete the chunk and start a new one
|
// Lets delete the chunk and start a new one
|
||||||
ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let {
|
ChunkEntity.findLastForwardChunkOfThread(this, roomId, rootThreadEventId)?.let {
|
||||||
|
deleteAndClearThreadEvents(it)
|
||||||
Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..")
|
Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..")
|
||||||
}
|
}
|
||||||
val threadChunk = it.createObject<ChunkEntity>().apply {
|
val threadChunk = copyToRealm(ChunkEntity().apply {
|
||||||
Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId")
|
Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId")
|
||||||
this.rootThreadEventId = rootThreadEventId
|
this.rootThreadEventId = rootThreadEventId
|
||||||
this.isLastForwardThread = true
|
this.isLastForwardThread = true
|
||||||
}
|
}
|
||||||
|
)
|
||||||
if (threadChunk.isValid()) {
|
if (threadChunk.isValid()) {
|
||||||
RoomEntity.where(it, roomId).findFirst()?.addIfNecessary(threadChunk)
|
RoomEntity.where(this, roomId).first().find()?.addIfNecessary(threadChunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,9 +320,10 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
/**
|
/**
|
||||||
* Clear any existing thread chunk.
|
* Clear any existing thread chunk.
|
||||||
*/
|
*/
|
||||||
private suspend fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) {
|
private suspend fun clearThreadChunkEntity(rootThreadEventId: String) {
|
||||||
realm.executeTransactionAwait {
|
dependencies.realmInstance.write {
|
||||||
ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let {
|
ChunkEntity.findLastForwardChunkOfThread(this, roomId, rootThreadEventId)?.let {
|
||||||
|
deleteAndClearThreadEvents(it)
|
||||||
Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..")
|
Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,9 +333,11 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
return timelineChunk?.hasReachedLastForward().orFalse()
|
return timelineChunk?.hasReachedLastForward().orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmResults<ChunkEntity>.createTimelineChunk(): TimelineChunk? {
|
private suspend fun RealmResults<ChunkEntity>.createTimelineChunk(): TimelineChunk? {
|
||||||
|
val realm = dependencies.realmInstance.getRealm()
|
||||||
return firstOrNull()?.let {
|
return firstOrNull()?.let {
|
||||||
return TimelineChunk(
|
return TimelineChunk(
|
||||||
|
timelineScope = dependencies.timelineScope,
|
||||||
chunkEntity = it,
|
chunkEntity = it,
|
||||||
timelineSettings = dependencies.timelineSettings,
|
timelineSettings = dependencies.timelineSettings,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
@ -333,7 +345,7 @@ internal class LoadTimelineStrategy constructor(
|
|||||||
fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask,
|
fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask,
|
||||||
eventDecryptor = dependencies.eventDecryptor,
|
eventDecryptor = dependencies.eventDecryptor,
|
||||||
paginationTask = dependencies.paginationTask,
|
paginationTask = dependencies.paginationTask,
|
||||||
realmConfiguration = dependencies.realmConfiguration,
|
realm = realm,
|
||||||
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask,
|
||||||
timelineEventMapper = dependencies.timelineEventMapper,
|
timelineEventMapper = dependencies.timelineEventMapper,
|
||||||
uiEchoManager = uiEchoManager,
|
uiEchoManager = uiEchoManager,
|
||||||
|
@ -16,78 +16,59 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import io.realm.Realm
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import io.realm.RealmChangeListener
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import io.realm.RealmList
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
internal interface SendingEventsDataSource {
|
internal interface SendingEventsDataSource {
|
||||||
fun start()
|
|
||||||
fun stop()
|
|
||||||
fun buildSendingEvents(): List<TimelineEvent>
|
fun buildSendingEvents(): List<TimelineEvent>
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class RealmSendingEventsDataSource(
|
internal class RealmSendingEventsDataSource(
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val realm: AtomicReference<Realm>,
|
private val realmInstance: RealmInstance,
|
||||||
|
private val timelineScope: CoroutineScope,
|
||||||
private val uiEchoManager: UIEchoManager,
|
private val uiEchoManager: UIEchoManager,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val onEventsUpdated: (Boolean) -> Unit
|
private val onEventsUpdated: (Boolean) -> Unit
|
||||||
) : SendingEventsDataSource {
|
) : SendingEventsDataSource {
|
||||||
|
|
||||||
private var roomEntity: RoomEntity? = null
|
private var sendingTimelineEvents: List<TimelineEventEntity>? = null
|
||||||
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
|
||||||
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
|
|
||||||
|
|
||||||
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
|
init {
|
||||||
if (events.isValid) {
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun start() {
|
||||||
|
realmInstance.getRealmFlow().flatMapConcat { realm ->
|
||||||
|
RoomEntity.where(realm, roomId = roomId).first().asFlow()
|
||||||
|
}.onEach { change ->
|
||||||
|
val events = change.obj?.sendingTimelineEvents.orEmpty()
|
||||||
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
|
||||||
updateFrozenResults(events)
|
sendingTimelineEvents = events
|
||||||
onEventsUpdated(false)
|
onEventsUpdated(false)
|
||||||
}
|
}.launchIn(timelineScope)
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
val safeRealm = realm.get()
|
|
||||||
roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst()
|
|
||||||
sendingTimelineEvents = roomEntity?.sendingTimelineEvents
|
|
||||||
sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener)
|
|
||||||
updateFrozenResults(sendingTimelineEvents)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener)
|
|
||||||
updateFrozenResults(null)
|
|
||||||
sendingTimelineEvents = null
|
|
||||||
roomEntity = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFrozenResults(sendingEvents: RealmList<TimelineEventEntity>?) {
|
|
||||||
// Makes sure to close the previous frozen realm
|
|
||||||
if (frozenSendingTimelineEvents?.isValid == true) {
|
|
||||||
frozenSendingTimelineEvents?.realm?.close()
|
|
||||||
}
|
|
||||||
frozenSendingTimelineEvents = sendingEvents?.freeze()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildSendingEvents(): List<TimelineEvent> {
|
override fun buildSendingEvents(): List<TimelineEvent> {
|
||||||
val builtSendingEvents = mutableListOf<TimelineEvent>()
|
val builtSendingEvents = mutableListOf<TimelineEvent>()
|
||||||
uiEchoManager.getInMemorySendingEvents()
|
uiEchoManager.getInMemorySendingEvents()
|
||||||
.addWithUiEcho(builtSendingEvents)
|
.addWithUiEcho(builtSendingEvents)
|
||||||
if (frozenSendingTimelineEvents?.isValid == true) {
|
|
||||||
frozenSendingTimelineEvents
|
sendingTimelineEvents
|
||||||
?.filter { timelineEvent ->
|
?.filter { timelineEvent ->
|
||||||
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
|
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
|
||||||
}
|
}
|
||||||
?.map {
|
?.map {
|
||||||
timelineEventMapper.map(it)
|
timelineEventMapper.map(it)
|
||||||
}?.addWithUiEcho(builtSendingEvents)
|
}?.addWithUiEcho(builtSendingEvents)
|
||||||
}
|
|
||||||
|
|
||||||
return builtSendingEvents
|
return builtSendingEvents
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,23 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.kotlin.TypedRealm
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
import io.realm.kotlin.ext.asFlow
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.kotlin.notifications.DeletedObject
|
||||||
import io.realm.RealmObjectChangeListener
|
import io.realm.kotlin.notifications.InitialObject
|
||||||
import io.realm.RealmQuery
|
import io.realm.kotlin.notifications.InitialResults
|
||||||
import io.realm.RealmResults
|
import io.realm.kotlin.notifications.ListChangeSet
|
||||||
import io.realm.Sort
|
import io.realm.kotlin.notifications.ObjectChange
|
||||||
import io.realm.kotlin.addChangeListener
|
import io.realm.kotlin.notifications.ResultsChange
|
||||||
import io.realm.kotlin.removeChangeListener
|
import io.realm.kotlin.notifications.UpdatedObject
|
||||||
|
import io.realm.kotlin.notifications.UpdatedResults
|
||||||
|
import io.realm.kotlin.query.RealmQuery
|
||||||
|
import io.realm.kotlin.query.RealmResults
|
||||||
|
import io.realm.kotlin.query.Sort
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
@ -39,6 +46,7 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
|||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.query.whereChunkId
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||||
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
|
||||||
@ -52,6 +60,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
* It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
|
* It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
|
||||||
*/
|
*/
|
||||||
internal class TimelineChunk(
|
internal class TimelineChunk(
|
||||||
|
private val timelineScope: CoroutineScope,
|
||||||
private val chunkEntity: ChunkEntity,
|
private val chunkEntity: ChunkEntity,
|
||||||
private val timelineSettings: TimelineSettings,
|
private val timelineSettings: TimelineSettings,
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
@ -59,7 +68,7 @@ internal class TimelineChunk(
|
|||||||
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||||
private val eventDecryptor: TimelineEventDecryptor,
|
private val eventDecryptor: TimelineEventDecryptor,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realm: TypedRealm,
|
||||||
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val uiEchoManager: UIEchoManager?,
|
private val uiEchoManager: UIEchoManager?,
|
||||||
@ -76,39 +85,8 @@ internal class TimelineChunk(
|
|||||||
private var prevChunkLatch: CompletableDeferred<Unit>? = null
|
private var prevChunkLatch: CompletableDeferred<Unit>? = null
|
||||||
private var nextChunkLatch: CompletableDeferred<Unit>? = null
|
private var nextChunkLatch: CompletableDeferred<Unit>? = null
|
||||||
|
|
||||||
private val chunkObjectListener = RealmObjectChangeListener<ChunkEntity> { _, changeSet ->
|
private var timelineEventEntities: RealmResults<TimelineEventEntity> =
|
||||||
if (changeSet == null) return@RealmObjectChangeListener
|
chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId).find()
|
||||||
if (changeSet.isDeleted.orFalse()) {
|
|
||||||
return@RealmObjectChangeListener
|
|
||||||
}
|
|
||||||
Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet.changedFields?.joinToString(",")}")
|
|
||||||
if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) {
|
|
||||||
isLastForward.set(chunkEntity.isLastForward)
|
|
||||||
}
|
|
||||||
if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_BACKWARD)) {
|
|
||||||
isLastBackward.set(chunkEntity.isLastBackward)
|
|
||||||
}
|
|
||||||
if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) {
|
|
||||||
nextChunk = createTimelineChunk(chunkEntity.nextChunk).also {
|
|
||||||
it?.prevChunk = this
|
|
||||||
}
|
|
||||||
nextChunkLatch?.complete(Unit)
|
|
||||||
}
|
|
||||||
if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) {
|
|
||||||
prevChunk = createTimelineChunk(chunkEntity.prevChunk).also {
|
|
||||||
it?.nextChunk = this
|
|
||||||
}
|
|
||||||
prevChunkLatch?.complete(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val timelineEventsChangeListener =
|
|
||||||
OrderedRealmCollectionChangeListener { results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet ->
|
|
||||||
Timber.v("on timeline events chunk update")
|
|
||||||
handleDatabaseChangeSet(results, changeSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timelineEventEntities: RealmResults<TimelineEventEntity> = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId)
|
|
||||||
private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList())
|
private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList())
|
||||||
private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>())
|
private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>())
|
||||||
|
|
||||||
@ -116,8 +94,13 @@ internal class TimelineChunk(
|
|||||||
private var prevChunk: TimelineChunk? = null
|
private var prevChunk: TimelineChunk? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
timelineEventEntities.addChangeListener(timelineEventsChangeListener)
|
timelineEventEntities.asFlow()
|
||||||
chunkEntity.addChangeListener(chunkObjectListener)
|
.onEach(::handleDatabaseChangeSet)
|
||||||
|
.launchIn(timelineScope)
|
||||||
|
|
||||||
|
chunkEntity.asFlow()
|
||||||
|
.onEach(::handleChunkObjectChange)
|
||||||
|
.launchIn(timelineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasReachedLastForward(): Boolean {
|
fun hasReachedLastForward(): Boolean {
|
||||||
@ -326,8 +309,6 @@ internal class TimelineChunk(
|
|||||||
nextChunkLatch?.cancel()
|
nextChunkLatch?.cancel()
|
||||||
prevChunk = null
|
prevChunk = null
|
||||||
prevChunkLatch?.cancel()
|
prevChunkLatch?.cancel()
|
||||||
chunkEntity.removeChangeListener(chunkObjectListener)
|
|
||||||
timelineEventEntities.removeChangeListener(timelineEventsChangeListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -337,12 +318,11 @@ internal class TimelineChunk(
|
|||||||
*/
|
*/
|
||||||
private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
|
private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
|
||||||
val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
|
val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
|
||||||
val baseQuery = timelineEventEntities.where()
|
val baseQuery = chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId)
|
||||||
|
|
||||||
val timelineEvents = baseQuery
|
val timelineEvents = baseQuery
|
||||||
.offsets(direction, count, displayIndex)
|
.offsets(direction, count, displayIndex)
|
||||||
.findAll()
|
.find()
|
||||||
.orEmpty()
|
|
||||||
|
|
||||||
if (timelineEvents.isEmpty()) return LoadedFromStorage()
|
if (timelineEvents.isEmpty()) return LoadedFromStorage()
|
||||||
// Disabled due to the new fallback
|
// Disabled due to the new fallback
|
||||||
@ -434,7 +414,7 @@ internal class TimelineChunk(
|
|||||||
val loadMoreResult = try {
|
val loadMoreResult = try {
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END
|
if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END
|
||||||
val lastKnownEventId = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId).firstOrNull()?.eventId
|
val lastKnownEventId = chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId).first().find()?.eventId
|
||||||
?: return LoadMoreResult.FAILURE
|
?: return LoadMoreResult.FAILURE
|
||||||
val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
|
val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
|
||||||
fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
|
fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
|
||||||
@ -485,64 +465,104 @@ internal class TimelineChunk(
|
|||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleChunkObjectChange(chunkChanged: ObjectChange<ChunkEntity>) {
|
||||||
|
|
||||||
|
fun onChunkUpdated(updatedObject: UpdatedObject<ChunkEntity>) {
|
||||||
|
Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${updatedObject.changedFields.joinToString(",")}")
|
||||||
|
if (updatedObject.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) {
|
||||||
|
isLastForward.set(chunkEntity.isLastForward)
|
||||||
|
}
|
||||||
|
if (updatedObject.isFieldChanged(ChunkEntityFields.IS_LAST_BACKWARD)) {
|
||||||
|
isLastBackward.set(chunkEntity.isLastBackward)
|
||||||
|
}
|
||||||
|
if (updatedObject.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) {
|
||||||
|
nextChunk = createTimelineChunk(chunkEntity.nextChunk).also {
|
||||||
|
it?.prevChunk = this
|
||||||
|
}
|
||||||
|
nextChunkLatch?.complete(Unit)
|
||||||
|
}
|
||||||
|
if (updatedObject.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) {
|
||||||
|
prevChunk = createTimelineChunk(chunkEntity.prevChunk).also {
|
||||||
|
it?.nextChunk = this
|
||||||
|
}
|
||||||
|
prevChunkLatch?.complete(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (chunkChanged) {
|
||||||
|
is InitialObject,
|
||||||
|
is DeletedObject -> return
|
||||||
|
is UpdatedObject -> onChunkUpdated(chunkChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is responsible for managing insertions and updates of events on this chunk.
|
* This method is responsible for managing insertions and updates of events on this chunk.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private fun handleDatabaseChangeSet(results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) {
|
private fun handleDatabaseChangeSet(resultChanges: ResultsChange<TimelineEventEntity>) {
|
||||||
val insertions = changeSet.insertionRanges
|
|
||||||
for (range in insertions) {
|
|
||||||
if (!validateInsertion(range, results)) continue
|
|
||||||
val newItems = results
|
|
||||||
.subList(range.startIndex, range.startIndex + range.length)
|
|
||||||
.map { it.buildAndDecryptIfNeeded() }
|
|
||||||
|
|
||||||
builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) }
|
fun validateInsertion(range: ListChangeSet.Range, results: RealmResults<TimelineEventEntity>): Boolean {
|
||||||
newItems.mapIndexed { index, timelineEvent ->
|
// Insertion can only happen from LastForward chunk after a sync.
|
||||||
if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) {
|
if (isLastForward.get()) {
|
||||||
isLastBackward.set(true)
|
val firstBuiltEvent = builtEvents.firstOrNull()
|
||||||
|
if (firstBuiltEvent != null) {
|
||||||
|
val lastInsertion = results[range.startIndex + range.length - 1] ?: return false
|
||||||
|
if (firstBuiltEvent.displayIndex + 1 != lastInsertion.displayIndex) {
|
||||||
|
Timber.v("There is no continuation in the chunk, chunk is not fully loaded yet, skip insert.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val correctedIndex = range.startIndex + index
|
|
||||||
builtEvents.add(correctedIndex, timelineEvent)
|
|
||||||
builtEventsIndexes[timelineEvent.eventId] = correctedIndex
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
val modifications = changeSet.changeRanges
|
|
||||||
for (range in modifications) {
|
fun handleUpdatedResults(updatedResults: UpdatedResults<TimelineEventEntity>) {
|
||||||
for (modificationIndex in (range.startIndex until range.startIndex + range.length)) {
|
val results = updatedResults.list
|
||||||
val updatedEntity = results[modificationIndex] ?: continue
|
val insertions = updatedResults.insertionRanges
|
||||||
val builtEventIndex = builtEventsIndexes[updatedEntity.eventId] ?: continue
|
for (range in insertions) {
|
||||||
try {
|
if (!validateInsertion(range, results)) continue
|
||||||
builtEvents[builtEventIndex] = updatedEntity.buildAndDecryptIfNeeded()
|
val newItems = results
|
||||||
} catch (failure: Throwable) {
|
.subList(range.startIndex, range.startIndex + range.length)
|
||||||
Timber.v("Fail to update items at index: $modificationIndex")
|
.map { it.buildAndDecryptIfNeeded() }
|
||||||
|
|
||||||
|
builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) }
|
||||||
|
newItems.mapIndexed { index, timelineEvent ->
|
||||||
|
if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) {
|
||||||
|
isLastBackward.set(true)
|
||||||
|
}
|
||||||
|
val correctedIndex = range.startIndex + index
|
||||||
|
builtEvents.add(correctedIndex, timelineEvent)
|
||||||
|
builtEventsIndexes[timelineEvent.eventId] = correctedIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val modifications = updatedResults.changeRanges
|
||||||
|
for (range in modifications) {
|
||||||
|
for (modificationIndex in (range.startIndex until range.startIndex + range.length)) {
|
||||||
|
val updatedEntity = results[modificationIndex] ?: continue
|
||||||
|
val builtEventIndex = builtEventsIndexes[updatedEntity.eventId] ?: continue
|
||||||
|
try {
|
||||||
|
builtEvents[builtEventIndex] = updatedEntity.buildAndDecryptIfNeeded()
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.v("Fail to update items at index: $modificationIndex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
|
||||||
|
onBuiltEvents(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val deletions = updatedResults.deletions
|
||||||
|
if (deletions.isNotEmpty()) {
|
||||||
|
onEventsDeleted()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
|
when (resultChanges) {
|
||||||
onBuiltEvents(true)
|
is InitialResults -> Unit
|
||||||
|
is UpdatedResults -> handleUpdatedResults(resultChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
val deletions = changeSet.deletions
|
|
||||||
if (deletions.isNotEmpty()) {
|
|
||||||
onEventsDeleted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateInsertion(range: OrderedCollectionChangeSet.Range, results: RealmResults<TimelineEventEntity>): Boolean {
|
|
||||||
// Insertion can only happen from LastForward chunk after a sync.
|
|
||||||
if (isLastForward.get()) {
|
|
||||||
val firstBuiltEvent = builtEvents.firstOrNull()
|
|
||||||
if (firstBuiltEvent != null) {
|
|
||||||
val lastInsertion = results[range.startIndex + range.length - 1] ?: return false
|
|
||||||
if (firstBuiltEvent.displayIndex + 1 != lastInsertion.displayIndex) {
|
|
||||||
Timber.v("There is no continuation in the chunk, chunk is not fully loaded yet, skip insert.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
|
private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
|
||||||
@ -551,11 +571,15 @@ internal class TimelineChunk(
|
|||||||
}
|
}
|
||||||
return if (builtEvents.isEmpty()) {
|
return if (builtEvents.isEmpty()) {
|
||||||
if (initialEventId != null) {
|
if (initialEventId != null) {
|
||||||
timelineEventEntities.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex
|
chunkEntity.querySortedTimelineEvents(realm, timelineSettings.rootThreadEventId)
|
||||||
|
.query("eventId == $0", initialEventId)
|
||||||
|
.first()
|
||||||
|
.find()
|
||||||
|
?.displayIndex
|
||||||
} else if (direction == Timeline.Direction.BACKWARDS) {
|
} else if (direction == Timeline.Direction.BACKWARDS) {
|
||||||
timelineEventEntities.first(null)?.displayIndex
|
timelineEventEntities.firstOrNull()?.displayIndex
|
||||||
} else {
|
} else {
|
||||||
timelineEventEntities.last(null)?.displayIndex
|
timelineEventEntities.lastOrNull()?.displayIndex
|
||||||
}
|
}
|
||||||
} else if (direction == Timeline.Direction.FORWARDS) {
|
} else if (direction == Timeline.Direction.FORWARDS) {
|
||||||
builtEvents.first().displayIndex + 1
|
builtEvents.first().displayIndex + 1
|
||||||
@ -567,13 +591,14 @@ internal class TimelineChunk(
|
|||||||
private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? {
|
private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? {
|
||||||
if (chunkEntity == null) return null
|
if (chunkEntity == null) return null
|
||||||
return TimelineChunk(
|
return TimelineChunk(
|
||||||
|
timelineScope = timelineScope,
|
||||||
|
realm = realm,
|
||||||
chunkEntity = chunkEntity,
|
chunkEntity = chunkEntity,
|
||||||
timelineSettings = timelineSettings,
|
timelineSettings = timelineSettings,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
timelineId = timelineId,
|
timelineId = timelineId,
|
||||||
eventDecryptor = eventDecryptor,
|
eventDecryptor = eventDecryptor,
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
realmConfiguration = realmConfiguration,
|
|
||||||
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
fetchThreadTimelineTask = fetchThreadTimelineTask,
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
timelineEventMapper = timelineEventMapper,
|
timelineEventMapper = timelineEventMapper,
|
||||||
@ -598,16 +623,14 @@ private fun RealmQuery<TimelineEventEntity>.offsets(
|
|||||||
startDisplayIndex: Int
|
startDisplayIndex: Int
|
||||||
): RealmQuery<TimelineEventEntity> {
|
): RealmQuery<TimelineEventEntity> {
|
||||||
return if (direction == Timeline.Direction.BACKWARDS) {
|
return if (direction == Timeline.Direction.BACKWARDS) {
|
||||||
lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
|
query("displayIndex <= $0", startDisplayIndex)
|
||||||
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
limit(count.toLong())
|
.limit(count)
|
||||||
} else {
|
} else {
|
||||||
greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
|
query("displayIndex >= $0", startDisplayIndex)
|
||||||
// We need to sort ascending first so limit works in the right direction
|
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
.limit(count)
|
||||||
limit(count.toLong())
|
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
// Result is expected to be sorted descending
|
|
||||||
sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,18 +638,11 @@ private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
|||||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults<TimelineEventEntity> {
|
private fun ChunkEntity.querySortedTimelineEvents(realm: TypedRealm, rootThreadEventId: String?): RealmQuery<TimelineEventEntity> {
|
||||||
return if (rootThreadEventId == null) {
|
return if (rootThreadEventId == null) {
|
||||||
timelineEvents
|
TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
|
||||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
|
||||||
} else {
|
} else {
|
||||||
timelineEvents
|
TimelineEventEntity.whereChunkId(realm, chunkId = chunkId)
|
||||||
.where()
|
.query("root.rootThreadEventId == $0 OR root.eventId == $0", rootThreadEventId)
|
||||||
.beginGroup()
|
}.sort("displayIndex", Sort.DESCENDING)
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
|
||||||
.or()
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId)
|
|
||||||
.endGroup()
|
|
||||||
.findAll()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
@ -24,6 +22,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
|||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
@ -35,8 +34,7 @@ import java.util.concurrent.Executors
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class TimelineEventDecryptor @Inject constructor(
|
internal class TimelineEventDecryptor @Inject constructor(
|
||||||
@SessionDatabase
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
||||||
) {
|
) {
|
||||||
@ -97,58 +95,59 @@ internal class TimelineEventDecryptor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
executor?.execute {
|
executor?.execute {
|
||||||
Realm.getInstance(realmConfiguration).use { realm ->
|
try {
|
||||||
try {
|
processDecryptRequest(request)
|
||||||
processDecryptRequest(request, realm)
|
} catch (e: InterruptedException) {
|
||||||
} catch (e: InterruptedException) {
|
Timber.i("Decryption got interrupted")
|
||||||
Timber.i("Decryption got interrupted")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun threadAwareNonEncryptedEvents(request: DecryptionRequest, realm: Realm) {
|
private fun threadAwareNonEncryptedEvents(request: DecryptionRequest) {
|
||||||
val event = request.event
|
val event = request.event
|
||||||
realm.executeTransaction {
|
realmInstance.blockingWrite {
|
||||||
val eventId = event.eventId ?: return@executeTransaction
|
val eventId = event.eventId ?: return@blockingWrite
|
||||||
val eventEntity = EventEntity
|
val eventEntity = EventEntity
|
||||||
.where(it, eventId = eventId)
|
.where(this, eventId = eventId)
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
val decryptedEvent = eventEntity?.asDomain()
|
val decryptedEvent = eventEntity?.asDomain()
|
||||||
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity)
|
threadsAwarenessHandler.makeEventThreadAware(this, event.roomId, decryptedEvent, eventEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
|
private fun processDecryptRequest(request: DecryptionRequest) {
|
||||||
val event = request.event
|
val event = request.event
|
||||||
val timelineId = request.timelineId
|
val timelineId = request.timelineId
|
||||||
|
|
||||||
if (!request.event.isEncrypted()) {
|
if (!request.event.isEncrypted()) {
|
||||||
// Here we have requested a decryption to an event that is not encrypted
|
// Here we have requested a decryption to an event that is not encrypted
|
||||||
// We will simply make this event thread aware
|
// We will simply make this event thread aware
|
||||||
threadAwareNonEncryptedEvents(request, realm)
|
threadAwareNonEncryptedEvents(request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
||||||
val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
|
val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
|
||||||
Timber.v("Successfully decrypted event ${event.eventId}")
|
Timber.v("Successfully decrypted event ${event.eventId}")
|
||||||
realm.executeTransaction {
|
realmInstance.blockingWrite {
|
||||||
val eventId = event.eventId ?: return@executeTransaction
|
val eventId = event.eventId ?: return@blockingWrite
|
||||||
val eventEntity = EventEntity
|
val eventEntity = EventEntity
|
||||||
.where(it, eventId = eventId)
|
.where(this, eventId = eventId)
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
eventEntity?.setDecryptionResult(result)
|
eventEntity?.setDecryptionResult(result)
|
||||||
val decryptedEvent = eventEntity?.asDomain()
|
val decryptedEvent = eventEntity?.asDomain()
|
||||||
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity)
|
threadsAwarenessHandler.makeEventThreadAware(this, event.roomId, decryptedEvent, eventEntity)
|
||||||
}
|
}
|
||||||
} catch (e: MXCryptoError) {
|
} catch (e: MXCryptoError) {
|
||||||
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
|
Timber.v("Failed to decrypt event ${event.eventId} : ${e.localizedMessage}")
|
||||||
if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) {
|
if (e is MXCryptoError.Base /*&& e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID*/) {
|
||||||
// Keep track of unknown sessions to automatically try to decrypt on new session
|
// Keep track of unknown sessions to automatically try to decrypt on new session
|
||||||
realm.executeTransaction {
|
realmInstance.blockingWrite {
|
||||||
EventEntity.where(it, eventId = event.eventId ?: "")
|
EventEntity.where(this, eventId = event.eventId ?: "")
|
||||||
.findFirst()
|
.first()
|
||||||
|
.find()
|
||||||
?.let {
|
?.let {
|
||||||
it.decryptionErrorCode = e.errorType.name
|
it.decryptionErrorCode = e.errorType.name
|
||||||
it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
it.decryptionErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
||||||
|
@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.isValid
|
import io.realm.kotlin.ext.isValid
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.helper.addIfNecessary
|
import org.matrix.android.sdk.internal.database.helper.addIfNecessary
|
||||||
import org.matrix.android.sdk.internal.database.helper.addStateEvent
|
import org.matrix.android.sdk.internal.database.helper.addStateEvent
|
||||||
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
|
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
|
||||||
@ -41,9 +41,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
|||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.extensions.realm
|
|
||||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -52,7 +50,7 @@ import javax.inject.Inject
|
|||||||
* Insert Chunk in DB, and eventually link next and previous chunk in db.
|
* Insert Chunk in DB, and eventually link next and previous chunk in db.
|
||||||
*/
|
*/
|
||||||
internal class TokenChunkEventPersistor @Inject constructor(
|
internal class TokenChunkEventPersistor @Inject constructor(
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||||
private val liveEventManager: Lazy<StreamEventsManager>,
|
private val liveEventManager: Lazy<StreamEventsManager>,
|
||||||
@ -70,8 +68,8 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
direction: PaginationDirection
|
direction: PaginationDirection
|
||||||
): Result {
|
): Result {
|
||||||
monarchy
|
realmInstance
|
||||||
.awaitTransaction { realm ->
|
.write {
|
||||||
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
||||||
|
|
||||||
val nextToken: String?
|
val nextToken: String?
|
||||||
@ -83,16 +81,16 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
nextToken = receivedChunk.start
|
nextToken = receivedChunk.start
|
||||||
prevToken = receivedChunk.end
|
prevToken = receivedChunk.end
|
||||||
}
|
}
|
||||||
val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken)
|
val existingChunk = ChunkEntity.find(this, roomId, prevToken = prevToken, nextToken = nextToken)
|
||||||
if (existingChunk != null) {
|
if (existingChunk != null) {
|
||||||
Timber.v("This chunk is already in the db, return.")
|
Timber.v("This chunk is already in the db, return.")
|
||||||
return@awaitTransaction
|
return@write
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates links in both directions
|
// Creates links in both directions
|
||||||
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
val prevChunk = ChunkEntity.find(this, roomId, nextToken = prevToken)
|
||||||
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
val nextChunk = ChunkEntity.find(this, roomId, prevToken = nextToken)
|
||||||
val currentChunk = ChunkEntity.create(realm, prevToken = prevToken, nextToken = nextToken).apply {
|
val currentChunk = ChunkEntity.create(this, prevToken = prevToken, nextToken = nextToken).apply {
|
||||||
this.nextChunk = nextChunk
|
this.nextChunk = nextChunk
|
||||||
this.prevChunk = prevChunk
|
this.prevChunk = prevChunk
|
||||||
}
|
}
|
||||||
@ -100,9 +98,9 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
prevChunk?.nextChunk = currentChunk
|
prevChunk?.nextChunk = currentChunk
|
||||||
|
|
||||||
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
|
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
|
||||||
handleReachEnd(roomId, direction, currentChunk)
|
handleReachEnd(this, roomId, direction, currentChunk)
|
||||||
} else {
|
} else {
|
||||||
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)
|
handlePagination(this, roomId, direction, receivedChunk, currentChunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,12 +115,11 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReachEnd(roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
|
private fun handleReachEnd(realm: MutableRealm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
|
||||||
Timber.v("Reach end of $roomId in $direction")
|
Timber.v("Reach end of $roomId in $direction")
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
// We should keep the lastForward chunk unique, the one from sync, so make an unidirectional link.
|
// We should keep the lastForward chunk unique, the one from sync, so make an unidirectional link.
|
||||||
// This will allow us to get live events from sync even from a permalink but won't make the link in the opposite.
|
// This will allow us to get live events from sync even from a permalink but won't make the link in the opposite.
|
||||||
val realm = currentChunk.realm
|
|
||||||
currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
|
currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
|
||||||
} else {
|
} else {
|
||||||
currentChunk.isLastBackward = true
|
currentChunk.isLastBackward = true
|
||||||
@ -130,7 +127,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePagination(
|
private fun handlePagination(
|
||||||
realm: Realm,
|
realm: MutableRealm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
receivedChunk: TokenChunkEvent,
|
receivedChunk: TokenChunkEvent,
|
||||||
@ -169,6 +166,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
}
|
}
|
||||||
liveEventManager.get().dispatchPaginatedEventReceived(event, roomId)
|
liveEventManager.get().dispatchPaginatedEventReceived(event, roomId)
|
||||||
currentChunk.addTimelineEvent(
|
currentChunk.addTimelineEvent(
|
||||||
|
realm = realm,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
eventEntity = eventEntity,
|
eventEntity = eventEntity,
|
||||||
direction = direction,
|
direction = direction,
|
||||||
@ -186,7 +184,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentChunk.isValid()) {
|
if (currentChunk.isValid()) {
|
||||||
RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk)
|
RoomEntity.where(realm, roomId).first().find()?.addIfNecessary(currentChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lightweightSettingsStorage.areThreadMessagesEnabled()) {
|
if (lightweightSettingsStorage.areThreadMessagesEnabled()) {
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.tombstone
|
package org.matrix.android.sdk.internal.session.room.tombstone
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.kotlin.MutableRealm
|
||||||
|
import io.realm.kotlin.UpdatePolicy
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
@ -30,17 +31,18 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor {
|
internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor {
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override fun process(realm: MutableRealm, event: Event) {
|
||||||
if (event.roomId == null) return
|
if (event.roomId == null) return
|
||||||
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
|
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
|
||||||
if (createRoomContent?.replacementRoomId == null) return
|
if (createRoomContent?.replacementRoomId == null) return
|
||||||
|
|
||||||
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst()
|
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).first().find()
|
||||||
?: RoomSummaryEntity(event.roomId)
|
?: RoomSummaryEntity()
|
||||||
|
predecessorRoomSummary.roomId = event.roomId
|
||||||
if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
|
if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
|
||||||
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED
|
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED
|
||||||
}
|
}
|
||||||
realm.insertOrUpdate(predecessorRoomSummary)
|
realm.copyToRealm(predecessorRoomSummary, updatePolicy = UpdatePolicy.ALL)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
||||||
|
@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.version
|
package org.matrix.android.sdk.internal.session.room.version
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
@ -41,8 +41,7 @@ internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params,
|
|||||||
internal class DefaultRoomVersionUpgradeTask @Inject constructor(
|
internal class DefaultRoomVersionUpgradeTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
@SessionDatabase
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val realmConfiguration: RealmConfiguration
|
|
||||||
) : RoomVersionUpgradeTask {
|
) : RoomVersionUpgradeTask {
|
||||||
|
|
||||||
override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
|
override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
|
||||||
@ -55,10 +54,8 @@ internal class DefaultRoomVersionUpgradeTask @Inject constructor(
|
|||||||
|
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
realm.where(RoomSummaryEntity::class.java)
|
RoomSummaryEntity.where(realm, replacementRoomId, listOf(Membership.JOIN))
|
||||||
.equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
|
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return replacementRoomId
|
return replacementRoomId
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.space
|
package org.matrix.android.sdk.internal.session.space
|
||||||
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
@ -41,8 +41,7 @@ internal interface JoinSpaceTask : Task<JoinSpaceTask.Params, JoinSpaceResult> {
|
|||||||
|
|
||||||
internal class DefaultJoinSpaceTask @Inject constructor(
|
internal class DefaultJoinSpaceTask @Inject constructor(
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val joinRoomTask: JoinRoomTask,
|
||||||
@SessionDatabase
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val realmConfiguration: RealmConfiguration,
|
|
||||||
private val roomSummaryDataSource: RoomSummaryDataSource
|
private val roomSummaryDataSource: RoomSummaryDataSource
|
||||||
) : JoinSpaceTask {
|
) : JoinSpaceTask {
|
||||||
|
|
||||||
@ -64,16 +63,16 @@ internal class DefaultJoinSpaceTask @Inject constructor(
|
|||||||
|
|
||||||
Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
|
Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
|
||||||
try {
|
try {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm ->
|
awaitNotEmptyResult(realmInstance, TimeUnit.MINUTES.toMillis(2L)) { realm ->
|
||||||
realm.where(RoomSummaryEntity::class.java)
|
realm.query(RoomSummaryEntity::class)
|
||||||
.apply {
|
.let {
|
||||||
if (params.roomIdOrAlias.startsWith("!")) {
|
if (params.roomIdOrAlias.startsWith("!")) {
|
||||||
equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias)
|
it.query("roomId == $0", params.roomIdOrAlias)
|
||||||
} else {
|
} else {
|
||||||
equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias)
|
it.query("canonicalAlias == $0", params.roomIdOrAlias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
.process("membershipStr", listOf(Membership.JOIN))
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
Timber.w("## Space: > Error created with timeout")
|
Timber.w("## Space: > Error created with timeout")
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.sync
|
package org.matrix.android.sdk.internal.session.sync
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import org.matrix.android.sdk.api.session.pushrules.PushRuleService
|
import org.matrix.android.sdk.api.session.pushrules.PushRuleService
|
||||||
import org.matrix.android.sdk.api.session.pushrules.RuleScope
|
import org.matrix.android.sdk.api.session.pushrules.RuleScope
|
||||||
import org.matrix.android.sdk.api.session.sync.InitialSyncStep
|
import org.matrix.android.sdk.api.session.sync.InitialSyncStep
|
||||||
@ -35,7 +34,6 @@ import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
|
|||||||
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
|
||||||
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
|
||||||
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
|
import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
@ -195,16 +195,14 @@ internal class RoomSyncHandler @Inject constructor(
|
|||||||
aggregator
|
aggregator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
realm.insertOrUpdate(roomEntities)
|
|
||||||
reporter?.reportProgress(index + 1F)
|
reporter?.reportProgress(index + 1F)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No need to split
|
// No need to split
|
||||||
val rooms = handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) {
|
handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) {
|
||||||
handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator)
|
handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator)
|
||||||
}
|
}
|
||||||
realm.insertOrUpdate(rooms)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,15 +364,10 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||||||
return updateEventEntity(event, eventEntity, eventPayload, messageTextContent)
|
return updateEventEntity(event, eventEntity, eventPayload, messageTextContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun eventThatRelatesTo(realm: TypedRealm, currentEventId: String, rootThreadEventId: String): List<EventEntity>? {
|
private fun eventThatRelatesTo(realm: TypedRealm, currentEventId: String, rootThreadEventId: String): List<EventEntity> {
|
||||||
val threadList = realm.where<EventEntity>()
|
val threadList = realm.query(EventEntity::class)
|
||||||
.beginGroup()
|
.query("rootThreadEventId == $0 OR eventId == $0", rootThreadEventId)
|
||||||
.equalTo(EventEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
.find()
|
||||||
.or()
|
|
||||||
.equalTo(EventEntityFields.EVENT_ID, rootThreadEventId)
|
|
||||||
.endGroup()
|
|
||||||
.and()
|
|
||||||
.findAll()
|
|
||||||
cacheEventRootId.add(rootThreadEventId)
|
cacheEventRootId.add(rootThreadEventId)
|
||||||
return threadList.filter {
|
return threadList.filter {
|
||||||
it.asDomain().getRelationContentForType(RelationType.THREAD)?.inReplyTo?.eventId == currentEventId
|
it.asDomain().getRelationContentForType(RelationType.THREAD)?.inReplyTo?.eventId == currentEventId
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.user.accountdata
|
package org.matrix.android.sdk.internal.session.user.accountdata
|
||||||
|
|
||||||
import io.realm.Realm
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.getDirectRooms
|
import org.matrix.android.sdk.internal.database.query.getDirectRooms
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
@ -25,20 +24,17 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMess
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DirectChatsHelper @Inject constructor(
|
internal class DirectChatsHelper @Inject constructor(
|
||||||
@SessionDatabase private val realmConfiguration: RealmConfiguration
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a map of userId <-> list of roomId
|
* @return a map of userId <-> list of roomId
|
||||||
*/
|
*/
|
||||||
fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent {
|
fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent {
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
val realm = realmInstance.getBlockingRealm()
|
||||||
// Makes sure we have the latest realm updates, this is important as we sent this information to the server.
|
return RoomSummaryEntity.getDirectRooms(realm)
|
||||||
realm.refresh()
|
.asSequence()
|
||||||
RoomSummaryEntity.getDirectRooms(realm)
|
.filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
|
||||||
.asSequence()
|
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
|
||||||
.filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
|
|
||||||
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
|
|||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmInstance
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
@ -35,7 +36,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
internal class WidgetFactory @Inject constructor(
|
internal class WidgetFactory @Inject constructor(
|
||||||
private val userDataSource: UserDataSource,
|
private val userDataSource: UserDataSource,
|
||||||
private val realmSessionProvider: RealmSessionProvider,
|
@SessionDatabase private val realmInstance: RealmInstance,
|
||||||
private val displayNameResolver: DisplayNameResolver,
|
private val displayNameResolver: DisplayNameResolver,
|
||||||
private val urlResolver: ContentUrlResolver,
|
private val urlResolver: ContentUrlResolver,
|
||||||
@UserId private val userId: String
|
@UserId private val userId: String
|
||||||
@ -49,16 +50,15 @@ internal class WidgetFactory @Inject constructor(
|
|||||||
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
|
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
realmSessionProvider.withRealm {
|
val realm = realmInstance.getBlockingRealm()
|
||||||
val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId)
|
val roomMemberHelper = RoomMemberHelper(realm, widgetEvent.roomId)
|
||||||
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
|
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
|
||||||
SenderInfo(
|
SenderInfo(
|
||||||
userId = widgetEvent.senderId,
|
userId = widgetEvent.senderId,
|
||||||
displayName = roomMemberSummaryEntity?.displayName,
|
displayName = roomMemberSummaryEntity?.displayName,
|
||||||
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
|
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
|
||||||
avatarUrl = roomMemberSummaryEntity?.avatarUrl
|
avatarUrl = roomMemberSummaryEntity?.avatarUrl
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val isAddedByMe = widgetEvent.senderId == userId
|
val isAddedByMe = widgetEvent.senderId == userId
|
||||||
return Widget(
|
return Widget(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user