diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt index 22250628d5..213ebed0e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.contentscanner -import androidx.lifecycle.LiveData +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.api.util.Optional @@ -24,12 +24,14 @@ interface ContentScannerService { val serverPublicKey: String? + //TODO: suspend fun getContentScannerServer(): String? - fun setScannerUrl(url: String?) - fun enableScanner(enabled: Boolean) + suspend fun setScannerUrl(url: String?) + suspend fun enableScanner(enabled: Boolean) + //TODO: suspend fun isScannerEnabled(): Boolean - fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): LiveData> - fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? + fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): Flow> + suspend fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? /** * Get the current public curve25519 key that the AV server is advertising. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 026df3b74e..b8d72df85e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -90,7 +90,6 @@ internal class DefaultSession @Inject constructor( override val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val realmConfiguration: RealmConfiguration, @CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration, - @ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, private val roomService: Lazy, @@ -266,7 +265,7 @@ internal class DefaultSession @Inject constructor( RealmDebugTools(realmConfiguration).logInfo("Session") RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") //RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") - RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") + //RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") } override fun getRealmConfigurations(): List { @@ -274,7 +273,7 @@ internal class DefaultSession @Inject constructor( realmConfiguration, realmConfigurationCrypto, //realmConfigurationIdentity, - realmConfigurationContentScanner, + //realmConfigurationContentScanner, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt index 89b5c44ef0..51a2c806f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt @@ -19,16 +19,20 @@ package org.matrix.android.sdk.internal.session.contentscanner import dagger.Binds import dagger.Module import dagger.Provides -import io.realm.RealmConfiguration +import io.realm.kotlin.RealmConfiguration +import kotlinx.coroutines.CoroutineScope +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService +import org.matrix.android.sdk.internal.database.RealmInstance import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.ContentScannerDatabase +import org.matrix.android.sdk.internal.di.MatrixCoroutineScope import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.session.SessionModule import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore -import org.matrix.android.sdk.internal.session.contentscanner.db.ContentScannerRealmModule +import org.matrix.android.sdk.internal.session.contentscanner.db.CONTENT_SCANNER_REALM_SCHEMA import org.matrix.android.sdk.internal.session.contentscanner.db.RealmContentScannerStore import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultDownloadEncryptedTask import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultGetServerPublicKeyTask @@ -48,22 +52,35 @@ internal abstract class ContentScannerModule { @JvmStatic @Provides @ContentScannerDatabase - @SessionScope - fun providesContentScannerRealmConfiguration( + fun providesRealmConfiguration( realmKeysUtils: RealmKeysUtils, @SessionFilesDirectory directory: File, @UserMd5 userMd5: String ): RealmConfiguration { - return RealmConfiguration.Builder() - .directory(directory) - .name("matrix-sdk-content-scanning.realm") + return RealmConfiguration.Builder(CONTENT_SCANNER_REALM_SCHEMA) + .directory(directory.path) .apply { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } - .allowWritesOnUiThread(true) - .modules(ContentScannerRealmModule()) + .name("matrix-sdk-content-scanning.realm") .build() } + + @JvmStatic + @Provides + @ContentScannerDatabase + @SessionScope + fun providesRealmInstance( + @ContentScannerDatabase realmConfiguration: RealmConfiguration, + @MatrixCoroutineScope matrixCoroutineScope: CoroutineScope, + matrixCoroutineDispatchers: MatrixCoroutineDispatchers + ): RealmInstance { + return RealmInstance( + coroutineScope = matrixCoroutineScope, + realmConfiguration = realmConfiguration, + coroutineDispatcher = matrixCoroutineDispatchers.io + ) + } } @Binds diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt index 5aaf058757..8ceeb2a6b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.contentscanner -import androidx.lifecycle.LiveData import dagger.Lazy +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import okhttp3.OkHttpClient import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService @@ -91,7 +91,7 @@ internal class DefaultContentScannerService @Inject constructor( ) } - override fun setScannerUrl(url: String?) = contentScannerStore.setScannerUrl(url).also { + override suspend fun setScannerUrl(url: String?) = contentScannerStore.setScannerUrl(url).also { if (url == null) { contentScannerApiProvider.contentScannerApi = null serverPublicKey = null @@ -111,18 +111,17 @@ internal class DefaultContentScannerService @Inject constructor( } } - override fun enableScanner(enabled: Boolean) = contentScannerStore.enableScanner(enabled) + override suspend fun enableScanner(enabled: Boolean) = contentScannerStore.enableScanner(enabled) override fun isScannerEnabled(): Boolean = contentScannerStore.isScanEnabled() - override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? { + override suspend fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? { return contentScannerStore.getScanResult(mxcUrl) } - override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData> { - val data = contentScannerStore.getLiveScanResult(mxcUrl) - if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) { - taskExecutor.executorScope.launch { + override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): Flow> { + taskExecutor.executorScope.launch { + if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) { try { getScanResultForAttachment(mxcUrl, fileInfo) } catch (failure: Throwable) { @@ -130,6 +129,6 @@ internal class DefaultContentScannerService @Inject constructor( } } } - return data + return contentScannerStore.getLiveScanResult(mxcUrl) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt index 41c444ad83..1f8cbbafde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.contentscanner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt @@ -46,21 +48,21 @@ internal class DisabledContentScannerService @Inject constructor() : ContentScan TODO("Not yet implemented") } - override fun setScannerUrl(url: String?) { + override suspend fun setScannerUrl(url: String?) { } - override fun enableScanner(enabled: Boolean) { + override suspend fun enableScanner(enabled: Boolean) { } override fun isScannerEnabled(): Boolean { return false } - override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData> { - return MutableLiveData() + override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): Flow> { + return emptyFlow() } - override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? { + override suspend fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? { return null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt index 5cfe851a5c..e20dd5e172 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/data/ContentScannerStore.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.session.contentscanner.data -import androidx.lifecycle.LiveData +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.contentscanner.ScanState import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo import org.matrix.android.sdk.api.util.Optional @@ -25,16 +25,16 @@ internal interface ContentScannerStore { fun getScannerUrl(): String? - fun setScannerUrl(url: String?) + suspend fun setScannerUrl(url: String?) - fun enableScanner(enabled: Boolean) + suspend fun enableScanner(enabled: Boolean) fun isScanEnabled(): Boolean - fun getScanResult(mxcUrl: String): ScanStatusInfo? - fun getLiveScanResult(mxcUrl: String): LiveData> - fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean + suspend fun getScanResult(mxcUrl: String): ScanStatusInfo? + fun getLiveScanResult(mxcUrl: String): Flow> + suspend fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean - fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) - fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) + suspend fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) + suspend fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt index 629de4d142..1675d29ef1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScanResultEntity.kt @@ -16,22 +16,19 @@ package org.matrix.android.sdk.internal.session.contentscanner.db -import io.realm.RealmModel -import io.realm.annotations.Index -import io.realm.annotations.RealmClass +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.Index import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.contentscanner.ScanState import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo -@RealmClass -internal open class ContentScanResultEntity( - @Index - var mediaUrl: String? = null, - var scanStatusString: String? = null, - var humanReadableMessage: String? = null, - var scanDateTimestamp: Long? = null, - var scannerUrl: String? = null -) : RealmModel { +internal class ContentScanResultEntity : RealmObject { + @Index + var mediaUrl: String? = null + var scanStatusString: String? = null + var humanReadableMessage: String? = null + var scanDateTimestamp: Long? = null + var scannerUrl: String? = null var scanResult: ScanState get() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt deleted file mode 100644 index bb3051bc96..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2021 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.session.contentscanner.db - -import io.realm.Realm -import io.realm.kotlin.createObject -import io.realm.kotlin.where - -internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity? { - return realm.where() - .equalTo(ContentScanResultEntityFields.MEDIA_URL, attachmentUrl) - .apply { - contentScannerUrl?.let { - equalTo(ContentScanResultEntityFields.SCANNER_URL, it) - } - } - .findFirst() -} - -internal fun ContentScanResultEntity.Companion.getOrCreate( - realm: Realm, - attachmentUrl: String, - contentScannerUrl: String?, - currentTimeMillis: Long -): ContentScanResultEntity { - return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl) - ?: realm.createObject().also { - it.mediaUrl = attachmentUrl - it.scanDateTimestamp = currentTimeMillis - it.scannerUrl = contentScannerUrl - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt index affd1d59f3..b411fda0bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerInfoEntity.kt @@ -16,14 +16,12 @@ package org.matrix.android.sdk.internal.session.contentscanner.db -import io.realm.RealmModel -import io.realm.annotations.RealmClass +import io.realm.kotlin.types.RealmObject -@RealmClass -internal open class ContentScannerInfoEntity( - var serverUrl: String? = null, - var enabled: Boolean? = null -) : RealmModel { +internal open class ContentScannerInfoEntity : RealmObject { + var serverUrl: String? = null + var enabled: Boolean? = null companion object } + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt index 1872bb72a9..5fb9e5387e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt @@ -16,16 +16,7 @@ package org.matrix.android.sdk.internal.session.contentscanner.db -import io.realm.annotations.RealmModule - -/** - * Realm module for content scanner classes. - */ -@RealmModule( - library = true, - classes = [ - ContentScannerInfoEntity::class, - ContentScanResultEntity::class - ] +internal val CONTENT_SCANNER_REALM_SCHEMA = setOf( + ContentScannerInfoEntity::class, + ContentScanResultEntity::class ) -internal class ContentScannerRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt index 72dc15c1bd..6cd3f2e273 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt @@ -16,18 +16,18 @@ package org.matrix.android.sdk.internal.session.contentscanner.db -import androidx.lifecycle.LiveData -import androidx.lifecycle.Transformations -import com.zhuinden.monarchy.Monarchy -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.kotlin.createObject -import io.realm.kotlin.where +import io.realm.kotlin.TypedRealm +import io.realm.kotlin.query.RealmQuery +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.contentscanner.ScanState import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmInstance +import org.matrix.android.sdk.internal.database.await import org.matrix.android.sdk.internal.di.ContentScannerDatabase import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore @@ -38,108 +38,124 @@ import javax.inject.Inject @SessionScope internal class RealmContentScannerStore @Inject constructor( @ContentScannerDatabase - private val realmConfiguration: RealmConfiguration, + private val realmInstance: RealmInstance, private val clock: Clock, ) : ContentScannerStore { - private val monarchy = Monarchy.Builder() - .setRealmConfiguration(realmConfiguration) - .build() - override fun getScannerUrl(): String? { - return monarchy.fetchAllMappedSync( - { realm -> - realm.where() - }, { - it.serverUrl - } - ).firstOrNull() + return realmInstance.getBlockingRealm() + .queryContentScannerInfoEntity() + .first() + .find() + ?.serverUrl } - override fun setScannerUrl(url: String?) { - monarchy.runTransactionSync { realm -> - val info = realm.where().findFirst() - ?: realm.createObject() - info.serverUrl = url - } + override suspend fun setScannerUrl(url: String?) = upsertContentScannerInfoEntity { + it.serverUrl = url } - override fun enableScanner(enabled: Boolean) { - monarchy.runTransactionSync { realm -> - val info = realm.where().findFirst() - ?: realm.createObject() - info.enabled = enabled + override suspend fun enableScanner(enabled: Boolean) = upsertContentScannerInfoEntity { + it.enabled = enabled + } + + private suspend fun upsertContentScannerInfoEntity(operation: (ContentScannerInfoEntity) -> Unit) { + realmInstance.write { + val contentScannerInfoEntity = queryContentScannerInfoEntity().first().find() + if (contentScannerInfoEntity != null) { + operation(contentScannerInfoEntity) + } else { + val newContentScannerInfoEntity = ContentScannerInfoEntity().apply { + operation(this) + } + copyToRealm(newContentScannerInfoEntity) + } } } override fun isScanEnabled(): Boolean { - return monarchy.fetchAllMappedSync( - { realm -> - realm.where() - }, { - it.enabled.orFalse() && it.serverUrl?.isValidUrl().orFalse() - } - ).firstOrNull().orFalse() + val contentScannerInfoEntity = realmInstance.getBlockingRealm() + .queryContentScannerInfoEntity() + .first() + .find() ?: return false + + return contentScannerInfoEntity.enabled.orFalse() && contentScannerInfoEntity.serverUrl?.isValidUrl().orFalse() } - override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) { - monarchy.runTransactionSync { - ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).scanResult = state - } + override suspend fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) = upsertContentScanResultEntity(mxcUrl, scannerUrl) { + it.scanResult = state } - override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) { - monarchy.runTransactionSync { - ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).apply { - scanResult = state - scanDateTimestamp = clock.epochMillis() - humanReadableMessage = humanReadable - } - } + override suspend fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) = upsertContentScanResultEntity( + mxcUrl, + scannerUrl + ) { + it.scanResult = state + it.scanDateTimestamp = clock.epochMillis() + it.humanReadableMessage = humanReadable } - override fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean { - var isKnown = false - monarchy.runTransactionSync { - val info = ContentScanResultEntity.get(it, mxcUrl, scannerUrl)?.scanResult - isKnown = when (info) { - ScanState.IN_PROGRESS, - ScanState.TRUSTED, - ScanState.INFECTED -> true - else -> false - } - } - return isKnown - } - - override fun getScanResult(mxcUrl: String): ScanStatusInfo? { - return monarchy.fetchAllMappedSync({ realm -> - realm.where() - .equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl) - .apply { - getScannerUrl()?.let { - equalTo(ContentScanResultEntityFields.SCANNER_URL, it) - } - } - }, { - it.toModel() - }) - .firstOrNull() - } - - override fun getLiveScanResult(mxcUrl: String): LiveData> { - val liveData = monarchy.findAllMappedWithChanges( - { realm: Realm -> - realm.where() - .equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl) - .equalTo(ContentScanResultEntityFields.SCANNER_URL, getScannerUrl()) - }, - { entity -> - entity.toModel() + private suspend fun upsertContentScanResultEntity(mxcUrl: String, scannerUrl: String?, operation: (ContentScanResultEntity) -> Unit) { + realmInstance.write { + val contentScanResultEntity = queryContentScanResultEntity(mxcUrl, scannerUrl).first().find() + if (contentScanResultEntity != null) { + operation(contentScanResultEntity) + } else { + val newContentScanResultEntity = ContentScanResultEntity().apply { + operation(this) } - ) - return Transformations.map(liveData) { - it.firstOrNull().toOptional() + copyToRealm(newContentScanResultEntity) + } } } + + override suspend fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean { + val info = realmInstance.getRealm() + .queryContentScanResultEntity(mxcUrl, scannerUrl) + .first() + .await() + ?.scanResult + + return when (info) { + ScanState.IN_PROGRESS, + ScanState.TRUSTED, + ScanState.INFECTED -> true + else -> false + } + } + + override suspend fun getScanResult(mxcUrl: String): ScanStatusInfo? { + val scannerUrl = getScannerUrl() + return realmInstance.getRealm() + .queryContentScanResultEntity(mxcUrl, scannerUrl) + .first() + .await() + ?.toModel() + } + + override fun getLiveScanResult(mxcUrl: String): Flow> { + return realmInstance.getRealmFlow() + .flatMapConcat { realm -> + val scannerUrl = getScannerUrl() + realm + .queryContentScanResultEntity(mxcUrl, scannerUrl) + .first() + .asFlow() + }.map { + val scanStatusInfo = it.obj?.toModel() + Optional.from(scanStatusInfo) + } + } + + private fun TypedRealm.queryContentScanResultEntity(mxcUrl: String, scannerUrl: String?): RealmQuery { + return query(ContentScanResultEntity::class, "mediaUrl == $0", mxcUrl) + .apply { + if (scannerUrl != null) { + query("scannerUrl == $0", scannerUrl) + } + } + } + + private fun TypedRealm.queryContentScannerInfoEntity(): RealmQuery { + return query(ContentScannerInfoEntity::class) + } }