Secure Storage: improve API for waitForBackupUploadSteadyState()

This commit is contained in:
Benoit Marty 2023-10-30 15:22:44 +01:00 committed by Benoit Marty
parent 4898d8012c
commit 29e1e9b316
5 changed files with 37 additions and 18 deletions

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient 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 io.element.android.libraries.matrix.api.encryption.EncryptionService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -46,13 +47,15 @@ class LogoutPresenter @Inject constructor(
mutableStateOf(Async.Uninitialized) 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 showLogoutDialog by remember { mutableStateOf(false) }
var isLastSession by remember { mutableStateOf(false) } var isLastSession by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
isLastSession = encryptionService.isLastDevice().getOrNull() ?: false isLastSession = encryptionService.isLastDevice().getOrNull() ?: false
encryptionService.waitForBackupUploadSteadyState()
} }
fun handleEvents(event: LogoutEvents) { fun handleEvents(event: LogoutEvents) {

View file

@ -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.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -73,6 +75,15 @@ class LogoutPresenterTest {
@Test @Test
fun `present - initial state - backing up`() = runTest { fun `present - initial state - backing up`() = runTest {
val encryptionService = FakeEncryptionService() 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( val presenter = createLogoutPresenter(
encryptionService = encryptionService encryptionService = encryptionService
) )
@ -81,13 +92,11 @@ class LogoutPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.isLastSession).isFalse() assertThat(initialState.isLastSession).isFalse()
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Waiting)
assertThat(initialState.showConfirmationDialog).isFalse() assertThat(initialState.showConfirmationDialog).isFalse()
assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized) assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized)
encryptionService.emitBackupUploadState(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
val state = awaitItem() val state = awaitItem()
assertThat(state.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2)) assertThat(state.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
encryptionService.emitBackupUploadState(BackupUploadState.Done)
val doneState = awaitItem() val doneState = awaitItem()
assertThat(doneState.backupUploadState).isEqualTo(BackupUploadState.Done) assertThat(doneState.backupUploadState).isEqualTo(BackupUploadState.Done)
} }

View file

@ -16,12 +16,12 @@
package io.element.android.libraries.matrix.api.encryption package io.element.android.libraries.matrix.api.encryption
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface EncryptionService { interface EncryptionService {
val backupStateStateFlow: StateFlow<BackupState> val backupStateStateFlow: StateFlow<BackupState>
val recoveryStateStateFlow: StateFlow<RecoveryState> val recoveryStateStateFlow: StateFlow<RecoveryState>
val backupUploadStateStateFlow: StateFlow<BackupUploadState>
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress> val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
suspend fun enableBackups(): Result<Unit> suspend fun enableBackups(): Result<Unit>
@ -46,7 +46,7 @@ interface EncryptionService {
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
/** /**
* Observe [backupUploadStateStateFlow] to get progress. * Wait for backup upload steady state.
*/ */
suspend fun waitForBackupUploadSteadyState(): Result<Unit> fun waitForBackupUploadSteadyState(): Flow<BackupUploadState>
} }

View file

@ -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.EnableRecoveryProgress
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 kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.BackupStateListener import org.matrix.rustcomponents.sdk.BackupStateListener
import org.matrix.rustcomponents.sdk.BackupSteadyStateListener import org.matrix.rustcomponents.sdk.BackupSteadyStateListener
@ -50,7 +53,6 @@ internal class RustEncryptionService(
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(service.backupState().let(backupStateMapper::map)) override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(service.backupState().let(backupStateMapper::map))
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map)) override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map))
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown) override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown)
fun start() { fun start() {
service.backupStateListener(object : BackupStateListener { service.backupStateListener(object : BackupStateListener {
@ -94,16 +96,19 @@ internal class RustEncryptionService(
} }
} }
override suspend fun waitForBackupUploadSteadyState( override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
): Result<Unit> = withContext(dispatchers.io) { return callbackFlow {
runCatching {
service.waitForBackupUploadSteadyState( service.waitForBackupUploadSteadyState(
progressListener = object : BackupSteadyStateListener { progressListener = object : BackupSteadyStateListener {
override fun onUpdate(status: RustBackupUploadState) { override fun onUpdate(status: RustBackupUploadState) {
backupUploadStateStateFlow.value = backupUploadStateMapper.map(status) trySend(backupUploadStateMapper.map(status))
if (status == RustBackupUploadState.Done) {
close()
}
} }
} }
) )
awaitClose {}
} }
} }

View file

@ -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.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
class FakeEncryptionService : EncryptionService { class FakeEncryptionService : EncryptionService {
private var disableRecoveryFailure: Exception? = null private var disableRecoveryFailure: Exception? = null
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN) override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN) override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown) override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown) private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
private var fixRecoveryIssuesFailure: Exception? = null private var fixRecoveryIssuesFailure: Exception? = null
@ -73,12 +75,12 @@ class FakeEncryptionService : EncryptionService {
return Result.success(Unit) return Result.success(Unit)
} }
override suspend fun waitForBackupUploadSteadyState(): Result<Unit> { fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
return Result.success(Unit) waitForBackupUploadSteadyStateFlow = flow
} }
suspend fun emitBackupUploadState(state: BackupUploadState) { override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
backupUploadStateStateFlow.emit(state) return waitForBackupUploadSteadyStateFlow
} }
suspend fun emitBackupState(state: BackupState) { suspend fun emitBackupState(state: BackupState) {