Add catchingExceptions method to replace runCatching (#4797)

- Add `runCatchingExceptions` and `mapCatchingExceptions` to replace `runCatching` and `mapCatching`.
- Make `tryOrNull { ... }` catch only exceptions too.
- Apply the changes to the whole project.
- Add new Rust fakes for tests to handle the code that's now unblocked - previously it just threw an `UnsatisfiedLinkError` which we ignored.
- Add a new `detekt-rules` project with a `RunCatchingRule` to prevent `runCatching` and `mapCatching` usages.
This commit is contained in:
Jorge Martin Espinosa 2025-06-04 09:02:26 +02:00 committed by GitHub
parent 7816529fd7
commit efdc10e60a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
144 changed files with 716 additions and 375 deletions

View file

@ -41,7 +41,7 @@ fun AudioManager.enableExternalAudioDevice() {
selectedDevice?.let { device ->
Timber.d("Audio device selected, type: ${device.type}")
tryOrNull(
onError = { failure ->
onException = { failure ->
Timber.e(failure, "Audio: exception when setting communication device")
}
) {

View file

@ -12,6 +12,7 @@ import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.core.net.toFile
import io.element.android.libraries.core.extensions.runCatchingExceptions
fun Context.getMimeType(uri: Uri): String? = when (uri.scheme) {
ContentResolver.SCHEME_CONTENT -> contentResolver.getType(uri)
@ -32,14 +33,14 @@ fun Context.getFileSize(uri: Uri): Long {
} ?: 0
}
private fun Context.getContentFileSize(uri: Uri): Long? = runCatching {
private fun Context.getContentFileSize(uri: Uri): Long? = runCatchingExceptions {
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
cursor.moveToFirst()
return@use cursor.getColumnIndexOrThrow(OpenableColumns.SIZE).let(cursor::getLong)
}
}.getOrNull()
private fun Context.getContentFileName(uri: Uri): String? = runCatching {
private fun Context.getContentFileName(uri: Uri): String? = runCatchingExceptions {
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
cursor.moveToFirst()
return@use cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString)

View file

@ -17,7 +17,7 @@ import java.util.UUID
fun File.safeDelete() {
if (exists().not()) return
tryOrNull(
onError = {
onException = {
Timber.e(it, "Error, unable to delete file $path")
},
operation = {
@ -30,7 +30,7 @@ fun File.safeDelete() {
fun File.safeRenameTo(dest: File) {
tryOrNull(
onError = {
onException = {
Timber.e(it, "Error, unable to rename file $path to ${dest.path}")
},
operation = {

View file

@ -13,6 +13,7 @@ import android.text.util.Linkify
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
import androidx.core.text.util.LinkifyCompat
import io.element.android.libraries.core.extensions.runCatchingExceptions
import timber.log.Timber
import kotlin.collections.component1
import kotlin.collections.component2
@ -48,7 +49,7 @@ object LinkifyHelper {
// Try to avoid including trailing punctuation in the link.
// Since this might fail in some edge cases, we catch the exception and just use the original end index.
val newEnd = runCatching {
val newEnd = runCatchingExceptions {
adjustLinkifiedUrlSpanEndIndex(spannable, start, end)
}.onFailure {
Timber.e(it, "Failed to adjust end index for link span")

View file

@ -15,6 +15,7 @@ android {
dependencies {
api(projects.libraries.di)
api(projects.libraries.core)
api(libs.dagger)
api(libs.appyx.core)
api(libs.androidx.lifecycle.runtime)

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.architecture
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import io.element.android.libraries.core.extensions.runCatchingExceptions
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -90,7 +91,7 @@ suspend inline fun <T> MutableState<AsyncAction<T>>.runCatchingUpdatingState(
state = this,
errorTransform = errorTransform,
resultBlock = {
runCatching {
runCatchingExceptions {
block()
}
},
@ -103,7 +104,7 @@ suspend inline fun <T> (suspend () -> T).runCatchingUpdatingState(
state = state,
errorTransform = errorTransform,
resultBlock = {
runCatching {
runCatchingExceptions {
this()
}
},

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.architecture
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import io.element.android.libraries.core.extensions.runCatchingExceptions
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -93,7 +94,7 @@ suspend inline fun <T> MutableState<AsyncData<T>>.runCatchingUpdatingState(
state = this,
errorTransform = errorTransform,
resultBlock = {
runCatching {
runCatchingExceptions {
block()
}
},
@ -106,7 +107,7 @@ suspend inline fun <T> (suspend () -> T).runCatchingUpdatingState(
state = state,
errorTransform = errorTransform,
resultBlock = {
runCatching {
runCatchingExceptions {
this()
}
},

View file

@ -38,15 +38,15 @@ class AsyncDataKtTest {
val result = runUpdatingState(state) {
delay(1)
Result.failure(MyThrowable("hello"))
Result.failure(MyException("hello"))
}
assertThat(result.isFailure).isTrue()
assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello"))
assertThat(result.exceptionOrNull()).isEqualTo(MyException("hello"))
assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized)
assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null))
assertThat(state.popFirst()).isEqualTo(AsyncData.Failure<Int>(MyThrowable("hello")))
assertThat(state.popFirst()).isEqualTo(AsyncData.Failure<Int>(MyException("hello")))
state.assertNoMoreValues()
}
@ -54,17 +54,17 @@ class AsyncDataKtTest {
fun `updates state when block returns failure transforming the error`() = runTest {
val state = TestableMutableState<AsyncData<Int>>(AsyncData.Uninitialized)
val result = runUpdatingState(state, { MyThrowable(it.message + " world") }) {
val result = runUpdatingState(state, { MyException(it.message + " world") }) {
delay(1)
Result.failure(MyThrowable("hello"))
Result.failure(MyException("hello"))
}
assertThat(result.isFailure).isTrue()
assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello world"))
assertThat(result.exceptionOrNull()).isEqualTo(MyException("hello world"))
assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized)
assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null))
assertThat(state.popFirst()).isEqualTo(AsyncData.Failure<Int>(MyThrowable("hello world")))
assertThat(state.popFirst()).isEqualTo(AsyncData.Failure<Int>(MyException("hello world")))
state.assertNoMoreValues()
}
}
@ -101,4 +101,4 @@ private class TestableMutableState<T>(
/**
* An exception that is also a data class so we can compare it using equals.
*/
private data class MyThrowable(val myMessage: String) : Throwable(myMessage)
private data class MyException(val myMessage: String) : Exception(myMessage)

View file

@ -7,11 +7,20 @@
package io.element.android.libraries.core.data
inline fun <A> tryOrNull(onError: ((Throwable) -> Unit) = { }, operation: () -> A): A? {
import kotlin.coroutines.cancellation.CancellationException
/**
* Can be used to catch [Exception]s in a block of code, returning `null` if an exception occurs.
*
* If the block throws a [CancellationException], it will be rethrown.
*/
inline fun <A> tryOrNull(onException: ((Exception) -> Unit) = { }, operation: () -> A): A? {
return try {
operation()
} catch (any: Throwable) {
onError.invoke(any)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
onException.invoke(e)
null
}
}

View file

@ -7,6 +7,61 @@
package io.element.android.libraries.core.extensions
import kotlin.coroutines.cancellation.CancellationException
/**
* Can be used to catch exceptions in a block of code and return a [Result].
* If the block throws a [CancellationException], it will be rethrown.
* If it throws any other exception, it will be wrapped in a [Result.failure].
*
* [Error]s are not caught by this function, as they are not meant to be caught in normal application flow.
*/
inline fun <T> runCatchingExceptions(
block: () -> T
): Result<T> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Result.failure(e)
}
}
/**
* Can be used to catch exceptions in a block of code and return a [Result].
* If the block throws a [CancellationException], it will be rethrown.
* If it throws any other exception, it will be wrapped in a [Result.failure].
*
* [Error]s are not caught by this function, as they are not meant to be caught in normal application flow.
*/
inline fun <T, R> T.runCatchingExceptions(
block: T.() -> R
): Result<R> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Result.failure(e)
}
}
/**
* Can be used to transform a [Result] into another [Result] by applying a [block] to the value if it is successful.
* If the original [Result] is a failure, the exception will be wrapped in a new [Result.failure].
*
* This is a safer version of [Result.mapCatching].
*/
inline fun <R, T> Result<T>.mapCatchingExceptions(
block: (T) -> R,
): Result<R> {
return fold(
onSuccess = { value -> runCatchingExceptions { block(value) } },
onFailure = { exception -> Result.failure(exception) }
)
}
/**
* Can be used to transform some Throwable into some other.
*/
@ -33,12 +88,16 @@ inline fun <R, T> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> {
* @return The result of the transform or a caught exception wrapped in a [Result].
*/
inline fun <R, T> Result<T>.flatMapCatching(transform: (T) -> Result<R>): Result<R> {
return mapCatching(transform).fold(
return mapCatchingExceptions(transform).fold(
onSuccess = { it },
onFailure = { Result.failure(it) }
)
}
/**
* Can be used to execute a block of code after the [Result] has been processed, regardless of whether it was successful or not.
* The block receives the exception if there was one, or `null` if the result was successful.
*/
inline fun <T> Result<T>.finally(block: (exception: Throwable?) -> Unit): Result<T> {
onSuccess { block(null) }
onFailure(block)

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.api.room.powerlevels
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
@ -60,7 +61,7 @@ suspend fun BaseRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sess
/**
* Shortcut for checking if current user can handle knock requests.
*/
suspend fun BaseRoom.canHandleKnockRequests(): Result<Boolean> = runCatching {
suspend fun BaseRoom.canHandleKnockRequests(): Result<Boolean> = runCatchingExceptions {
canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow()
}

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.DeviceId
@ -250,7 +251,7 @@ class RustMatrixClient(
}
override fun userIdServerName(): String {
return runCatching {
return runCatchingExceptions {
innerClient.userIdServerName()
}
.onFailure {
@ -261,7 +262,7 @@ class RustMatrixClient(
}
override suspend fun getUrl(url: String): Result<String> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.getUrl(url)
}
}
@ -301,19 +302,19 @@ class RustMatrixClient(
}
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.ignoreUser(userId.value)
}
}
override suspend fun unignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.unignoreUser(userId.value)
}
}
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
val rustParams = RustCreateRoomParameters(
name = createRoomParams.name,
topic = createRoomParams.topic,
@ -363,7 +364,7 @@ class RustMatrixClient(
}
override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.getProfile(userId.value).let(UserProfileMapper::map)
}
}
@ -373,28 +374,28 @@ class RustMatrixClient(
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map)
}
}
override suspend fun setDisplayName(displayName: String): Result<Unit> =
withContext(sessionDispatcher) {
runCatching { innerClient.setDisplayName(displayName) }
runCatchingExceptions { innerClient.setDisplayName(displayName) }
}
override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> =
withContext(sessionDispatcher) {
runCatching { innerClient.uploadAvatar(mimeType, data) }
runCatchingExceptions { innerClient.uploadAvatar(mimeType, data) }
}
override suspend fun removeAvatar(): Result<Unit> =
withContext(sessionDispatcher) {
runCatching { innerClient.removeAvatar() }
runCatchingExceptions { innerClient.removeAvatar() }
}
override suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.joinRoomById(roomId.value).destroy()
try {
awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.JOINED)
@ -406,7 +407,7 @@ class RustMatrixClient(
}.mapFailure { it.mapClientException() }
override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.joinRoomByIdOrAlias(
roomIdOrAlias = roomIdOrAlias.identifier,
serverNames = serverNames,
@ -423,7 +424,7 @@ class RustMatrixClient(
override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomSummary?> = withContext(
sessionDispatcher
) {
runCatching {
runCatchingExceptions {
innerClient.knock(roomIdOrAlias.identifier, message, serverNames).destroy()
try {
awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED)
@ -435,19 +436,19 @@ class RustMatrixClient(
}
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.trackRecentlyVisitedRoom(roomId.value)
}
}
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.getRecentlyVisitedRooms().map(::RoomId)
}
}
override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
val result = innerClient.resolveRoomAlias(roomAlias.value)?.let {
ResolvedRoomAlias(
roomId = RoomId(it.roomId),
@ -459,7 +460,7 @@ class RustMatrixClient(
}
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<NotJoinedRoom> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
when (roomIdOrAlias) {
is RoomIdOrAlias.Alias -> {
val roomId = innerClient.resolveRoomAlias(roomIdOrAlias.roomAlias.value)?.roomId?.let { RoomId(it) }
@ -556,7 +557,7 @@ class RustMatrixClient(
}
override fun canDeactivateAccount(): Boolean {
return runCatching {
return runCatchingExceptions {
innerClient.canDeactivateAccount()
}
.getOrNull()
@ -568,9 +569,9 @@ class RustMatrixClient(
// Remove current delegate so we don't receive an auth error
clientDelegateTaskHandle?.cancelAndDestroy()
clientDelegateTaskHandle = null
runCatching {
runCatchingExceptions {
// First call without AuthData, should fail
val firstAttempt = runCatching {
val firstAttempt = runCatchingExceptions {
innerClient.deactivateAccount(
authData = null,
eraseData = eraseData,
@ -579,7 +580,7 @@ class RustMatrixClient(
if (firstAttempt.isFailure) {
Timber.w(firstAttempt.exceptionOrNull(), "Expected failure, try again")
// This is expected, try again with the password
runCatching {
runCatchingExceptions {
innerClient.deactivateAccount(
authData = AuthData.Password(
passwordDetails = AuthDataPasswordDetails(
@ -606,13 +607,13 @@ class RustMatrixClient(
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> = withContext(sessionDispatcher) {
val rustAction = action?.toRustAction()
runCatching {
runCatchingExceptions {
innerClient.accountUrl(rustAction)
}
}
override suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.uploadMedia(mimeType, data, progressCallback?.toProgressWatcher())
}
}
@ -654,19 +655,19 @@ class RustMatrixClient(
}.buffer(Channel.UNLIMITED)
override suspend fun availableSlidingSyncVersions(): Result<List<SlidingSyncVersion>> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.availableSlidingSyncVersions().map { it.map() }
}
}
override suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion> = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.session().slidingSyncVersion.map()
}
}
override suspend fun canReportRoom(): Boolean = withContext(sessionDispatcher) {
runCatching {
runCatchingExceptions {
innerClient.isReportRoomApiSupported()
}.getOrDefault(false)
}

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
@ -91,7 +92,7 @@ class RustMatrixAuthenticationService @Inject constructor(
}
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> = withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
val sessionData = sessionStore.getSession(sessionId.value)
if (sessionData != null) {
if (sessionData.isTokenValid) {
@ -126,7 +127,7 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
withContext(coroutineDispatchers.io) {
val emptySessionPath = rotateSessionPath()
runCatching {
runCatchingExceptions {
val client = makeClient(sessionPaths = emptySessionPath) {
serverNameOrHomeserverUrl(homeserver)
}
@ -144,7 +145,7 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun login(username: String, password: String): Result<SessionId> =
withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first")
client.login(username, password, "Element X Android", null)
@ -170,7 +171,7 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun importCreatedSession(externalSession: ExternalSession): Result<SessionId> =
withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first")
val sessionData = externalSession.toSessionData(
@ -192,7 +193,7 @@ class RustMatrixAuthenticationService @Inject constructor(
loginHint: String?,
): Result<OidcDetails> {
return withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val oAuthAuthorizationData = client.urlForOidc(
oidcConfiguration = oidcConfigurationProvider.get(),
@ -211,7 +212,7 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun cancelOidcLogin(): Result<Unit> {
return withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
pendingOAuthAuthorizationData?.use {
currentClient?.abortOidcAuth(it)
}
@ -228,7 +229,7 @@ class RustMatrixAuthenticationService @Inject constructor(
*/
override suspend fun loginWithOidc(callbackUrl: String): Result<SessionId> {
return withContext(coroutineDispatchers.io) {
runCatching {
runCatchingExceptions {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first")
client.loginWithOidcCallback(callbackUrl)
@ -268,7 +269,7 @@ class RustMatrixAuthenticationService @Inject constructor(
progress(state.toStep())
}
}
runCatching {
runCatchingExceptions {
val client = makeQrCodeLoginClient(
sessionPaths = emptySessionPaths,
passphrase = pendingPassphrase,

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.auth.qrlogin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory
@ -17,6 +18,6 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class RustQrCodeLoginDataFactory @Inject constructor() : MatrixQrCodeLoginDataFactory {
override fun parseQrCodeData(data: ByteArray): Result<MatrixQrCodeLoginData> {
return runCatching { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) }
return runCatchingExceptions { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) }
}
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.call
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import org.matrix.rustcomponents.sdk.ElementWellKnown
import org.matrix.rustcomponents.sdk.makeElementWellKnown
@ -20,7 +21,7 @@ interface ElementWellKnownParser {
@ContributesBinding(AppScope::class)
class RustElementWellKnownParser @Inject constructor() : ElementWellKnownParser {
override fun parse(str: String): Result<ElementWellKnown> {
return runCatching {
return runCatchingExceptions {
makeElementWellKnown(str)
}
}

View file

@ -7,13 +7,14 @@
package io.element.android.libraries.matrix.impl.core
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.SendHandle
class RustSendHandle(
val inner: org.matrix.rustcomponents.sdk.SendHandle,
) : SendHandle {
override suspend fun retry(): Result<Unit> {
return runCatching {
return runCatchingExceptions {
inner.tryResend()
}
}

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.BackupState
@ -96,7 +97,7 @@ internal class RustEncryptionService(
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.enableBackups()
}.mapFailure {
it.mapRecoveryException()
@ -106,7 +107,7 @@ internal class RustEncryptionService(
override suspend fun enableRecovery(
waitForBackupsToUpload: Boolean,
): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.enableRecovery(
waitForBackupsToUpload = waitForBackupsToUpload,
progressListener = object : EnableRecoveryProgressListener {
@ -124,14 +125,14 @@ internal class RustEncryptionService(
}
override suspend fun doesBackupExistOnServer(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.backupExistsOnServer()
}
}
override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
return callbackFlow {
runCatching {
runCatchingExceptions {
service.waitForBackupUploadSteadyState(
progressListener = object : BackupSteadyStateListener {
override fun onUpdate(status: RustBackupUploadState) {
@ -155,7 +156,7 @@ internal class RustEncryptionService(
}
override suspend fun disableRecovery(): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.disableRecovery()
}.mapFailure {
it.mapRecoveryException()
@ -163,7 +164,7 @@ internal class RustEncryptionService(
}
private suspend fun isLastDevice(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.isLastDevice()
}.mapFailure {
it.mapRecoveryException()
@ -171,7 +172,7 @@ internal class RustEncryptionService(
}
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.resetRecoveryKey()
}.mapFailure {
it.mapRecoveryException()
@ -179,7 +180,7 @@ internal class RustEncryptionService(
}
override suspend fun recover(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
service.recover(recoveryKey)
}.mapFailure {
it.mapRecoveryException()
@ -187,34 +188,34 @@ internal class RustEncryptionService(
}
override suspend fun deviceCurve25519(): String? {
return runCatching { service.curve25519Key() }.getOrNull()
return runCatchingExceptions { service.curve25519Key() }.getOrNull()
}
override suspend fun deviceEd25519(): String? {
return runCatching { service.ed25519Key() }.getOrNull()
return runCatchingExceptions { service.ed25519Key() }.getOrNull()
}
override suspend fun startIdentityReset(): Result<IdentityResetHandle?> {
return runCatching {
return runCatchingExceptions {
service.resetIdentity()
}.flatMap { handle ->
RustIdentityResetHandleFactory.create(sessionId, handle)
}
}
override suspend fun isUserVerified(userId: UserId): Result<Boolean> = runCatching {
override suspend fun isUserVerified(userId: UserId): Result<Boolean> = runCatchingExceptions {
getUserIdentityInternal(userId).isVerified()
}
override suspend fun pinUserIdentity(userId: UserId): Result<Unit> = runCatching {
override suspend fun pinUserIdentity(userId: UserId): Result<Unit> = runCatchingExceptions {
getUserIdentityInternal(userId).pin()
}
override suspend fun withdrawVerification(userId: UserId): Result<Unit> = runCatching {
override suspend fun withdrawVerification(userId: UserId): Result<Unit> = runCatchingExceptions {
getUserIdentityInternal(userId).withdrawVerification()
}
override suspend fun getUserIdentity(userId: UserId): Result<IdentityState?> = runCatching {
override suspend fun getUserIdentity(userId: UserId): Result<IdentityState?> = runCatchingExceptions {
val identity = getUserIdentityInternal(userId)
val isVerified = identity.isVerified()
when {

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
@ -20,7 +21,7 @@ object RustIdentityResetHandleFactory {
userId: UserId,
identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle?
): Result<IdentityResetHandle?> {
return runCatching {
return runCatchingExceptions {
identityResetHandle?.let {
when (val authType = identityResetHandle.authType()) {
is CrossSigningResetAuthType.Oidc -> RustOidcIdentityResetHandle(identityResetHandle, authType.info.approvalUrl)
@ -37,7 +38,7 @@ class RustPasswordIdentityResetHandle(
private val identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle,
) : IdentityPasswordResetHandle {
override suspend fun resetPassword(password: String): Result<Unit> {
return runCatching { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) }
return runCatchingExceptions { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) }
}
override suspend fun cancel() {
@ -50,7 +51,7 @@ class RustOidcIdentityResetHandle(
override val url: String,
) : IdentityOidcResetHandle {
override suspend fun resetOidc(): Result<Unit> {
return runCatching { identityResetHandle.reset(null) }
return runCatchingExceptions { identityResetHandle.reset(null) }
}
override suspend fun cancel() {

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import java.io.File
@ -17,7 +18,7 @@ class MediaUploadHandlerImpl(
private val sendAttachmentJoinHandle: SendAttachmentJoinHandle,
) : MediaUploadHandler {
override suspend fun await(): Result<Unit> =
runCatching {
runCatchingExceptions {
sendAttachmentJoinHandle.join()
}
.also { cleanUpFiles() }

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaFile
@ -34,7 +35,7 @@ class RustMediaLoader(
@OptIn(ExperimentalUnsignedTypes::class)
override suspend fun loadMediaContent(source: MediaSource): Result<ByteArray> =
withContext(mediaDispatcher) {
runCatching {
runCatchingExceptions {
source.toRustMediaSource().use { source ->
innerClient.getMediaContent(source)
}
@ -48,7 +49,7 @@ class RustMediaLoader(
height: Long
): Result<ByteArray> =
withContext(mediaDispatcher) {
runCatching {
runCatchingExceptions {
source.toRustMediaSource().use { mediaSource ->
innerClient.getMediaThumbnail(
mediaSource = mediaSource,
@ -66,7 +67,7 @@ class RustMediaLoader(
useCache: Boolean,
): Result<MediaFile> =
withContext(mediaDispatcher) {
runCatching {
runCatchingExceptions {
source.toRustMediaSource().use { mediaSource ->
val mediaFile = innerClient.getMediaFile(
mediaSource = mediaSource,

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
@ -30,7 +31,7 @@ class RustNotificationService(
override suspend fun getNotifications(
ids: Map<RoomId, List<EventId>>
): Result<Map<EventId, NotificationData>> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
val requests = ids.map { (roomId, eventIds) ->
NotificationItemsRequest(
roomId = roomId.value,

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.notificationsettings
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.suspendLazy
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@ -48,12 +49,12 @@ class RustNotificationSettingsService(
}
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> =
runCatching {
runCatchingExceptions {
notificationSettings.await().getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map)
}
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationMode> =
runCatching {
runCatchingExceptions {
notificationSettings.await().getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode)
}
@ -62,7 +63,7 @@ class RustNotificationSettingsService(
mode: RoomNotificationMode,
isOneToOne: Boolean
): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
try {
notificationSettings.await().setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode))
} catch (exception: NotificationSettingsException.RuleNotFound) {
@ -74,13 +75,13 @@ class RustNotificationSettingsService(
}
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().setRoomNotificationMode(roomId.value, mode.let(RoomNotificationSettingsMapper::mapMode))
}
}
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().restoreDefaultRoomNotificationMode(roomId.value)
}
}
@ -88,54 +89,54 @@ class RustNotificationSettingsService(
override suspend fun muteRoom(roomId: RoomId): Result<Unit> = setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean) = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().unmuteRoom(roomId.value, isEncrypted, isOneToOne)
}
}
override suspend fun isRoomMentionEnabled(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().isRoomMentionEnabled()
}
}
override suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().setRoomMentionEnabled(enabled)
}
}
override suspend fun isCallEnabled(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().isCallEnabled()
}
}
override suspend fun setCallEnabled(enabled: Boolean): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().setCallEnabled(enabled)
}
}
override suspend fun isInviteForMeEnabled(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().isInviteForMeEnabled()
}
}
override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
notificationSettings.await().setInviteForMeEnabled(enabled)
}
}
override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> =
runCatching {
runCatchingExceptions {
notificationSettings.await().getRoomsWithUserDefinedRules(enabled = true)
}
override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result<Boolean> =
runCatching {
runCatchingExceptions {
notificationSettings.await().canPushEncryptedEventToDevice()
}
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.permalink
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.RoomAlias
@ -24,7 +25,7 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder {
if (!MatrixPatterns.isUserId(userId.value)) {
return Result.failure(PermalinkBuilderError.InvalidData)
}
return runCatching {
return runCatchingExceptions {
matrixToUserPermalink(userId.value)
}
}
@ -33,7 +34,7 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder {
if (!MatrixPatterns.isRoomAlias(roomAlias.value)) {
return Result.failure(PermalinkBuilderError.InvalidData)
}
return runCatching {
return runCatchingExceptions {
matrixToRoomAliasPermalink(roomAlias.value)
}
}

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.permalink
import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
@ -44,7 +45,7 @@ class DefaultPermalinkParser @Inject constructor(
// so convert URI to matrix.to to simplify parsing process
val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
val result = runCatching {
val result = runCatchingExceptions {
parseMatrixEntityFrom(matrixToUri.toString())
}.getOrNull()
return if (result == null) {

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.pushers
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
@ -26,7 +27,7 @@ class RustPushersService(
) : PushersService {
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result<Unit> {
return withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
client.setPusher(
identifiers = PusherIdentifiers(
pushkey = setHttpPusherData.pushKey,
@ -51,7 +52,7 @@ class RustPushersService(
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> {
return withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
client.deletePusher(
identifiers = PusherIdentifiers(
pushkey = unsetHttpPusherData.pushKey,

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
@ -204,7 +205,7 @@ class JoinedRustRoom(
// Track read receipts only for focused timeline for performance optimization
val trackReadReceipts = createTimelineParams is CreateTimelineParams.Focused
runCatching {
runCatchingExceptions {
innerRoom.timelineWithConfiguration(
configuration = TimelineConfiguration(
focus = focus,
@ -243,7 +244,7 @@ class JoinedRustRoom(
htmlBody: String?,
intentionalMentions: List<IntentionalMention>
): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
MessageEventContent.from(body, htmlBody, intentionalMentions).use { newContent ->
innerRoom.edit(eventId.value, newContent)
}
@ -251,43 +252,43 @@ class JoinedRustRoom(
}
override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.typingNotice(isTyping)
}
}
override suspend fun inviteUserById(id: UserId): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.inviteUserById(id.value)
}
}
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.uploadAvatar(mimeType, data, null)
}
}
override suspend fun removeAvatar(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.removeAvatar()
}
}
override suspend fun setName(name: String): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.setName(name)
}
}
override suspend fun setTopic(topic: String): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.setTopic(topic)
}
}
override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason)
if (blockUserId != null) {
innerRoom.ignoreUser(blockUserId.value)
@ -299,7 +300,7 @@ class JoinedRustRoom(
val currentState = roomNotificationSettingsStateFlow.value
val currentRoomNotificationSettings = currentState.roomNotificationSettings()
roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings)
runCatching {
runCatchingExceptions {
val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow()
notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
}.map {
@ -313,56 +314,56 @@ class JoinedRustRoom(
}
override suspend fun updateCanonicalAlias(canonicalAlias: RoomAlias?, alternativeAliases: List<RoomAlias>): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.updateCanonicalAlias(canonicalAlias?.value, alternativeAliases.map { it.value })
}
}
override suspend fun publishRoomAliasInRoomDirectory(roomAlias: RoomAlias): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.publishRoomAliasInRoomDirectory(roomAlias.value)
}
}
override suspend fun removeRoomAliasFromRoomDirectory(roomAlias: RoomAlias): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.removeRoomAliasFromRoomDirectory(roomAlias.value)
}
}
override suspend fun updateRoomVisibility(roomVisibility: RoomVisibility): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.updateRoomVisibility(roomVisibility.map())
}
}
override suspend fun updateHistoryVisibility(historyVisibility: RoomHistoryVisibility): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.updateHistoryVisibility(historyVisibility.map())
}
}
override suspend fun enableEncryption(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.enableEncryption()
}
}
override suspend fun updateJoinRule(joinRule: JoinRule): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.updateJoinRules(joinRule.map())
}
}
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> {
return runCatching {
return runCatchingExceptions {
val powerLevelChanges = changes.map { UserPowerLevelUpdate(it.userId.value, it.powerLevel) }
innerRoom.updatePowerLevelsForUsers(powerLevelChanges)
}
}
override suspend fun updatePowerLevels(roomPowerLevels: RoomPowerLevels): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
val changes = RoomPowerLevelChanges(
ban = roomPowerLevels.ban,
invite = roomPowerLevels.invite,
@ -378,25 +379,25 @@ class JoinedRustRoom(
}
override suspend fun resetPowerLevels(): Result<RoomPowerLevels> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
RoomPowerLevelsMapper.map(innerRoom.resetPowerLevels())
}
}
override suspend fun kickUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.kickUser(userId.value, reason)
}
}
override suspend fun banUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.banUser(userId.value, reason)
}
}
override suspend fun unbanUser(userId: UserId, reason: String?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.unbanUser(userId.value, reason)
}
}
@ -407,13 +408,13 @@ class JoinedRustRoom(
languageTag: String?,
theme: String?,
) = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
widgetSettings.generateWidgetWebViewUrl(innerRoom, clientId, languageTag, theme)
}
}
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> {
return runCatching {
return runCatchingExceptions {
RustWidgetDriver(
widgetSettings = widgetSettings,
room = innerRoom,
@ -427,7 +428,7 @@ class JoinedRustRoom(
}
override suspend fun sendCallNotificationIfNeeded(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.sendCallNotificationIfNeeded()
}
}
@ -435,14 +436,14 @@ class JoinedRustRoom(
override suspend fun setSendQueueEnabled(enabled: Boolean) {
withContext(roomDispatcher) {
Timber.d("setSendQueuesEnabled: $enabled")
runCatching {
runCatchingExceptions {
innerRoom.enableSendQueue(enabled)
}
}
}
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, sendHandle: SendHandle) = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.ignoreDeviceTrustAndResend(
devices = devices.entries.associate { entry ->
entry.key.value to entry.value.map { it.value }
@ -453,7 +454,7 @@ class JoinedRustRoom(
}
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, sendHandle: SendHandle) = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.withdrawVerificationAndResend(
userIds = userIds.map { it.value },
sendHandle = (sendHandle as RustSendHandle).inner,

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.room
import androidx.compose.runtime.Immutable
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
@ -20,8 +21,8 @@ class NotJoinedRustRoom(
override val localRoom: RustBaseRoom?,
override val previewInfo: RoomPreviewInfo,
) : NotJoinedRoom {
override suspend fun membershipDetails(): Result<RoomMembershipDetails?> = runCatching {
val room = localRoom?.innerRoom ?: return@runCatching null
override suspend fun membershipDetails(): Result<RoomMembershipDetails?> = runCatchingExceptions {
val room = localRoom?.innerRoom ?: return@runCatchingExceptions null
val (ownMember, senderInfo) = room.memberWithSenderInfo(sessionId.value)
RoomMembershipDetails(
currentUserMember = RoomMemberMapper.map(ownMember),

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.parallelMap
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.ForwardEventException
@ -52,7 +53,7 @@ class RoomContentForwarder(
val failedForwardingTo = mutableSetOf<RoomId>()
targetRooms.parallelMap { room ->
room.use { targetRoom ->
runCatching {
runCatchingExceptions {
// Sending a message requires a registered timeline listener
targetRoom.timeline().runWithTimelineListenerRegistered {
withTimeout(timeoutMs.milliseconds) {

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
@ -90,7 +91,7 @@ class RustBaseRoom(
}
override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.members().use {
it.nextChunk(limit.toUInt()).orEmpty().map { roomMember ->
RoomMemberMapper.map(roomMember)
@ -100,7 +101,7 @@ class RustBaseRoom(
}
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
RoomMemberMapper.map(innerRoom.member(userId.value))
}
}
@ -113,32 +114,32 @@ class RustBaseRoom(
}
override suspend fun userDisplayName(userId: UserId): Result<String?> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.memberDisplayName(userId.value)
}
}
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value))
}
}
override suspend fun powerLevels(): Result<RoomPowerLevels> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
RoomPowerLevelsMapper.map(innerRoom.getPowerLevels())
}
}
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.memberAvatarUrl(userId.value)
}
}
override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
val membershipBeforeLeft = roomInfoFlow.value.currentUserMembership
runCatching {
runCatchingExceptions {
innerRoom.leave()
}.onSuccess {
roomMembershipObserver.notifyUserLeftRoom(roomId, membershipBeforeLeft)
@ -146,142 +147,142 @@ class RustBaseRoom(
}
override suspend fun join(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.join()
}
}
override suspend fun forget(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.forget()
}
}
override suspend fun canUserInvite(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserInvite(userId.value)
}
}
override suspend fun canUserKick(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserKick(userId.value)
}
}
override suspend fun canUserBan(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserBan(userId.value)
}
}
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserRedactOwn(userId.value)
}
}
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserRedactOther(userId.value)
}
}
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserSendState(userId.value, type.map())
}
}
override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserSendMessage(userId.value, type.map())
}
}
override suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserTriggerRoomNotification(userId.value)
}
}
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.canUserPinUnpin(userId.value)
}
}
override suspend fun clearEventCacheStorage(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.clearEventCacheStorage()
}
}
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.setIsFavourite(isFavorite, null)
}
}
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.markAsRead(receiptType.toRustReceiptType())
}
}
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.setUnreadFlag(isUnread)
}
}
override suspend fun getPermalink(): Result<String> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.matrixToPermalink()
}
}
override suspend fun getPermalinkFor(eventId: EventId): Result<String> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.matrixToEventPermalink(eventId.value)
}
}
override suspend fun getRoomVisibility(): Result<RoomVisibility> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.getRoomVisibility().map()
}
}
override suspend fun getUpdatedIsEncrypted(): Result<Boolean> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
innerRoom.latestEncryptionState() == EncryptionState.ENCRYPTED
}
}
override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
Timber.d("saveComposerDraft: $composerDraft into $roomId")
innerRoom.saveComposerDraft(composerDraft.into())
}
}
override suspend fun loadComposerDraft(): Result<ComposerDraft?> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
Timber.d("loadComposerDraft for $roomId")
innerRoom.loadComposerDraft()?.into()
}
}
override suspend fun clearComposerDraft(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
Timber.d("clearComposerDraft for $roomId")
innerRoom.clearComposerDraft()
}
}
override suspend fun reportRoom(reason: String?): Result<Unit> = withContext(roomDispatcher) {
runCatching {
runCatchingExceptions {
Timber.d("reportRoom $roomId")
innerRoom.reportRoom(reason)
}

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.room.knock
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
@ -23,19 +24,19 @@ class RustKnockRequest(
override val timestamp: Long? = inner.timestamp?.toLong()
override val isSeen: Boolean = inner.isSeen
override suspend fun accept(): Result<Unit> = runCatching {
override suspend fun accept(): Result<Unit> = runCatchingExceptions {
inner.actions.accept()
}
override suspend fun decline(reason: String?): Result<Unit> = runCatching {
override suspend fun decline(reason: String?): Result<Unit> = runCatchingExceptions {
inner.actions.decline(reason)
}
override suspend fun declineAndBan(reason: String?): Result<Unit> = runCatching {
override suspend fun declineAndBan(reason: String?): Result<Unit> = runCatchingExceptions {
inner.actions.declineAndBan(reason)
}
override suspend fun markAsSeen(): Result<Unit> = runCatching {
override suspend fun markAsSeen(): Result<Unit> = runCatchingExceptions {
inner.actions.markAsSeen()
}
}

View file

@ -116,7 +116,7 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncI
internal fun RoomListServiceInterface.roomOrNull(roomId: String): Room? {
return tryOrNull(
onError = { Timber.e(it, "Failed finding room with id=$roomId.") }
onException = { Timber.e(it, "Failed finding room with id=$roomId.") }
) {
room(roomId)
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState
import kotlinx.coroutines.CoroutineDispatcher
@ -33,10 +34,10 @@ class RustSyncService(
private val isServiceReady = AtomicBoolean(true)
override suspend fun startSync() = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
if (!isServiceReady.get()) {
Timber.d("Can't start sync: service is not ready")
return@runCatching
return@runCatchingExceptions
}
Timber.i("Start sync")
inner.start()
@ -46,10 +47,10 @@ class RustSyncService(
}
override suspend fun stopSync() = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
if (!isServiceReady.get()) {
Timber.d("Can't stop sync: service is not ready")
return@runCatching
return@runCatchingExceptions
}
Timber.i("Stop sync")
inner.stop()

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
@ -165,7 +166,7 @@ class RustTimeline(
override val membershipChangeEventReceived: Flow<Unit> = timelineDiffProcessor.membershipChangeEventReceived
override suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.sendReadReceipt(receiptType.toRustReceiptType(), eventId.value)
}
}
@ -181,7 +182,7 @@ class RustTimeline(
override suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> = withContext(NonCancellable) {
withContext(dispatcher) {
initLatch.await()
runCatching {
runCatchingExceptions {
if (!canPaginate(direction)) throw TimelineException.CannotPaginate
updatePaginationStatus(direction) { it.copy(isPaginating = true) }
when (direction) {
@ -275,14 +276,14 @@ class RustTimeline(
intentionalMentions: List<IntentionalMention>,
): Result<Unit> = withContext(dispatcher) {
MessageEventContent.from(body, htmlBody, intentionalMentions).use { content ->
runCatching<Unit> {
runCatchingExceptions<Unit> {
inner.send(content)
}
}
}
override suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.redactEvent(
eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(),
reason = reason,
@ -296,7 +297,7 @@ class RustTimeline(
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
val editedContent = EditedContent.RoomMessage(
content = MessageEventContent.from(
body = body,
@ -316,7 +317,7 @@ class RustTimeline(
caption: String?,
formattedCaption: String?,
): Result<Unit> = withContext(dispatcher) {
runCatching<Unit> {
runCatchingExceptions<Unit> {
val editedContent = EditedContent.MediaCaption(
caption = caption,
formattedCaption = formattedCaption?.let {
@ -340,7 +341,7 @@ class RustTimeline(
intentionalMentions: List<IntentionalMention>,
fromNotification: Boolean,
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
val msg = MessageEventContent.from(body, htmlBody, intentionalMentions)
inner.sendReply(
msg = msg,
@ -462,7 +463,7 @@ class RustTimeline(
}
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.toggleReaction(
key = emoji,
itemId = eventOrTransactionId.toRustEventOrTransactionId(),
@ -471,7 +472,7 @@ class RustTimeline(
}
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
roomContentForwarder.forward(fromTimeline = inner, eventId = eventId, toRoomIds = roomIds)
}.onFailure {
Timber.e(it)
@ -485,7 +486,7 @@ class RustTimeline(
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.sendLocation(
body = body,
geoUri = geoUri,
@ -528,7 +529,7 @@ class RustTimeline(
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.createPoll(
question = question,
answers = answers,
@ -545,7 +546,7 @@ class RustTimeline(
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
val editedContent = EditedContent.PollStart(
pollData = PollData(
question = question,
@ -565,7 +566,7 @@ class RustTimeline(
pollStartId: EventId,
answers: List<String>
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.sendPollResponse(
pollStartEventId = pollStartId.value,
answers = answers,
@ -577,7 +578,7 @@ class RustTimeline(
pollStartId: EventId,
text: String
): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.endPoll(
pollStartEventId = pollStartId.value,
text = text,
@ -586,7 +587,7 @@ class RustTimeline(
}
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
return runCatching {
return runCatchingExceptions {
MediaUploadHandlerImpl(files, handle())
}
}
@ -609,19 +610,19 @@ class RustTimeline(
}
override suspend fun pinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.pinEvent(eventId = eventId.value)
}
}
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.unpinEvent(eventId = eventId.value)
}
}
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(dispatcher) {
runCatching {
runCatchingExceptions {
inner.fetchDetailsForEvent(eventId.value)
}
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.verification
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@ -100,7 +101,6 @@ class RustSessionVerificationService(
init {
// Instantiate the verification controller when possible, this is needed to get incoming verification requests
sessionCoroutineScope.launch {
// Needed to avoid crashes on unit tests due to the Rust SDK not being available
tryOrNull {
encryptionService.waitForE2eeInitializationTasks()
initVerificationControllerIfNeeded()
@ -152,7 +152,7 @@ class RustSessionVerificationService(
}
private suspend fun tryOrFail(block: suspend () -> Unit) {
runCatching {
runCatchingExceptions {
// Ensure the block cannot be cancelled, else if the Rust SDK emit a new state during the API execution,
// the state machine may cancel the api call.
withContext(NonCancellable) {
@ -184,7 +184,7 @@ class RustSessionVerificationService(
sessionCoroutineScope.launch {
// Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it returns false if run immediately
// It also sometimes unexpectedly fails to report the session as verified, so we have to handle that possibility and fail if needed
runCatching {
runCatchingExceptions {
withTimeout(20.seconds) {
// Wait until the SDK reports the state as verified
sessionVerifiedStatus.first { it == SessionVerifiedStatus.Verified }
@ -252,7 +252,7 @@ class RustSessionVerificationService(
}
private fun updateVerificationStatus() {
runCatching {
runCatchingExceptions {
_sessionVerifiedStatus.value = encryptionService.verificationState().map()
Timber.d("New verification status: ${_sessionVerifiedStatus.value}")
}

View file

@ -15,6 +15,7 @@ import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.IgnoredUsersListener
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
@ -23,9 +24,11 @@ import org.matrix.rustcomponents.sdk.PusherIdentifiers
import org.matrix.rustcomponents.sdk.PusherKind
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SessionVerificationController
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
import org.matrix.rustcomponents.sdk.UserProfile
class FakeRustClient(
private val userId: String = A_USER_ID.value,
@ -61,5 +64,16 @@ class FakeRustClient(
override suspend fun deletePusher(identifiers: PusherIdentifiers) = Unit
override suspend fun clearCaches() = simulateLongTask { clearCachesResult() }
override suspend fun setUtdDelegate(utdDelegate: UnableToDecryptDelegate) = withUtdHook(utdDelegate)
override suspend fun getSessionVerificationController(): SessionVerificationController = FakeRustSessionVerificationController()
override suspend fun ignoredUsers(): List<String> {
return emptyList()
}
override fun subscribeToIgnoredUsers(listener: IgnoredUsersListener): TaskHandle {
return FakeRustTaskHandle()
}
override suspend fun getProfile(userId: String): UserProfile {
return UserProfile(userId = userId, displayName = null, avatarUrl = null)
}
override fun close() = closeResult()
}

View file

@ -7,8 +7,12 @@
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.BackupState
import org.matrix.rustcomponents.sdk.BackupStateListener
import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RecoveryState
import org.matrix.rustcomponents.sdk.RecoveryStateListener
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.VerificationStateListener
@ -21,4 +25,22 @@ class FakeRustEncryption : Encryption(NoPointer) {
override fun recoveryStateListener(listener: RecoveryStateListener): TaskHandle {
return FakeRustTaskHandle()
}
override suspend fun waitForE2eeInitializationTasks() = simulateLongTask {}
override suspend fun isLastDevice(): Boolean {
return false
}
override fun backupState(): BackupState {
return BackupState.ENABLED
}
override fun recoveryState(): RecoveryState {
return RecoveryState.ENABLED
}
override fun backupStateListener(listener: BackupStateListener): TaskHandle {
return FakeRustTaskHandle()
}
}

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.RoomListServiceStateListener
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener
import org.matrix.rustcomponents.sdk.TaskHandle
@ -32,4 +33,8 @@ class FakeRustRoomListService : RoomListService(NoPointer) {
fun emitRoomListServiceSyncIndicator(syncIndicator: RoomListServiceSyncIndicator) {
listener?.onUpdate(syncIndicator)
}
override fun state(listener: RoomListServiceStateListener): TaskHandle {
return FakeRustTaskHandle()
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.SessionVerificationController
import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate
class FakeRustSessionVerificationController : SessionVerificationController(NoPointer) {
override fun setDelegate(delegate: SessionVerificationControllerDelegate?) {}
}

View file

@ -10,9 +10,15 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.SyncService
import org.matrix.rustcomponents.sdk.SyncServiceStateObserver
import org.matrix.rustcomponents.sdk.TaskHandle
class FakeRustSyncService(
private val roomListService: RoomListService = FakeRustRoomListService(),
) : SyncService(NoPointer) {
override fun roomListService(): RoomListService = roomListService
override fun state(listener: SyncServiceStateObserver): TaskHandle {
return FakeRustTaskHandle()
}
override suspend fun stop() {}
}

View file

@ -36,5 +36,9 @@ class FakeRustTimeline : Timeline(NoPointer) {
paginationStatusListener!!.onUpdate(status)
}
override suspend fun paginateBackwards(numEvents: UShort): Boolean {
return true
}
override suspend fun fetchMembers() = Unit
}

View file

@ -81,6 +81,7 @@ const val AN_AVATAR_URL = "mxc://data"
const val A_FAILURE_REASON = "There has been a failure"
@Suppress("unused")
val A_THROWABLE = Throwable(A_FAILURE_REASON)
val AN_EXCEPTION = Exception(A_FAILURE_REASON)

View file

@ -22,6 +22,7 @@ dependencies {
implementation(libs.dagger)
implementation(projects.libraries.audio.api)
implementation(projects.libraries.core)
implementation(projects.libraries.di)
implementation(libs.coroutines.core)

View file

@ -337,6 +337,7 @@ class DefaultMediaPlayerTest {
duration = null,
)
)
@Suppress("RunCatchingNotAllowed")
val result = runCatching {
sut.setMedia("uri", "mediaId", "mimeType", 12)
}

View file

@ -192,8 +192,10 @@ class MediaSender @Inject constructor(
}
}
// We handle the cancellations here manually, so we suppress the warning
@Suppress("RunCatchingNotAllowed")
return handler
.flatMapCatching { uploadHandler ->
.mapCatching { uploadHandler ->
ongoingUploadJobs[Job] = uploadHandler
uploadHandler.await()
}

View file

@ -21,6 +21,7 @@ import io.element.android.libraries.androidutils.media.runAndRelease
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage
@ -73,7 +74,7 @@ class AndroidMediaPreProcessor @Inject constructor(
deleteOriginal: Boolean,
compressIfPossible: Boolean,
): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) {
runCatching {
runCatchingExceptions {
val result = when {
// Special case for SVG, since Android can't read its metadata or create a thumbnail, it must be sent as a file
mimeType == MimeTypes.Svg -> {
@ -188,7 +189,7 @@ class AndroidMediaPreProcessor @Inject constructor(
}
private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo {
val resultFile = runCatching {
val resultFile = runCatchingExceptions {
videoCompressor.compress(uri, shouldBeCompressed)
.onEach {
// TODO handle progress

View file

@ -16,6 +16,7 @@ import io.element.android.libraries.androidutils.bitmap.resizeToMax
import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation
import io.element.android.libraries.androidutils.file.createTmpFile
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.ApplicationContext
import kotlinx.coroutines.withContext
import java.io.File
@ -38,7 +39,7 @@ class ImageCompressor @Inject constructor(
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
desiredQuality: Int = 78,
): Result<ImageCompressionResult> = withContext(dispatchers.io) {
runCatching {
runCatchingExceptions {
val format = mimeTypeToCompressFormat(mimeType)
val extension = mimeTypeToCompressFileExtension(mimeType)
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
@ -65,7 +66,7 @@ class ImageCompressor @Inject constructor(
inputStreamProvider: () -> InputStream,
resizeMode: ResizeMode,
orientation: Int,
): Result<Bitmap> = runCatching {
): Result<Bitmap> = runCatchingExceptions {
val options = BitmapFactory.Options()
// Decode bounds
inputStreamProvider().use { input ->

View file

@ -22,6 +22,7 @@ import com.otaliastudios.transcoder.validator.WriteAlwaysValidator
import io.element.android.libraries.androidutils.file.createTmpFile
import io.element.android.libraries.androidutils.file.getMimeType
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
@ -81,7 +82,7 @@ class VideoCompressor @Inject constructor(
}
private fun getVideoMetadata(uri: Uri): VideoFileMetadata? {
return runCatching {
return runCatchingExceptions {
MediaMetadataRetriever().use {
it.setDataSource(context, uri)

View file

@ -22,6 +22,7 @@ import dagger.assisted.AssistedInject
import io.element.android.libraries.androidutils.R
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
@ -154,7 +155,7 @@ class MediaGalleryPresenter @AssistedInject constructor(
mimeType = mediaItem.mediaInfo().mimeType,
filename = mediaItem.mediaInfo().filename
)
.mapCatching { mediaFile ->
.mapCatchingExceptions { mediaFile ->
localMediaFactory.createFromMediaFile(
mediaFile = mediaFile,
mediaInfo = mediaItem.mediaInfo()
@ -164,7 +165,7 @@ class MediaGalleryPresenter @AssistedInject constructor(
private suspend fun saveOnDisk(mediaItem: MediaItem.Event) {
downloadMedia(mediaItem)
.mapCatching { localMedia ->
.mapCatchingExceptions { localMedia ->
localMediaActions.saveOnDisk(localMedia)
}
.onSuccess {
@ -179,7 +180,7 @@ class MediaGalleryPresenter @AssistedInject constructor(
private suspend fun share(mediaItem: MediaItem.Event) {
downloadMedia(mediaItem)
.mapCatching { localMedia ->
.mapCatchingExceptions { localMedia ->
localMediaActions.share(localMedia)
}
.onFailure {

View file

@ -32,6 +32,7 @@ import androidx.core.net.toFile
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.AppScope
@ -83,7 +84,7 @@ class AndroidLocalMediaActions @Inject constructor(
override suspend fun saveOnDisk(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) {
require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE)
runCatching {
runCatchingExceptions {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
saveOnDiskUsingMediaStore(localMedia)
} else {
@ -98,7 +99,7 @@ class AndroidLocalMediaActions @Inject constructor(
override suspend fun share(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) {
require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE)
runCatching {
runCatchingExceptions {
val shareableUri = localMedia.toShareableUri()
val shareMediaIntent = Intent(Intent.ACTION_SEND)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
@ -117,7 +118,7 @@ class AndroidLocalMediaActions @Inject constructor(
override suspend fun open(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) {
require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE)
runCatching {
runCatchingExceptions {
when (localMedia.info.mimeType) {
MimeTypes.Apk -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View file

@ -10,10 +10,11 @@ package io.element.android.libraries.mediaviewer.impl.local.pdf
import android.content.Context
import android.net.Uri
import android.os.ParcelFileDescriptor
import io.element.android.libraries.core.extensions.runCatchingExceptions
import java.io.File
class ParcelFileDescriptorFactory(private val context: Context) {
fun create(model: Any?) = runCatching {
fun create(model: Any?) = runCatchingExceptions {
when (model) {
is File -> ParcelFileDescriptor.open(model, ParcelFileDescriptor.MODE_READ_ONLY)
is Uri -> context.contentResolver.openFileDescriptor(model, "r")!!

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.local.pdf
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.extensions.runCatchingExceptions
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
@ -35,7 +36,7 @@ class PdfRendererManager(
coroutineScope.launch {
mutex.withLock {
withContext(Dispatchers.IO) {
pdfRenderer = runCatching {
pdfRenderer = runCatchingExceptions {
PdfRenderer(parcelFileDescriptor)
}.fold(
onSuccess = { pdfRenderer ->

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.viewfolder.api.TextFileViewer
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
@ -43,7 +44,7 @@ fun TextFileView(
data.value = AsyncData.Loading()
if (localMedia?.uri != null) {
// Load the file content
val result = runCatching {
val result = runCatchingExceptions {
context.contentResolver.openInputStream(localMedia.uri).use {
it?.bufferedReader()?.readLines()?.toList().orEmpty()
}

View file

@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaFile
import io.element.android.libraries.matrix.api.timeline.Timeline
@ -173,7 +174,7 @@ class MediaViewerDataSource(
.onSuccess { mediaFile ->
mediaFiles.add(mediaFile)
}
.mapCatching { mediaFile ->
.mapCatchingExceptions { mediaFile ->
localMediaFactory.createFromMediaFile(
mediaFile = mediaFile,
mediaInfo = data.mediaInfo

View file

@ -14,7 +14,7 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.oidc.api.OidcAction
@ -68,7 +68,7 @@ class OidcPresenterTest {
A_OIDC_DATA,
authenticationService,
)
authenticationService.givenOidcCancelError(A_THROWABLE)
authenticationService.givenOidcCancelError(AN_EXCEPTION)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -77,7 +77,7 @@ class OidcPresenterTest {
val loadingState = awaitItem()
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
val finalState = awaitItem()
assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE))
assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
// Note: in real life I do not think this can happen, and the app should not block the user.
}
}
@ -124,7 +124,7 @@ class OidcPresenterTest {
A_OIDC_DATA,
authenticationService,
)
authenticationService.givenLoginError(A_THROWABLE)
authenticationService.givenLoginError(AN_EXCEPTION)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -133,7 +133,7 @@ class OidcPresenterTest {
val loadingState = awaitItem()
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
val errorState = awaitItem()
assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE))
assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
errorState.eventSink.invoke(OidcEvents.ClearError)
val finalState = awaitItem()
assertThat(finalState.requestState).isEqualTo(AsyncAction.Uninitialized)

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.push.impl.notifications
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
@ -54,13 +55,13 @@ class DefaultCallNotificationEventResolver @Inject constructor(
sessionId: SessionId,
notificationData: NotificationData,
forceNotify: Boolean
): Result<NotifiableEvent> = runCatching {
): Result<NotifiableEvent> = runCatchingExceptions {
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
?: throw ResolvingException("content is not a call notify")
val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value
// We need the sync service working to get the updated room info
val isRoomCallActive = runCatching {
val isRoomCallActive = runCatchingExceptions {
if (content.type == CallNotifyType.RING) {
appForegroundStateService.updateHasRingingCall(true)

View file

@ -11,6 +11,8 @@ import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
@ -90,7 +92,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
val ids = notificationEventRequests.groupBy { it.roomId }.mapValues { (_, value) -> value.map { it.eventId } }
// TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event
val notifications = client.notificationService().getNotifications(ids).mapCatching { map ->
val notifications = client.notificationService().getNotifications(ids).mapCatchingExceptions { map ->
map.mapValues { (_, notificationData) ->
notificationData.asNotifiableEvent(client, sessionId)
}
@ -112,7 +114,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
private suspend fun NotificationData.asNotifiableEvent(
client: MatrixClient,
userId: SessionId,
): Result<ResolvedPushEvent> = runCatching {
): Result<ResolvedPushEvent> = runCatchingExceptions {
when (val content = this.content) {
is NotificationContent.MessageLike.RoomMessage -> {
val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId)

View file

@ -208,7 +208,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
private suspend fun MatrixClient.getSafeUserProfile(): MatrixUser {
return tryOrNull(
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
onException = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
operation = {
val profile = getUserProfile().getOrNull()
// displayName cannot be empty else NotificationCompat.MessagingStyle() will crash

View file

@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.matrix.api.MatrixClient
@ -85,7 +86,7 @@ class DefaultNotificationMediaRepo @AssistedInject constructor(
source = mediaSource,
mimeType = mimeType,
filename = filename,
).mapCatching {
).mapCatchingExceptions {
it.use { mediaFile ->
val dest = cachedFile.apply { parentFile?.mkdirs() }
if (mediaFile.persist(dest.path)) {

View file

@ -63,6 +63,7 @@ class NotificationTest @Inject constructor(
notificationClickHandler.state.first()
Timber.d("Notification clicked!")
}
@Suppress("RunCatchingNotAllowed")
runCatching {
withTimeout(30.seconds) {
job.join()

View file

@ -76,6 +76,7 @@ class PushLoopbackTest @Inject constructor(
job.cancel()
return
}
@Suppress("RunCatchingNotAllowed")
runCatching {
withTimeout(10.seconds) {
completable.await()

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.pushproviders.firebase
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ -24,7 +25,7 @@ class DefaultFirebaseTokenRotator @Inject constructor(
private val firebaseTokenGetter: FirebaseTokenGetter,
) : FirebaseTokenRotator {
override suspend fun rotate(): Result<Unit> {
return runCatching {
return runCatchingExceptions {
firebaseTokenDeleter.delete()
firebaseTokenGetter.get()
}

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.pushproviders.firebase
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ -24,7 +25,7 @@ class DefaultFirebaseTroubleshooter @Inject constructor(
private val firebaseTokenGetter: FirebaseTokenGetter,
) : FirebaseTroubleshooter {
override suspend fun troubleshoot(): Result<Unit> {
return runCatching {
return runCatchingExceptions {
val token = firebaseTokenGetter.get()
newTokenHandler.handle(token)
}

View file

@ -35,6 +35,7 @@ class DefaultRegisterUnifiedPushUseCase @Inject constructor(
// VectorUnifiedPushMessagingReceiver.onNewEndpoint
UnifiedPush.register(context = context, instance = clientSecret)
// Wait for VectorUnifiedPushMessagingReceiver.onNewEndpoint to proceed
@Suppress("RunCatchingNotAllowed")
return runCatching {
withTimeout(30.seconds) {
val result = endpointRegistrationHandler.state

View file

@ -36,7 +36,7 @@ class DefaultUnifiedPushGatewayResolver @Inject constructor(
) : UnifiedPushGatewayResolver {
override suspend fun getGateway(endpoint: String): UnifiedPushGatewayResolverResult {
val url = tryOrNull(
onError = { Timber.tag("DefaultUnifiedPushGatewayResolver").d(it, "Cannot parse endpoint as an URL") }
onException = { Timber.tag("DefaultUnifiedPushGatewayResolver").d(it, "Cannot parse endpoint as an URL") }
) {
URL(endpoint)
}

View file

@ -50,7 +50,7 @@ internal class MatrixUserListDataSourceTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenSearchUsersResult(
searchTerm = "test",
result = Result.failure(Throwable("Ruhroh"))
result = Result.failure(RuntimeException("Ruhroh"))
)
val dataSource = MatrixUserListDataSource(matrixClient)
@ -76,7 +76,7 @@ internal class MatrixUserListDataSourceTest {
val matrixClient = FakeMatrixClient()
matrixClient.givenGetProfileResult(
userId = A_USER_ID,
result = Result.failure(Throwable("Ruhroh"))
result = Result.failure(RuntimeException("Ruhroh"))
)
val dataSource = MatrixUserListDataSource(matrixClient)

View file

@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
@ -80,7 +81,7 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
source = mediaSource,
mimeType = mimeType,
filename = filename,
).mapCatching {
).mapCatchingExceptions {
it.use { mediaFile ->
val dest = cachedFile.apply { parentFile?.mkdirs() }
if (mediaFile.persist(dest.path)) {

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.voiceplayer.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
@ -180,7 +181,7 @@ class DefaultVoiceMessagePlayer(
}.distinctUntilChanged()
override suspend fun prepare(): Result<Unit> = if (eventId != null) {
repo.getMediaFile().mapCatching<Unit, File> { mediaFile ->
repo.getMediaFile().mapCatchingExceptions<Unit, File> { mediaFile ->
val state = internalState.value
mediaPlayer.setMedia(
uri = mediaFile.path,

View file

@ -17,6 +17,7 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.ui.utils.time.formatShort
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents
@ -101,7 +102,7 @@ class VoiceMessagePresenter(
},
) {
player.prepare().flatMap {
runCatching { player.play() }
runCatchingExceptions { player.play() }
}
}
}