providing progress during room key imports by switching to a flow
This commit is contained in:
parent
d05d86a02d
commit
5b2243cfa9
|
@ -6,3 +6,9 @@ sealed interface Lce<T> {
|
|||
data class Content<T>(val value: T) : Lce<T>
|
||||
}
|
||||
|
||||
sealed interface LceWithProgress<T> {
|
||||
data class Loading<T>(val progress: T) : LceWithProgress<T>
|
||||
data class Error<T>(val cause: Throwable) : LceWithProgress<T>
|
||||
data class Content<T>(val value: T) : LceWithProgress<T>
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.net.toUri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LceWithProgress
|
||||
import app.dapk.st.core.StartObserving
|
||||
import app.dapk.st.core.components.CenteredLoading
|
||||
import app.dapk.st.core.components.Header
|
||||
|
@ -136,10 +137,11 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
|||
}
|
||||
}
|
||||
|
||||
is Lce.Content -> {
|
||||
is LceWithProgress.Content -> {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = "Import success")
|
||||
Text(text = "Successfully imported ${it.importProgress.value} keys")
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(onClick = { navigator.navigate.upToHome() }) {
|
||||
Text(text = "Close".uppercase())
|
||||
}
|
||||
|
@ -147,7 +149,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
|||
}
|
||||
}
|
||||
|
||||
is Lce.Error -> {
|
||||
is LceWithProgress.Error -> {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = "Import failed")
|
||||
|
@ -158,7 +160,15 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
|||
}
|
||||
}
|
||||
|
||||
is Lce.Loading -> CenteredLoading()
|
||||
is LceWithProgress.Loading -> {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = "Imported ${it.importProgress.progress} keys")
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
CircularProgressIndicator(Modifier.wrapContentSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package app.dapk.st.settings
|
|||
|
||||
import android.net.Uri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LceWithProgress
|
||||
import app.dapk.st.design.components.Route
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.push.Registrar
|
||||
|
@ -15,7 +16,7 @@ internal sealed interface Page {
|
|||
object Security : Page
|
||||
data class ImportRoomKey(
|
||||
val selectedFile: NamedUri? = null,
|
||||
val importProgress: Lce<Unit>? = null,
|
||||
val importProgress: LceWithProgress<Long>? = null,
|
||||
) : Page
|
||||
|
||||
data class PushProviders(
|
||||
|
|
|
@ -2,11 +2,14 @@ package app.dapk.st.settings
|
|||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.core.LceWithProgress
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.domain.StoreCleaner
|
||||
import app.dapk.st.matrix.crypto.CryptoService
|
||||
import app.dapk.st.matrix.crypto.ImportResult
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.push.PushTokenRegistrars
|
||||
import app.dapk.st.push.Registrar
|
||||
|
@ -15,6 +18,8 @@ import app.dapk.st.settings.SettingsEvent.*
|
|||
import app.dapk.st.viewmodel.DapkViewModel
|
||||
import app.dapk.st.viewmodel.MutableStateFactory
|
||||
import app.dapk.st.viewmodel.defaultStateFactory
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val PRIVACY_POLICY_URL = "https://ouchadam.github.io/small-talk/privacy/"
|
||||
|
@ -120,17 +125,26 @@ internal class SettingsViewModel(
|
|||
}
|
||||
|
||||
fun importFromFileKeys(file: Uri, passphrase: String) {
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = Lce.Loading()) }
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = LceWithProgress.Loading(0L)) }
|
||||
viewModelScope.launch {
|
||||
kotlin.runCatching {
|
||||
with(cryptoService) {
|
||||
val roomsToRefresh = contentResolver.openInputStream(file)?.importRoomKeys(passphrase)
|
||||
roomsToRefresh?.let { syncService.forceManualRefresh(roomsToRefresh) }
|
||||
}
|
||||
}.fold(
|
||||
onSuccess = { updatePageState<Page.ImportRoomKey> { copy(importProgress = Lce.Content(Unit)) } },
|
||||
onFailure = { updatePageState<Page.ImportRoomKey> { copy(importProgress = Lce.Error(it)) } }
|
||||
)
|
||||
with(cryptoService) {
|
||||
contentResolver.openInputStream(file)?.importRoomKeys(passphrase)
|
||||
?.onEach {
|
||||
when (it) {
|
||||
is ImportResult.Error -> {
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = LceWithProgress.Error(it.cause)) }
|
||||
}
|
||||
is ImportResult.Update -> {
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = LceWithProgress.Loading(it.importedKeysCount)) }
|
||||
}
|
||||
is ImportResult.Success -> {
|
||||
syncService.forceManualRefresh(it.roomIds.toList())
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = LceWithProgress.Content(it.totalImportedKeysCount)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
?.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ interface CryptoService : MatrixService {
|
|||
suspend fun encrypt(roomId: RoomId, credentials: DeviceCredentials, messageJson: JsonString): Crypto.EncryptionResult
|
||||
suspend fun decrypt(encryptedPayload: EncryptedMessageContent): DecryptionResult
|
||||
suspend fun importRoomKeys(keys: List<SharedRoomKey>)
|
||||
suspend fun InputStream.importRoomKeys(password: String): List<RoomId>
|
||||
suspend fun InputStream.importRoomKeys(password: String): Flow<ImportResult>
|
||||
|
||||
suspend fun maybeCreateMoreKeys(serverKeyCount: ServerKeyCount)
|
||||
suspend fun updateOlmSession(userIds: List<UserId>, syncToken: SyncToken?)
|
||||
|
@ -159,4 +159,10 @@ fun MatrixServiceProvider.cryptoService(): CryptoService = this.getService(key =
|
|||
|
||||
fun interface RoomMembersProvider {
|
||||
suspend fun userIdsForRoom(roomId: RoomId): List<UserId>
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ImportResult {
|
||||
data class Success(val roomIds: Set<RoomId>, val totalImportedKeysCount: Long) : ImportResult
|
||||
data class Error(val cause: Throwable) : ImportResult
|
||||
data class Update(val importedKeysCount: Long) : ImportResult
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import app.dapk.st.core.logP
|
|||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.crypto.Crypto
|
||||
import app.dapk.st.matrix.crypto.CryptoService
|
||||
import app.dapk.st.matrix.crypto.ImportResult
|
||||
import app.dapk.st.matrix.crypto.Verification
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.io.InputStream
|
||||
|
@ -48,7 +49,7 @@ internal class DefaultCryptoService(
|
|||
verificationHandler.onUserVerificationAction(verificationAction)
|
||||
}
|
||||
|
||||
override suspend fun InputStream.importRoomKeys(password: String): List<RoomId> {
|
||||
override suspend fun InputStream.importRoomKeys(password: String): Flow<ImportResult> {
|
||||
return logP("import room keys") {
|
||||
with(roomKeyImporter) {
|
||||
importRoomKeys(password) {
|
||||
|
|
|
@ -7,6 +7,8 @@ import app.dapk.st.matrix.common.AlgorithmName
|
|||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.SessionId
|
||||
import app.dapk.st.matrix.common.SharedRoomKey
|
||||
import app.dapk.st.matrix.crypto.ImportResult
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -28,8 +30,10 @@ class RoomKeyImporter(
|
|||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
|
||||
suspend fun InputStream.importRoomKeys(password: String, onChunk: suspend (List<SharedRoomKey>) -> Unit): List<RoomId> {
|
||||
return dispatchers.withIoContext {
|
||||
suspend fun InputStream.importRoomKeys(password: String, onChunk: suspend (List<SharedRoomKey>) -> Unit): Flow<ImportResult> {
|
||||
return flow {
|
||||
var importedKeysCount = 0L
|
||||
|
||||
val decryptCipher = Cipher.getInstance("AES/CTR/NoPadding")
|
||||
var jsonSegment = ""
|
||||
|
||||
|
@ -87,13 +91,20 @@ class RoomKeyImporter(
|
|||
)
|
||||
}
|
||||
.chunked(500)
|
||||
.forEach { onChunk(it) }
|
||||
.forEach {
|
||||
onChunk(it)
|
||||
importedKeysCount += it.size
|
||||
emit(ImportResult.Update(importedKeysCount))
|
||||
}
|
||||
}
|
||||
roomIds.toList().ifEmpty {
|
||||
throw IOException("Found no rooms to import in the file")
|
||||
|
||||
if (roomIds.isEmpty()) {
|
||||
emit(ImportResult.Error(IOException("Found no rooms to import in the file")))
|
||||
} else {
|
||||
emit(ImportResult.Success(roomIds, importedKeysCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private fun Cipher.initialize(payload: ByteArray, passphrase: String) {
|
||||
|
|
Loading…
Reference in New Issue