diff --git a/coroutine-utils/build.gradle.kts b/coroutine-utils/build.gradle.kts index 2a20df08..78f1e8c2 100644 --- a/coroutine-utils/build.gradle.kts +++ b/coroutine-utils/build.gradle.kts @@ -10,4 +10,5 @@ plugins { dependencies { implementation(libs.kotlin.coroutines.core) implementation(libs.dagger.hilt.core) + api(libs.thirdparty.kotlinResult) } diff --git a/coroutine-utils/src/main/kotlin/dev/msfjarvis/aps/util/coroutines/RunSuspendCatching.kt b/coroutine-utils/src/main/kotlin/dev/msfjarvis/aps/util/coroutines/RunSuspendCatching.kt new file mode 100644 index 00000000..7e5b906c --- /dev/null +++ b/coroutine-utils/src/main/kotlin/dev/msfjarvis/aps/util/coroutines/RunSuspendCatching.kt @@ -0,0 +1,48 @@ +@file:OptIn(ExperimentalContracts::class) +@file:Suppress("RedundantSuspendModifier") + +package dev.msfjarvis.aps.util.coroutines + +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlinx.coroutines.CancellationException + +/** + * Calls the specified function [block] with [this] value as its receiver and returns its + * encapsulated result if invocation was successful, catching any [Throwable] except + * [CancellationException] that was thrown from the [block] function execution and encapsulating it + * as a failure. + */ +public suspend inline fun runSuspendCatching(block: () -> V): Result { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + + return try { + Ok(block()) + } catch (e: Throwable) { + if (e is CancellationException) throw e + Err(e) + } +} + +/** + * Calls the specified function [block] with [this] value as its receiver and returns its + * encapsulated result if invocation was successful, catching any [Throwable] except + * [CancellationException] that was thrown from the [block] function execution and encapsulating it + * as a failure. + */ +public suspend inline infix fun T.runSuspendCatching( + block: T.() -> V +): Result { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + + return try { + Ok(block()) + } catch (e: Throwable) { + if (e is CancellationException) throw e + Err(e) + } +} diff --git a/crypto-pgpainless/build.gradle.kts b/crypto-pgpainless/build.gradle.kts index ee55f349..b3ace3d9 100644 --- a/crypto-pgpainless/build.gradle.kts +++ b/crypto-pgpainless/build.gradle.kts @@ -10,6 +10,7 @@ plugins { dependencies { api(projects.cryptoCommon) + implementation(projects.coroutineUtils) implementation(libs.androidx.annotation) implementation(libs.dagger.hilt.core) implementation(libs.kotlin.coroutines.core) diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt index adcce22b..8c54d516 100644 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt +++ b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt @@ -8,9 +8,9 @@ package dev.msfjarvis.aps.crypto import androidx.annotation.VisibleForTesting import com.github.michaelbull.result.Result -import com.github.michaelbull.result.runCatching import dev.msfjarvis.aps.crypto.KeyUtils.tryGetId import dev.msfjarvis.aps.crypto.KeyUtils.tryParseKeyring +import dev.msfjarvis.aps.util.coroutines.runSuspendCatching import java.io.File import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -28,7 +28,7 @@ constructor( override suspend fun addKey(key: PGPKey, replace: Boolean): Result = withContext(dispatcher) { - runCatching { + runSuspendCatching { if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") @@ -49,7 +49,7 @@ constructor( override suspend fun removeKey(key: PGPKey): Result = withContext(dispatcher) { - runCatching { + runSuspendCatching { if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException if (tryParseKeyring(key) == null) throw KeyManagerException.InvalidKeyException val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") @@ -63,7 +63,7 @@ constructor( override suspend fun getKeyById(id: GpgIdentifier): Result = withContext(dispatcher) { - runCatching { + runSuspendCatching { if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException val keyFiles = keyDir.listFiles() if (keyFiles.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException @@ -89,7 +89,7 @@ constructor( } if (matchResult != null) { - return@runCatching matchResult + return@runSuspendCatching matchResult } throw KeyManagerException.KeyNotFoundException("$id") @@ -98,10 +98,10 @@ constructor( override suspend fun getAllKeys(): Result, Throwable> = withContext(dispatcher) { - runCatching { + runSuspendCatching { if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException val keyFiles = keyDir.listFiles() - if (keyFiles.isNullOrEmpty()) return@runCatching emptyList() + if (keyFiles.isNullOrEmpty()) return@runSuspendCatching emptyList() keyFiles.map { keyFile -> PGPKey(keyFile.readBytes()) }.toList() } }