Skip to content

Commit

Permalink
fix: Prevent realmTransfers NullPointerException with coroutines (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
sirambd authored Dec 17, 2024
2 parents 126e1c4 + 5772e3a commit 727847e
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.infomaniak.multiplatform_swisstransfer.common.exceptions.RealmExcepti
import com.infomaniak.multiplatform_swisstransfer.database.controllers.TransferController
import com.infomaniak.multiplatform_swisstransfer.database.controllers.UploadController
import com.infomaniak.multiplatform_swisstransfer.network.utils.SharedApiRoutes
import kotlin.coroutines.cancellation.CancellationException

/**
* Utility class responsible for creating API URLs for shared routes.
Expand All @@ -33,14 +34,14 @@ class SharedApiUrlCreator internal constructor(

fun shareTransferUrl(transferUUID: String) = SharedApiRoutes.shareTransfer(transferUUID)

@Throws(RealmException::class)
fun downloadFilesUrl(transferUUID: String): String? {
@Throws(RealmException::class, CancellationException::class)
suspend fun downloadFilesUrl(transferUUID: String): String? {
val transfer = transferController.getTransfer(transferUUID) ?: return null
return SharedApiRoutes.downloadFiles(transfer.downloadHost, transfer.linkUUID)
}

@Throws(RealmException::class)
fun downloadFileUrl(transferUUID: String, fileUUID: String?): String? {
@Throws(RealmException::class, CancellationException::class)
suspend fun downloadFileUrl(transferUUID: String, fileUUID: String?): String? {
val transfer = transferController.getTransfer(transferUUID) ?: return null
return SharedApiRoutes.downloadFile(transfer.downloadHost, transfer.linkUUID, fileUUID)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AccountManager internal constructor(
@Throws(RealmException::class, CancellationException::class)
suspend fun loadUser(userId: Int) {
appSettingsController.initAppSettings(emailLanguageUtils.getEmailLanguageFromLocal())
realmProvider.openRealmTransfers(userId)
realmProvider.openTransfersDb(userId)
}

/**
Expand All @@ -60,6 +60,6 @@ class AccountManager internal constructor(
uploadController.removeData()
transferController.removeData()

realmProvider.closeAllRealms()
realmProvider.closeAllDatabases()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class TransferManager internal constructor(
*
* @return A transfer matching the specified transferUUID or null.
*/
fun getTransferByUUID(transferUUID: String): TransferUi? {
suspend fun getTransferByUUID(transferUUID: String): TransferUi? {
return transferController.getTransfer(transferUUID)?.let(::TransferUi)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,53 @@ import com.infomaniak.multiplatform_swisstransfer.database.models.upload.RemoteU
import com.infomaniak.multiplatform_swisstransfer.database.models.upload.UploadContainerDB
import com.infomaniak.multiplatform_swisstransfer.database.models.upload.UploadFileSessionDB
import com.infomaniak.multiplatform_swisstransfer.database.models.upload.UploadSessionDB
import com.infomaniak.multiplatform_swisstransfer.database.utils.RealmUtils.runThrowingRealm
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow

class RealmProvider(private val loadDataInMemory: Boolean = false) {

val realmAppSettings by lazy { Realm.open(realmAppSettingsConfiguration) }
val realmUploads by lazy { Realm.open(realmUploadDBConfiguration) }
var realmTransfers: Realm? = null
private set
val appSettings by lazy { Realm.open(realmAppSettingsConfiguration) }
val uploads by lazy { Realm.open(realmUploadDBConfiguration) }
private val transfersAsync = CompletableDeferred<Realm>()
private suspend fun transfers(): Realm = transfersAsync.await()

fun openRealmTransfers(userId: Int) {
realmTransfers = Realm.open(realmTransfersConfiguration(userId))
fun openTransfersDb(userId: Int) {
transfersAsync.complete(Realm.open(realmTransfersConfiguration(userId)))
}

fun closeRealmAppSettings() {
realmAppSettings.close()
internal suspend inline fun <T> withTransfersDb(block: (Realm) -> T): T {
runThrowingRealm {
return block(transfers())
}
}

fun closeRealmUploads() {
realmUploads.close()
internal fun <T> flowWithTransfersDb(block: suspend (Realm) -> Flow<T>): Flow<T> = flow {
runThrowingRealm {
emitAll(block(transfers()))
}
}

fun closeRealmTransfers() {
realmTransfers?.close()
fun closeAppSettingsDb() {
appSettings.close()
}

fun closeAllRealms() {
closeRealmAppSettings()
closeRealmUploads()
closeRealmTransfers()
fun closeUploadsDb() {
uploads.close()
}

suspend fun closeTransfersDb() {
transfersAsync.await().close()
}

suspend fun closeAllDatabases() {
closeAppSettingsDb()
closeUploadsDb()
closeTransfersDb()
}

private val realmAppSettingsConfiguration = RealmConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import kotlin.coroutines.cancellation.CancellationException
@OptIn(ExperimentalCoroutinesApi::class)
class AppSettingsController(private val realmProvider: RealmProvider) {

private val realm by lazy { realmProvider.realmAppSettings }
private val realm by lazy { realmProvider.appSettings }

private val appSettingsQuery get() = realm.query<AppSettingsDB>().first()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import io.realm.kotlin.ext.query
import kotlin.coroutines.cancellation.CancellationException

class EmailTokensController(private val realmProvider: RealmProvider) {
private val realm by lazy { realmProvider.realmAppSettings }
private val realm by lazy { realmProvider.appSettings }

//region Get data
@Throws(RealmException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.infomaniak.multiplatform_swisstransfer.common.exceptions.RealmExcepti
import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.File
import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.database.models.transfers.FileDB
import com.infomaniak.multiplatform_swisstransfer.database.utils.RealmUtils.runThrowingRealm
import io.realm.kotlin.ext.query
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
Expand All @@ -30,11 +29,9 @@ import kotlinx.coroutines.flow.mapLatest
@OptIn(ExperimentalCoroutinesApi::class)
class FileController(private val realmProvider: RealmProvider) {

private val realm by lazy { realmProvider.realmTransfers!! }

@Throws(RealmException::class)
fun getFilesFromTransfer(folderUuid: String): Flow<List<File>> = runThrowingRealm {
fun getFilesFromTransfer(folderUuid: String): Flow<List<File>> = realmProvider.flowWithTransfersDb { realm ->
val query = "${FileDB::folder.name}.uuid == '$folderUuid'"
return realm.query<FileDB>(query).asFlow().mapLatest { it.list }
realm.query<FileDB>(query).asFlow().mapLatest { it.list }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.infomaniak.multiplatform_swisstransfer.common.utils.DateUtils
import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.database.models.transfers.TransferDB
import com.infomaniak.multiplatform_swisstransfer.database.utils.FileUtils
import com.infomaniak.multiplatform_swisstransfer.database.utils.RealmUtils.runThrowingRealm
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.Realm
import io.realm.kotlin.UpdatePolicy
Expand All @@ -43,11 +42,11 @@ import kotlin.coroutines.cancellation.CancellationException
@OptIn(ExperimentalCoroutinesApi::class)
class TransferController(private val realmProvider: RealmProvider) {

private val realm by lazy { realmProvider.realmTransfers!! }

//region Get data
@Throws(RealmException::class)
internal fun getTransfers(transferDirection: TransferDirection? = null): RealmResults<TransferDB> = runThrowingRealm {
@Throws(RealmException::class, CancellationException::class)
internal suspend fun getTransfers(
transferDirection: TransferDirection? = null
): RealmResults<TransferDB> = realmProvider.withTransfersDb { realm ->
val directionFilterQuery = when (transferDirection) {
null -> TRUE_PREDICATE
else -> "${TransferDB.transferDirectionPropertyName} == '${transferDirection}'"
Expand All @@ -56,30 +55,30 @@ class TransferController(private val realmProvider: RealmProvider) {
}

@Throws(RealmException::class)
fun getTransfersFlow(transferDirection: TransferDirection): Flow<List<Transfer>> = runThrowingRealm {
return getTransfers(transferDirection).asFlow().mapLatest { it.list }
fun getTransfersFlow(transferDirection: TransferDirection): Flow<List<Transfer>> = realmProvider.flowWithTransfersDb {
getTransfers(transferDirection).asFlow().mapLatest { it.list }
}

@Throws(RealmException::class)
fun getTransferFlow(linkUUID: String): Flow<Transfer?> = runThrowingRealm {
return getTransferQuery(realm, linkUUID).asFlow().mapLatest { it.obj }
fun getTransferFlow(linkUUID: String): Flow<Transfer?> = realmProvider.flowWithTransfersDb { realm ->
getTransferQuery(realm, linkUUID).asFlow().mapLatest { it.obj }
}

@Throws(RealmException::class)
fun getTransfer(linkUUID: String): Transfer? = runThrowingRealm {
@Throws(RealmException::class, CancellationException::class)
suspend fun getTransfer(linkUUID: String): Transfer? = realmProvider.withTransfersDb { realm ->
return getTransferQuery(realm, linkUUID).find()
}

@Throws(RealmException::class)
fun getNotReadyTransfers(): List<Transfer> = runThrowingRealm {
@Throws(RealmException::class, CancellationException::class)
suspend fun getNotReadyTransfers(): List<Transfer> = realmProvider.withTransfersDb { realm ->
val query = "${TransferDB.transferStatusPropertyName} != '${TransferStatus.READY.name}'"
return realm.query<TransferDB>(query).find()
}
//endregion

//region Upsert data
@Throws(RealmException::class, CancellationException::class, TransferWithoutFilesException::class)
suspend fun upsert(transfer: Transfer, transferDirection: TransferDirection, password: String?) = runThrowingRealm {
suspend fun upsert(transfer: Transfer, transferDirection: TransferDirection, password: String?) = realmProvider.withTransfersDb { realm ->
realm.write {
val transferDB = TransferDB(transfer, transferDirection, password)
transferDB.container?.files?.let { transferFiles ->
Expand All @@ -94,7 +93,7 @@ class TransferController(private val realmProvider: RealmProvider) {
linkUUID: String,
uploadSession: UploadSession,
transferStatus: TransferStatus,
) = runThrowingRealm {
) = realmProvider.withTransfersDb { realm ->
val transferDB = TransferDB(linkUUID, uploadSession, transferStatus).apply {
container?.files?.let { files ->
FileUtils.getFileDBTree(containerUUID, files)
Expand All @@ -109,20 +108,20 @@ class TransferController(private val realmProvider: RealmProvider) {

//region Update data
@Throws(RealmException::class, CancellationException::class)
suspend fun deleteTransfer(transferUUID: String) = runThrowingRealm {
suspend fun deleteTransfer(transferUUID: String) = realmProvider.withTransfersDb { realm ->
realm.write {
val transferToDelete = query<TransferDB>("${TransferDB::linkUUID.name} == '$transferUUID'").first()
delete(transferToDelete)
}
}

@Throws(RealmException::class, CancellationException::class)
suspend fun deleteExpiredTransfers() = runThrowingRealm {
suspend fun deleteExpiredTransfers() = realmProvider.withTransfersDb { realm ->
realm.write { delete(getExpiredTransfersQuery(realm = this)) }
}

@Throws(RealmException::class, CancellationException::class)
suspend fun removeData() = runThrowingRealm {
suspend fun removeData() = realmProvider.withTransfersDb { realm ->
realm.write { deleteAll() }
}
//endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import kotlin.coroutines.cancellation.CancellationException

class UploadController(private val realmProvider: RealmProvider) {

private val realm by lazy { realmProvider.realmUploads }
private val realm by lazy { realmProvider.uploads }

//region Queries
private fun getUploadsQuery() = realm.query<UploadSessionDB>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
package com.infomaniak.multiplatform_swisstransfer.database.utils

import com.infomaniak.multiplatform_swisstransfer.common.exceptions.RealmException
import kotlinx.coroutines.CancellationException

object RealmUtils {

@Throws(RealmException::class)
inline fun <R> runThrowingRealm(block: () -> R): R {
return runCatching { block() }.getOrElse { throw RealmException(it) }
return runCatching { block() }.onFailure {
if (it is CancellationException) throw it
}.getOrElse { throw RealmException(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ class TransferControllerTest {

@BeforeTest
fun setup() {
realmProvider = RealmProvider(loadDataInMemory = true).apply { openRealmTransfers(userId = 0) }
realmProvider = RealmProvider(loadDataInMemory = true).apply { openTransfersDb(userId = 0) }
transferController = TransferController(realmProvider)
}

@AfterTest
fun tearDown() = runTest {
transferController.removeData()
realmProvider.closeRealmTransfers()
realmProvider.closeTransfersDb()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class UploadControllerTest {
@AfterTest
fun tearDown() = runTest {
uploadController.removeData()
realmProvider.closeRealmUploads()
realmProvider.closeUploadsDb()
}

@Test
Expand Down

0 comments on commit 727847e

Please sign in to comment.