Realm: convert ContentScannerDatabase to realm-kotlin

This commit is contained in:
ganfra 2022-07-29 17:35:53 +02:00
parent c5883b3e7c
commit af5bbe12f5
11 changed files with 182 additions and 207 deletions

View File

@ -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<Optional<ScanStatusInfo>>
fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo?
fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): Flow<Optional<ScanStatusInfo>>
suspend fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo?
/**
* Get the current public curve25519 key that the AV server is advertising.

View File

@ -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<RoomService>,
@ -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<RealmConfiguration> {
@ -274,7 +273,7 @@ internal class DefaultSession @Inject constructor(
realmConfiguration,
realmConfigurationCrypto,
//realmConfigurationIdentity,
realmConfigurationContentScanner,
//realmConfigurationContentScanner,
)
}
}

View File

@ -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

View File

@ -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<Optional<ScanStatusInfo>> {
val data = contentScannerStore.getLiveScanResult(mxcUrl)
if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) {
taskExecutor.executorScope.launch {
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): Flow<Optional<ScanStatusInfo>> {
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)
}
}

View File

@ -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<Optional<ScanStatusInfo>> {
return MutableLiveData()
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): Flow<Optional<ScanStatusInfo>> {
return emptyFlow()
}
override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
override suspend fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
return null
}
}

View File

@ -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<Optional<ScanStatusInfo>>
fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean
suspend fun getScanResult(mxcUrl: String): ScanStatusInfo?
fun getLiveScanResult(mxcUrl: String): Flow<Optional<ScanStatusInfo>>
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)
}

View File

@ -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() {

View File

@ -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<ContentScanResultEntity>()
.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<ContentScanResultEntity>().also {
it.mediaUrl = attachmentUrl
it.scanDateTimestamp = currentTimeMillis
it.scannerUrl = contentScannerUrl
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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<ContentScannerInfoEntity>()
}, {
it.serverUrl
}
).firstOrNull()
return realmInstance.getBlockingRealm()
.queryContentScannerInfoEntity()
.first()
.find()
?.serverUrl
}
override fun setScannerUrl(url: String?) {
monarchy.runTransactionSync { realm ->
val info = realm.where<ContentScannerInfoEntity>().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<ContentScannerInfoEntity>().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<ContentScannerInfoEntity>()
}, {
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<ContentScanResultEntity>()
.equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
.apply {
getScannerUrl()?.let {
equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
}
}
}, {
it.toModel()
})
.firstOrNull()
}
override fun getLiveScanResult(mxcUrl: String): LiveData<Optional<ScanStatusInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<ContentScanResultEntity>()
.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<Optional<ScanStatusInfo>> {
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<ContentScanResultEntity> {
return query(ContentScanResultEntity::class, "mediaUrl == $0", mxcUrl)
.apply {
if (scannerUrl != null) {
query("scannerUrl == $0", scannerUrl)
}
}
}
private fun TypedRealm.queryContentScannerInfoEntity(): RealmQuery<ContentScannerInfoEntity> {
return query(ContentScannerInfoEntity::class)
}
}