Merge pull request #3886 from element-hq/feature/bma/fixSendQueueCrash

fix : protect some usages of client to avoid crashes
This commit is contained in:
ganfra 2024-11-22 13:11:45 +01:00 committed by GitHub
commit 126181a8b2
14 changed files with 156 additions and 111 deletions

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
@ -102,10 +103,7 @@ class LoggedInPresenter @Inject constructor(
} }
} }
LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch { LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch {
// Force the user to log out if they were using the proxy sliding sync and it's no longer available, but native sliding sync is. forceNativeSlidingSyncMigration = matrixClient.forceNativeSlidingSyncMigration().getOrDefault(false)
forceNativeSlidingSyncMigration = !matrixClient.isUsingNativeSlidingSync() &&
matrixClient.isNativeSlidingSyncSupported() &&
!matrixClient.isSlidingSyncProxySupported()
} }
LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch { LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch {
// Enable native sliding sync if it wasn't already the case // Enable native sliding sync if it wasn't already the case
@ -125,6 +123,18 @@ class LoggedInPresenter @Inject constructor(
) )
} }
// Force the user to log out if they were using the proxy sliding sync and it's no longer available, but native sliding sync is.
private suspend fun MatrixClient.forceNativeSlidingSyncMigration(): Result<Boolean> = runCatching {
val currentSlidingSyncVersion = currentSlidingSyncVersion().getOrThrow()
if (currentSlidingSyncVersion == SlidingSyncVersion.Proxy) {
val availableSlidingSyncVersions = availableSlidingSyncVersions().getOrThrow()
availableSlidingSyncVersions.contains(SlidingSyncVersion.Native) &&
!availableSlidingSyncVersions.contains(SlidingSyncVersion.Proxy)
} else {
false
}
}
private suspend fun ensurePusherIsRegistered(pusherRegistrationState: MutableState<AsyncData<Unit>>) { private suspend fun ensurePusherIsRegistered(pusherRegistrationState: MutableState<AsyncData<Unit>>) {
Timber.tag(pusherTag.value).d("Ensure pusher is registered") Timber.tag(pusherTag.value).d("Ensure pusher is registered")
val currentPushProvider = pushService.getCurrentPushProvider() val currentPushProvider = pushService.getCurrentPushProvider()

View file

@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
@ -501,9 +502,8 @@ class LoggedInPresenterTest {
// - The sliding sync proxy is no longer supported // - The sliding sync proxy is no longer supported
// - The native sliding sync is supported // - The native sliding sync is supported
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
isUsingNativeSlidingSyncLambda = { false }, currentSlidingSyncVersionLambda = { Result.success(SlidingSyncVersion.Proxy) },
isSlidingSyncProxySupportedLambda = { false }, availableSlidingSyncVersionsLambda = { Result.success(listOf(SlidingSyncVersion.Native)) },
isNativeSlidingSyncSupportedLambda = { true },
) )
val presenter = createLoggedInPresenter(matrixClient = matrixClient) val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -521,9 +521,8 @@ class LoggedInPresenterTest {
@Test @Test
fun `present - CheckSlidingSyncProxyAvailability will not force the migration if native sliding sync is not supported too`() = runTest { fun `present - CheckSlidingSyncProxyAvailability will not force the migration if native sliding sync is not supported too`() = runTest {
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
isUsingNativeSlidingSyncLambda = { false }, currentSlidingSyncVersionLambda = { Result.success(SlidingSyncVersion.Proxy) },
isSlidingSyncProxySupportedLambda = { false }, availableSlidingSyncVersionsLambda = { Result.success(emptyList()) },
isNativeSlidingSyncSupportedLambda = { false },
) )
val presenter = createLoggedInPresenter(matrixClient = matrixClient) val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {

View file

@ -39,7 +39,6 @@ import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
import io.element.android.features.roomlist.impl.search.RoomListSearchState import io.element.android.features.roomlist.impl.search.RoomListSearchState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
@ -51,6 +50,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationCleaner
@ -231,10 +231,7 @@ class RoomListPresenter @Inject constructor(
} }
} }
val needsSlidingSyncMigration by produceState(false) { val needsSlidingSyncMigration by produceState(false) {
value = runCatching { value = client.needsSlidingSyncMigration().getOrDefault(false)
// Note: this can fail when the session is destroyed from another client.
client.isNativeSlidingSyncSupported() && !client.isUsingNativeSlidingSync()
}.getOrNull().orFalse()
} }
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed, needsSlidingSyncMigration) val securityBannerState by rememberSecurityBannerState(securityBannerDismissed, needsSlidingSyncMigration)
return when { return when {
@ -315,6 +312,19 @@ class RoomListPresenter @Inject constructor(
} }
} }
/**
* Checks if the user needs to migrate to a native sliding sync version.
*/
private suspend fun MatrixClient.needsSlidingSyncMigration(): Result<Boolean> = runCatching {
val currentSlidingSyncVersion = currentSlidingSyncVersion().getOrThrow()
if (currentSlidingSyncVersion != SlidingSyncVersion.Native) {
val availableSlidingSyncVersions = availableSlidingSyncVersions().getOrThrow()
availableSlidingSyncVersions.contains(SlidingSyncVersion.Native)
} else {
false
}
}
private var currentUpdateVisibleRangeJob: Job? = null private var currentUpdateVisibleRangeJob: Job? = null
private fun CoroutineScope.updateVisibleRange(range: IntRange) { private fun CoroutineScope.updateVisibleRange(range: IntRange) {
currentUpdateVisibleRangeJob?.cancel() currentUpdateVisibleRangeJob?.cancel()

View file

@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -145,14 +146,15 @@ interface MatrixClient : Closeable {
suspend fun getUrl(url: String): Result<String> suspend fun getUrl(url: String): Result<String>
suspend fun getRoomPreviewInfo(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreviewInfo> suspend fun getRoomPreviewInfo(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreviewInfo>
/** Returns `true` if the home server supports native sliding sync. */ /**
suspend fun isNativeSlidingSyncSupported(): Boolean * Returns the currently used sliding sync version.
*/
suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion>
/** Returns `true` if the home server supports sliding sync using a proxy. */ /**
suspend fun isSlidingSyncProxySupported(): Boolean * Returns the available sliding sync versions for the current user.
*/
/** Returns `true` if the current session is using native sliding sync, `false` if it's using a proxy. */ suspend fun availableSlidingSyncVersions(): Result<List<SlidingSyncVersion>>
fun isUsingNativeSlidingSync(): Boolean
fun canDeactivateAccount(): Boolean fun canDeactivateAccount(): Boolean
suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit> suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>

View file

@ -0,0 +1,14 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.api.sync
sealed interface SlidingSyncVersion {
data object None : SlidingSyncVersion
data object Proxy : SlidingSyncVersion
data object Native : SlidingSyncVersion
}

View file

@ -12,6 +12,7 @@ import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
@ -41,6 +42,7 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
@ -62,6 +64,7 @@ import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryS
import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory
import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService
import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.sync.RustSyncService
import io.element.android.libraries.matrix.impl.sync.map
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.util.SessionPathsProvider import io.element.android.libraries.matrix.impl.util.SessionPathsProvider
@ -100,7 +103,6 @@ import org.matrix.rustcomponents.sdk.IgnoredUsersListener
import org.matrix.rustcomponents.sdk.NotificationProcessSetup import org.matrix.rustcomponents.sdk.NotificationProcessSetup
import org.matrix.rustcomponents.sdk.PowerLevels import org.matrix.rustcomponents.sdk.PowerLevels
import org.matrix.rustcomponents.sdk.SendQueueRoomErrorListener import org.matrix.rustcomponents.sdk.SendQueueRoomErrorListener
import org.matrix.rustcomponents.sdk.SlidingSyncVersion
import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
@ -116,44 +118,44 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
class RustMatrixClient( class RustMatrixClient(
private val client: Client, private val innerClient: Client,
private val baseDirectory: File, private val baseDirectory: File,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val appCoroutineScope: CoroutineScope, private val appCoroutineScope: CoroutineScope,
private val sessionDelegate: RustClientSessionDelegate, private val sessionDelegate: RustClientSessionDelegate,
syncService: ClientSyncService, innerSyncService: ClientSyncService,
dispatchers: CoroutineDispatchers, dispatchers: CoroutineDispatchers,
baseCacheDirectory: File, baseCacheDirectory: File,
clock: SystemClock, clock: SystemClock,
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
featureFlagService: FeatureFlagService, featureFlagService: FeatureFlagService,
) : MatrixClient { ) : MatrixClient {
override val sessionId: UserId = UserId(client.userId()) override val sessionId: UserId = UserId(innerClient.userId())
override val deviceId: DeviceId = DeviceId(client.deviceId()) override val deviceId: DeviceId = DeviceId(innerClient.deviceId())
override val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId") override val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId")
private val innerRoomListService = syncService.roomListService()
private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionDispatcher = dispatchers.io.limitedParallelism(64)
private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) private val innerRoomListService = innerSyncService.roomListService()
private val rustSyncService = RustSyncService(innerSyncService, sessionCoroutineScope)
private val pushersService = RustPushersService( private val pushersService = RustPushersService(
client = client, client = innerClient,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService) private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(innerSyncService)
private val notificationClient = runBlocking { client.notificationClient(notificationProcessSetup) } private val innerNotificationClient = runBlocking { innerClient.notificationClient(notificationProcessSetup) }
private val notificationService = RustNotificationService(notificationClient, dispatchers, clock) private val notificationService = RustNotificationService(innerNotificationClient, dispatchers, clock)
private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers) private val notificationSettingsService = RustNotificationSettingsService(innerClient, dispatchers)
.apply { start() } .apply { start() }
private val encryptionService = RustEncryptionService( private val encryptionService = RustEncryptionService(
client = client, client = innerClient,
syncService = rustSyncService, syncService = rustSyncService,
sessionCoroutineScope = sessionCoroutineScope, sessionCoroutineScope = sessionCoroutineScope,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
private val roomDirectoryService = RustRoomDirectoryService( private val roomDirectoryService = RustRoomDirectoryService(
client = client, client = innerClient,
sessionDispatcher = sessionDispatcher, sessionDispatcher = sessionDispatcher,
) )
@ -173,13 +175,12 @@ class RustMatrixClient(
) )
private val verificationService = RustSessionVerificationService( private val verificationService = RustSessionVerificationService(
client = client, client = innerClient,
isSyncServiceReady = rustSyncService.syncState.map { it == SyncState.Running }, isSyncServiceReady = rustSyncService.syncState.map { it == SyncState.Running },
sessionCoroutineScope = sessionCoroutineScope, sessionCoroutineScope = sessionCoroutineScope,
) )
private val roomMembershipObserver = RoomMembershipObserver() private val roomMembershipObserver = RoomMembershipObserver()
private val roomFactory = RustRoomFactory( private val roomFactory = RustRoomFactory(
roomListService = roomListService, roomListService = roomListService,
innerRoomListService = innerRoomListService, innerRoomListService = innerRoomListService,
@ -199,24 +200,24 @@ class RustMatrixClient(
override val mediaLoader: MatrixMediaLoader = RustMediaLoader( override val mediaLoader: MatrixMediaLoader = RustMediaLoader(
baseCacheDirectory = baseCacheDirectory, baseCacheDirectory = baseCacheDirectory,
dispatchers = dispatchers, dispatchers = dispatchers,
innerClient = client, innerClient = innerClient,
) )
private var clientDelegateTaskHandle: TaskHandle? = client.setDelegate(sessionDelegate) private var clientDelegateTaskHandle: TaskHandle? = innerClient.setDelegate(sessionDelegate)
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow( private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(
MatrixUser( MatrixUser(
userId = sessionId, userId = sessionId,
// TODO cache for displayName? // TODO cache for displayName?
displayName = null, displayName = null,
avatarUrl = client.cachedAvatarUrl(), avatarUrl = innerClient.cachedAvatarUrl(),
) )
) )
override val userProfile: StateFlow<MatrixUser> = _userProfile override val userProfile: StateFlow<MatrixUser> = _userProfile
override val ignoredUsersFlow = mxCallbackFlow<ImmutableList<UserId>> { override val ignoredUsersFlow = mxCallbackFlow<ImmutableList<UserId>> {
client.subscribeToIgnoredUsers(object : IgnoredUsersListener { innerClient.subscribeToIgnoredUsers(object : IgnoredUsersListener {
override fun call(ignoredUserIds: List<String>) { override fun call(ignoredUserIds: List<String>) {
channel.trySend(ignoredUserIds.map(::UserId).toPersistentList()) channel.trySend(ignoredUserIds.map(::UserId).toPersistentList())
} }
@ -237,7 +238,7 @@ class RustMatrixClient(
override fun userIdServerName(): String { override fun userIdServerName(): String {
return runCatching { return runCatching {
client.userIdServerName() innerClient.userIdServerName()
} }
.onFailure { .onFailure {
Timber.w(it, "Failed to get userIdServerName") Timber.w(it, "Failed to get userIdServerName")
@ -248,7 +249,7 @@ class RustMatrixClient(
override suspend fun getUrl(url: String): Result<String> = withContext(sessionDispatcher) { override suspend fun getUrl(url: String): Result<String> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.getUrl(url) innerClient.getUrl(url)
} }
} }
@ -278,23 +279,23 @@ class RustMatrixClient(
.filter { roomSummary -> roomSummary.info.currentUserMembership == currentUserMembership } .filter { roomSummary -> roomSummary.info.currentUserMembership == currentUserMembership }
.first() .first()
// Ensure that the room is ready // Ensure that the room is ready
.also { client.awaitRoomRemoteEcho(it.roomId.value) } .also { innerClient.awaitRoomRemoteEcho(it.roomId.value) }
} }
} }
override suspend fun findDM(userId: UserId): RoomId? { override suspend fun findDM(userId: UserId): RoomId? {
return client.getDmRoom(userId.value)?.use { RoomId(it.id()) } return innerClient.getDmRoom(userId.value)?.use { RoomId(it.id()) }
} }
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) { override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.ignoreUser(userId.value) innerClient.ignoreUser(userId.value)
} }
} }
override suspend fun unignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) { override suspend fun unignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.unignoreUser(userId.value) innerClient.unignoreUser(userId.value)
} }
} }
@ -337,7 +338,7 @@ class RustMatrixClient(
}, },
canonicalAlias = createRoomParams.roomAliasName.getOrNull(), canonicalAlias = createRoomParams.roomAliasName.getOrNull(),
) )
val roomId = RoomId(client.createRoom(rustParams)) val roomId = RoomId(innerClient.createRoom(rustParams))
// Wait to receive the room back from the sync but do not returns failure if it fails. // Wait to receive the room back from the sync but do not returns failure if it fails.
try { try {
awaitRoom(roomId.toRoomIdOrAlias(), 30.seconds, CurrentUserMembership.JOINED) awaitRoom(roomId.toRoomIdOrAlias(), 30.seconds, CurrentUserMembership.JOINED)
@ -362,7 +363,7 @@ class RustMatrixClient(
override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(sessionDispatcher) { override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.getProfile(userId.value).let(UserProfileMapper::map) innerClient.getProfile(userId.value).let(UserProfileMapper::map)
} }
} }
@ -372,28 +373,28 @@ class RustMatrixClient(
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> = override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
runCatching { runCatching {
client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map) innerClient.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map)
} }
} }
override suspend fun setDisplayName(displayName: String): Result<Unit> = override suspend fun setDisplayName(displayName: String): Result<Unit> =
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
runCatching { client.setDisplayName(displayName) } runCatching { innerClient.setDisplayName(displayName) }
} }
override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> = override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> =
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
runCatching { client.uploadAvatar(mimeType, data) } runCatching { innerClient.uploadAvatar(mimeType, data) }
} }
override suspend fun removeAvatar(): Result<Unit> = override suspend fun removeAvatar(): Result<Unit> =
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
runCatching { client.removeAvatar() } runCatching { innerClient.removeAvatar() }
} }
override suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?> = withContext(sessionDispatcher) { override suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.joinRoomById(roomId.value).destroy() innerClient.joinRoomById(roomId.value).destroy()
try { try {
awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.JOINED) awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.JOINED)
} catch (e: Exception) { } catch (e: Exception) {
@ -405,7 +406,7 @@ class RustMatrixClient(
override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> = withContext(sessionDispatcher) { override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.joinRoomByIdOrAlias( innerClient.joinRoomByIdOrAlias(
roomIdOrAlias = roomIdOrAlias.identifier, roomIdOrAlias = roomIdOrAlias.identifier,
serverNames = serverNames, serverNames = serverNames,
).destroy() ).destroy()
@ -422,7 +423,7 @@ class RustMatrixClient(
sessionDispatcher sessionDispatcher
) { ) {
runCatching { runCatching {
client.knock(roomIdOrAlias.identifier, message, serverNames).destroy() innerClient.knock(roomIdOrAlias.identifier, message, serverNames).destroy()
try { try {
awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED) awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED)
} catch (e: Exception) { } catch (e: Exception) {
@ -434,19 +435,19 @@ class RustMatrixClient(
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) { override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.trackRecentlyVisitedRoom(roomId.value) innerClient.trackRecentlyVisitedRoom(roomId.value)
} }
} }
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> = withContext(sessionDispatcher) { override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.getRecentlyVisitedRooms().map(::RoomId) innerClient.getRecentlyVisitedRooms().map(::RoomId)
} }
} }
override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>> = withContext(sessionDispatcher) { override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>> = withContext(sessionDispatcher) {
runCatching { runCatching {
val result = client.resolveRoomAlias(roomAlias.value)?.let { val result = innerClient.resolveRoomAlias(roomAlias.value)?.let {
ResolvedRoomAlias( ResolvedRoomAlias(
roomId = RoomId(it.roomId), roomId = RoomId(it.roomId),
servers = it.servers, servers = it.servers,
@ -459,8 +460,8 @@ class RustMatrixClient(
override suspend fun getRoomPreviewInfo(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreviewInfo> = withContext(sessionDispatcher) { override suspend fun getRoomPreviewInfo(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreviewInfo> = withContext(sessionDispatcher) {
runCatching { runCatching {
when (roomIdOrAlias) { when (roomIdOrAlias) {
is RoomIdOrAlias.Alias -> client.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value) is RoomIdOrAlias.Alias -> innerClient.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
is RoomIdOrAlias.Id -> client.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames) is RoomIdOrAlias.Id -> innerClient.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
}.use { roomPreview -> }.use { roomPreview ->
RoomPreviewInfoMapper.map(roomPreview.info()) RoomPreviewInfoMapper.map(roomPreview.info())
} }
@ -490,11 +491,6 @@ class RustMatrixClient(
clientDelegateTaskHandle?.cancelAndDestroy() clientDelegateTaskHandle?.cancelAndDestroy()
notificationSettingsService.destroy() notificationSettingsService.destroy()
verificationService.destroy() verificationService.destroy()
innerRoomListService.destroy()
notificationClient.destroy()
notificationProcessSetup.destroy()
encryptionService.destroy()
client.destroy()
} }
override suspend fun getCacheSize(): Long { override suspend fun getCacheSize(): Long {
@ -514,13 +510,13 @@ class RustMatrixClient(
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
if (userInitiated) { if (userInitiated) {
try { try {
result = client.logout() result = innerClient.logout()
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (ignoreSdkError) { if (ignoreSdkError) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.") Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
} else { } else {
// If the logout failed we need to restore the delegate // If the logout failed we need to restore the delegate
clientDelegateTaskHandle = client.setDelegate(sessionDelegate) clientDelegateTaskHandle = innerClient.setDelegate(sessionDelegate)
Timber.e(failure, "Fail to call logout on HS.") Timber.e(failure, "Fail to call logout on HS.")
throw failure throw failure
} }
@ -538,7 +534,7 @@ class RustMatrixClient(
override fun canDeactivateAccount(): Boolean { override fun canDeactivateAccount(): Boolean {
return runCatching { return runCatching {
client.canDeactivateAccount() innerClient.canDeactivateAccount()
} }
.getOrNull() .getOrNull()
.orFalse() .orFalse()
@ -552,7 +548,7 @@ class RustMatrixClient(
runCatching { runCatching {
// First call without AuthData, should fail // First call without AuthData, should fail
val firstAttempt = runCatching { val firstAttempt = runCatching {
client.deactivateAccount( innerClient.deactivateAccount(
authData = null, authData = null,
eraseData = eraseData, eraseData = eraseData,
) )
@ -561,7 +557,7 @@ class RustMatrixClient(
Timber.w(firstAttempt.exceptionOrNull(), "Expected failure, try again") Timber.w(firstAttempt.exceptionOrNull(), "Expected failure, try again")
// This is expected, try again with the password // This is expected, try again with the password
runCatching { runCatching {
client.deactivateAccount( innerClient.deactivateAccount(
authData = AuthData.Password( authData = AuthData.Password(
passwordDetails = AuthDataPasswordDetails( passwordDetails = AuthDataPasswordDetails(
identifier = sessionId.value, identifier = sessionId.value,
@ -573,7 +569,7 @@ class RustMatrixClient(
}.onFailure { }.onFailure {
Timber.e(it, "Failed to deactivate account") Timber.e(it, "Failed to deactivate account")
// If the deactivation failed we need to restore the delegate // If the deactivation failed we need to restore the delegate
clientDelegateTaskHandle = client.setDelegate(sessionDelegate) clientDelegateTaskHandle = innerClient.setDelegate(sessionDelegate)
throw it throw it
} }
} }
@ -588,13 +584,13 @@ class RustMatrixClient(
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> = withContext(sessionDispatcher) { override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> = withContext(sessionDispatcher) {
val rustAction = action?.toRustAction() val rustAction = action?.toRustAction()
runCatching { runCatching {
client.accountUrl(rustAction) innerClient.accountUrl(rustAction)
} }
} }
override suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String> = withContext(sessionDispatcher) { override suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.uploadMedia(mimeType, data, progressCallback?.toProgressWatcher()) innerClient.uploadMedia(mimeType, data, progressCallback?.toProgressWatcher())
} }
} }
@ -617,29 +613,33 @@ class RustMatrixClient(
.distinctUntilChanged() .distinctUntilChanged()
} }
override suspend fun setAllSendQueuesEnabled(enabled: Boolean) = withContext(sessionDispatcher) { override suspend fun setAllSendQueuesEnabled(enabled: Boolean) {
Timber.i("setAllSendQueuesEnabled($enabled)") withContext(sessionDispatcher) {
client.enableAllSendQueues(enabled) Timber.i("setAllSendQueuesEnabled($enabled)")
tryOrNull {
innerClient.enableAllSendQueues(enabled)
}
}
} }
override fun sendQueueDisabledFlow(): Flow<RoomId> = mxCallbackFlow { override fun sendQueueDisabledFlow(): Flow<RoomId> = mxCallbackFlow {
client.subscribeToSendQueueStatus(object : SendQueueRoomErrorListener { innerClient.subscribeToSendQueueStatus(object : SendQueueRoomErrorListener {
override fun onError(roomId: String, error: ClientException) { override fun onError(roomId: String, error: ClientException) {
trySend(RoomId(roomId)) trySend(RoomId(roomId))
} }
}) })
}.buffer(Channel.UNLIMITED) }.buffer(Channel.UNLIMITED)
override suspend fun isNativeSlidingSyncSupported(): Boolean { override suspend fun availableSlidingSyncVersions(): Result<List<SlidingSyncVersion>> = withContext(sessionDispatcher) {
return client.availableSlidingSyncVersions().contains(SlidingSyncVersion.Native) runCatching {
innerClient.availableSlidingSyncVersions().map { it.map() }
}
} }
override suspend fun isSlidingSyncProxySupported(): Boolean { override suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion> = withContext(sessionDispatcher) {
return client.availableSlidingSyncVersions().any { it is SlidingSyncVersion.Proxy } runCatching {
} innerClient.session().slidingSyncVersion.map()
}
override fun isUsingNativeSlidingSync(): Boolean {
return client.session().slidingSyncVersion == SlidingSyncVersion.Native
} }
private suspend fun File.getCacheSize( private suspend fun File.getCacheSize(

View file

@ -77,12 +77,12 @@ class RustMatrixClientFactory @Inject constructor(
.finish() .finish()
return RustMatrixClient( return RustMatrixClient(
client = client, innerClient = client,
baseDirectory = baseDirectory, baseDirectory = baseDirectory,
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = appCoroutineScope, appCoroutineScope = appCoroutineScope,
sessionDelegate = sessionDelegate, sessionDelegate = sessionDelegate,
syncService = syncService, innerSyncService = syncService,
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
baseCacheDirectory = cacheDirectory, baseCacheDirectory = cacheDirectory,
clock = clock, clock = clock,

View file

@ -94,10 +94,6 @@ internal class RustEncryptionService(
} }
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
fun destroy() {
service.destroy()
}
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) { override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
runCatching { runCatching {
service.enableBackups() service.enableBackups()

View file

@ -42,7 +42,6 @@ class RustNotificationSettingsService(
fun destroy() { fun destroy() {
notificationSettings.setDelegate(null) notificationSettings.setDelegate(null)
notificationSettings.destroy()
} }
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> = override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> =

View file

@ -10,12 +10,14 @@ package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.SyncServiceState import org.matrix.rustcomponents.sdk.SyncServiceState
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -49,12 +51,11 @@ class RustSyncService(
Timber.d("Stop sync failed: $it") Timber.d("Stop sync failed: $it")
} }
suspend fun destroy() { suspend fun destroy() = withContext(NonCancellable) {
// If the service was still running, stop it // If the service was still running, stop it
stopSync() stopSync()
Timber.d("Destroying sync service") Timber.d("Destroying sync service")
isServiceReady.set(false) isServiceReady.set(false)
innerSyncService.destroy()
} }
override val syncState: StateFlow<SyncState> = override val syncState: StateFlow<SyncState> =

View file

@ -0,0 +1,19 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import org.matrix.rustcomponents.sdk.SlidingSyncVersion as RustSlidingSyncVersion
internal fun RustSlidingSyncVersion.map(): SlidingSyncVersion {
return when (this) {
RustSlidingSyncVersion.None -> SlidingSyncVersion.None
is RustSlidingSyncVersion.Proxy -> SlidingSyncVersion.Proxy
RustSlidingSyncVersion.Native -> SlidingSyncVersion.Native
}
}

View file

@ -229,7 +229,6 @@ class RustSessionVerificationService(
recoveryStateListenerTaskHandle.cancelAndDestroy() recoveryStateListenerTaskHandle.cancelAndDestroy()
if (this::verificationController.isInitialized) { if (this::verificationController.isInitialized) {
verificationController.setDelegate(null) verificationController.setDelegate(null)
verificationController.destroy()
} }
} }

View file

@ -35,14 +35,14 @@ class RustMatrixClientTest {
private fun TestScope.createRustMatrixClient( private fun TestScope.createRustMatrixClient(
sessionStore: SessionStore = InMemorySessionStore(), sessionStore: SessionStore = InMemorySessionStore(),
) = RustMatrixClient( ) = RustMatrixClient(
client = FakeRustClient(), innerClient = FakeRustClient(),
baseDirectory = File(""), baseDirectory = File(""),
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = this, appCoroutineScope = this,
sessionDelegate = aRustClientSessionDelegate( sessionDelegate = aRustClientSessionDelegate(
sessionStore = sessionStore, sessionStore = sessionStore,
), ),
syncService = FakeRustSyncService(), innerSyncService = FakeRustSyncService(),
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
baseCacheDirectory = File(""), baseCacheDirectory = File(""),
clock = FakeSystemClock(), clock = FakeSystemClock(),

View file

@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@ -84,9 +85,8 @@ class FakeMatrixClient(
private val getUrlLambda: (String) -> Result<String> = { lambdaError() }, private val getUrlLambda: (String) -> Result<String> = { lambdaError() },
private val canDeactivateAccountResult: () -> Boolean = { lambdaError() }, private val canDeactivateAccountResult: () -> Boolean = { lambdaError() },
private val deactivateAccountResult: (String, Boolean) -> Result<Unit> = { _, _ -> lambdaError() }, private val deactivateAccountResult: (String, Boolean) -> Result<Unit> = { _, _ -> lambdaError() },
var isNativeSlidingSyncSupportedLambda: suspend () -> Boolean = { true }, private val currentSlidingSyncVersionLambda: () -> Result<SlidingSyncVersion> = { lambdaError() },
var isSlidingSyncProxySupportedLambda: suspend () -> Boolean = { true }, private val availableSlidingSyncVersionsLambda: () -> Result<List<SlidingSyncVersion>> = { lambdaError() }
var isUsingNativeSlidingSyncLambda: () -> Boolean = { true },
) : MatrixClient { ) : MatrixClient {
var setDisplayNameCalled: Boolean = false var setDisplayNameCalled: Boolean = false
private set private set
@ -340,15 +340,11 @@ class FakeMatrixClient(
return getUrlLambda(url) return getUrlLambda(url)
} }
override suspend fun isNativeSlidingSyncSupported(): Boolean { override suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion> {
return isNativeSlidingSyncSupportedLambda() return currentSlidingSyncVersionLambda()
} }
override suspend fun isSlidingSyncProxySupported(): Boolean { override suspend fun availableSlidingSyncVersions(): Result<List<SlidingSyncVersion>> {
return isSlidingSyncProxySupportedLambda() return availableSlidingSyncVersionsLambda()
}
override fun isUsingNativeSlidingSync(): Boolean {
return isUsingNativeSlidingSyncLambda()
} }
} }