Skip to content

Commit

Permalink
fix: Fix realmTransfers NullPointerException with coroutines
Browse files Browse the repository at this point in the history
Instead of depending on an init order that doesn't always happen as wanted,
and having the !! footgun, we are leveraging coroutines to wait (if needed),
until the instance we need is initialized.
  • Loading branch information
LouisCAD committed Dec 17, 2024
1 parent 126e1c4 commit 2bddba1
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 35 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 @@ -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,18 +26,37 @@ 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

suspend fun realmTransfers(): Realm = realmTransfersAsync.await()

private val realmTransfersAsync = CompletableDeferred<Realm>()

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

suspend inline fun <T> withRealm(block: (Realm) -> T): T {
runThrowingRealm {
return block(realmTransfers())
}
}

fun <T> flowWithRealm(block: suspend (Realm) -> Flow<T>): Flow<T> = flow {
runThrowingRealm {
emitAll(block(realmTransfers()))
}
}

fun closeRealmAppSettings() {
Expand All @@ -48,11 +67,11 @@ class RealmProvider(private val loadDataInMemory: Boolean = false) {
realmUploads.close()
}

fun closeRealmTransfers() {
realmTransfers?.close()
suspend fun closeRealmTransfers() {
realmTransfersAsync.await().close()
}

fun closeAllRealms() {
suspend fun closeAllRealms() {
closeRealmAppSettings()
closeRealmUploads()
closeRealmTransfers()
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.flowWithRealm { 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.withRealm { 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.flowWithRealm {
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.flowWithRealm { 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.withRealm { 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.withRealm { 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.withRealm { 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.withRealm { 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.withRealm { 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.withRealm { realm ->
realm.write { delete(getExpiredTransfersQuery(realm = this)) }
}

@Throws(RealmException::class, CancellationException::class)
suspend fun removeData() = runThrowingRealm {
suspend fun removeData() = realmProvider.withRealm { realm ->
realm.write { deleteAll() }
}
//endregion
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) }
}
}

0 comments on commit 2bddba1

Please sign in to comment.