From 90b377b3a52853232acce5aac37e70270fb9612f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Oct 2023 15:22:44 +0100 Subject: [PATCH] Secure Storage: improve API for `waitForBackupUploadSteadyState()` --- .../features/logout/impl/LogoutPresenter.kt | 7 +++++-- .../features/logout/impl/LogoutPresenterTest.kt | 15 ++++++++++++--- .../matrix/api/encryption/EncryptionService.kt | 6 +++--- .../impl/encryption/RustEncryptionService.kt | 15 ++++++++++----- .../test/encryption/FakeEncryptionService.kt | 12 +++++++----- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index 8d97df3801..982fb0d796 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -46,13 +47,15 @@ class LogoutPresenter @Inject constructor( mutableStateOf(Async.Uninitialized) } - val backupUploadState by encryptionService.backupUploadStateStateFlow.collectAsState() + val backupUploadState: BackupUploadState by remember { + encryptionService.waitForBackupUploadSteadyState() + } + .collectAsState(initial = BackupUploadState.Unknown) var showLogoutDialog by remember { mutableStateOf(false) } var isLastSession by remember { mutableStateOf(false) } LaunchedEffect(Unit) { isLastSession = encryptionService.isLastDevice().getOrNull() ?: false - encryptionService.waitForBackupUploadSteadyState() } fun handleEvents(event: LogoutEvents) { diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index fe703780ba..150401c902 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -28,6 +28,8 @@ import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -73,6 +75,15 @@ class LogoutPresenterTest { @Test fun `present - initial state - backing up`() = runTest { val encryptionService = FakeEncryptionService() + encryptionService.givenWaitForBackupUploadSteadyStateFlow( + flow { + emit(BackupUploadState.Waiting) + delay(1) + emit(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2)) + delay(1) + emit(BackupUploadState.Done) + } + ) val presenter = createLogoutPresenter( encryptionService = encryptionService ) @@ -81,13 +92,11 @@ class LogoutPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.isLastSession).isFalse() - assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) + assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Waiting) assertThat(initialState.showConfirmationDialog).isFalse() assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) - encryptionService.emitBackupUploadState(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2)) val state = awaitItem() assertThat(state.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2)) - encryptionService.emitBackupUploadState(BackupUploadState.Done) val doneState = awaitItem() assertThat(doneState.backupUploadState).isEqualTo(BackupUploadState.Done) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index b11cc3f9ab..9652c53678 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -16,12 +16,12 @@ package io.element.android.libraries.matrix.api.encryption +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface EncryptionService { val backupStateStateFlow: StateFlow val recoveryStateStateFlow: StateFlow - val backupUploadStateStateFlow: StateFlow val enableRecoveryProgressStateFlow: StateFlow suspend fun enableBackups(): Result @@ -46,7 +46,7 @@ interface EncryptionService { suspend fun fixRecoveryIssues(recoveryKey: String): Result /** - * Observe [backupUploadStateStateFlow] to get progress. + * Wait for backup upload steady state. */ - suspend fun waitForBackupUploadSteadyState(): Result + fun waitForBackupUploadSteadyState(): Flow } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 9b1e3909d7..b1089a215d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -22,7 +22,10 @@ import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.BackupStateListener import org.matrix.rustcomponents.sdk.BackupSteadyStateListener @@ -50,7 +53,6 @@ internal class RustEncryptionService( override val backupStateStateFlow: MutableStateFlow = MutableStateFlow(service.backupState().let(backupStateMapper::map)) override val recoveryStateStateFlow: MutableStateFlow = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map)) override val enableRecoveryProgressStateFlow: MutableStateFlow = MutableStateFlow(EnableRecoveryProgress.Unknown) - override val backupUploadStateStateFlow: MutableStateFlow = MutableStateFlow(BackupUploadState.Unknown) fun start() { service.backupStateListener(object : BackupStateListener { @@ -94,16 +96,19 @@ internal class RustEncryptionService( } } - override suspend fun waitForBackupUploadSteadyState( - ): Result = withContext(dispatchers.io) { - runCatching { + override fun waitForBackupUploadSteadyState(): Flow { + return callbackFlow { service.waitForBackupUploadSteadyState( progressListener = object : BackupSteadyStateListener { override fun onUpdate(status: RustBackupUploadState) { - backupUploadStateStateFlow.value = backupUploadStateMapper.map(status) + trySend(backupUploadStateMapper.map(status)) + if (status == RustBackupUploadState.Done) { + close() + } } } ) + awaitClose {} } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index 548b2f9165..825d929c0e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -22,14 +22,16 @@ import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf class FakeEncryptionService : EncryptionService { private var disableRecoveryFailure: Exception? = null override val backupStateStateFlow: MutableStateFlow = MutableStateFlow(BackupState.UNKNOWN) override val recoveryStateStateFlow: MutableStateFlow = MutableStateFlow(RecoveryState.UNKNOWN) override val enableRecoveryProgressStateFlow: MutableStateFlow = MutableStateFlow(EnableRecoveryProgress.Unknown) - override val backupUploadStateStateFlow: MutableStateFlow = MutableStateFlow(BackupUploadState.Unknown) + private var waitForBackupUploadSteadyStateFlow: Flow = flowOf() private var fixRecoveryIssuesFailure: Exception? = null @@ -73,12 +75,12 @@ class FakeEncryptionService : EncryptionService { return Result.success(Unit) } - override suspend fun waitForBackupUploadSteadyState(): Result { - return Result.success(Unit) + fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow) { + waitForBackupUploadSteadyStateFlow = flow } - suspend fun emitBackupUploadState(state: BackupUploadState) { - backupUploadStateStateFlow.emit(state) + override fun waitForBackupUploadSteadyState(): Flow { + return waitForBackupUploadSteadyStateFlow } suspend fun emitBackupState(state: BackupState) {