Convert KeysExporter to coroutines

This commit is contained in:
Benoit Marty 2021-06-01 16:50:10 +02:00 committed by Benoit Marty
parent f99600f115
commit 3be95ca442
8 changed files with 92 additions and 122 deletions

View File

@ -84,7 +84,7 @@ interface CryptoService {
fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>)
suspend fun exportRoomKeys(password: String): ByteArray
fun setRoomBlacklistUnverifiedDevices(roomId: String)

View File

@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor(
* Export the crypto keys
*
* @param password the password
* @param callback the exported keys
* @return the exported keys
*/
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}.foldToCallback(callback)
}
override suspend fun exportRoomKeys(password: String): ByteArray {
return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}
/**

View File

@ -32,6 +32,7 @@ import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.verification.VerificationBottomSheet
@ -138,6 +139,7 @@ interface ScreenComponent {
fun inject(activity: LinkHandlerActivity)
fun inject(activity: MainActivity)
fun inject(activity: RoomDirectoryActivity)
fun inject(activity: KeysBackupSetupActivity)
fun inject(activity: BugReportActivity)
fun inject(activity: FilteredRoomsActivity)
fun inject(activity: CreateRoomActivity)

View File

@ -18,35 +18,24 @@ package im.vector.app.features.crypto.keys
import android.content.Context
import android.net.Uri
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.util.awaitCallback
class KeysExporter(private val session: Session) {
import javax.inject.Inject
class KeysExporter @Inject constructor(
private val session: Session,
private val context: Context
) {
/**
* Export keys and return the file path with the callback
* Export keys and write them to the provided uri
*/
fun export(context: Context, password: String, uri: Uri, callback: MatrixCallback<Boolean>) {
session.coroutineScope.launch(Dispatchers.Main) {
runCatching {
withContext(Dispatchers.IO) {
val data = awaitCallback<ByteArray> { session.cryptoService().exportRoomKeys(password, it) }
val os = context.contentResolver?.openOutputStream(uri)
if (os == null) {
false
} else {
os.write(data)
os.flush()
true
}
}
}.foldToCallback(callback)
suspend fun export(password: String, uri: Uri) {
return withContext(Dispatchers.IO) {
val data = session.cryptoService().exportRoomKeys(password)
context.contentResolver.openOutputStream(uri)
?.use { it.write(data) }
?: throw IllegalStateException("Unable to open file for writting")
}
}
}

View File

@ -18,10 +18,13 @@ package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys
@ -30,7 +33,8 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast
import im.vector.app.features.crypto.keys.KeysExporter
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.launch
import javax.inject.Inject
class KeysBackupSetupActivity : SimpleFragmentActivity() {
@ -38,6 +42,13 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@Inject lateinit var keysExporter: KeysExporter
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
}
override fun initUiAndData() {
super.initUiAndData()
if (isFirstCreation()) {
@ -132,30 +143,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
showWaitingView()
KeysExporter(session)
.export(this@KeysBackupSetupActivity,
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
toast(getString(R.string.encryption_exported_successfully))
Intent().apply {
putExtra(MANUAL_EXPORT, true)
}.let {
setResult(Activity.RESULT_OK, it)
finish()
}
}
hideWaitingView()
}
override fun onFailure(failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
hideWaitingView()
}
})
export(passphrase, uri)
}
})
} else {
@ -165,6 +153,20 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
}
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
toast(getString(R.string.encryption_exported_successfully))
setResult(Activity.RESULT_OK, Intent().apply { putExtra(MANUAL_EXPORT, true) })
finish()
} catch (failure: Throwable) {
toast(failure.localizedMessage ?: getString(R.string.unexpected_error))
}
hideWaitingView()
}
}
override fun onBackPressed() {
if (viewModel.shouldPromptOnBack) {
if (waitingView?.isVisible == true) {

View File

@ -20,6 +20,7 @@ package im.vector.app.features.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
@ -60,6 +61,7 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.launch
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
@ -75,6 +77,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore,
private val keysExporter: KeysExporter,
private val navigator: Navigator
) : VectorSettingsBaseFragment() {
@ -320,29 +323,24 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override fun onPassphrase(passphrase: String) {
displayLoadingView()
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
export(passphrase, uri)
}
})
}
}
private fun export(passphrase: String, uri: Uri) {
lifecycleScope.launch {
try {
keysExporter.export(passphrase, uri)
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} catch (failure: Throwable) {
requireActivity().toast(errorFormatter.toHumanReadable(failure))
}
hideLoadingView()
}
}
private val pinActivityResultLauncher = registerStartForActivityResult {
if (it.resultCode == Activity.RESULT_OK) {
doOpenPinCodePreferenceScreen()

View File

@ -42,9 +42,7 @@ import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import timber.log.Timber
import javax.inject.Inject
// TODO this needs to be refactored to current standard and remove legacy
@ -113,31 +111,6 @@ class SignOutBottomSheetDialogFragment :
views.setupMegolmBackupButton.action = {
setupBackupActivityResultLauncher.launch(KeysBackupSetupActivity.intent(requireContext(), true))
}
viewModel.observeViewEvents {
when (it) {
is SignoutCheckViewModel.ViewEvents.ExportKeys -> {
it.exporter
.export(requireContext(),
it.passphrase,
it.uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
viewModel.handle(SignoutCheckViewModel.Actions.KeySuccessfullyManuallyExported)
} else {
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
viewModel.handle(SignoutCheckViewModel.Actions.KeyExportFailed)
}
})
}
}
}
}
override fun invalidate() = withState(viewModel) { state ->

View File

@ -17,6 +17,7 @@
package im.vector.app.features.workers.signout
import android.net.Uri
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
@ -27,13 +28,14 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.crypto.keys.KeysExporter
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
@ -41,6 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_S
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
data class SignoutCheckViewState(
val userId: String = "",
@ -50,18 +53,15 @@ data class SignoutCheckViewState(
val hasBeenExportedToFile: Async<Boolean> = Uninitialized
) : MvRxState
class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState: SignoutCheckViewState,
private val session: Session)
: VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, SignoutCheckViewModel.ViewEvents>(initialState), KeysBackupStateListener {
class SignoutCheckViewModel @AssistedInject constructor(
@Assisted initialState: SignoutCheckViewState,
private val session: Session,
private val keysExporter: KeysExporter
) : VectorViewModel<SignoutCheckViewState, SignoutCheckViewModel.Actions, EmptyViewEvents>(initialState), KeysBackupStateListener {
sealed class Actions : VectorViewModelAction {
data class ExportKeys(val passphrase: String, val uri: Uri) : Actions()
object KeySuccessfullyManuallyExported : Actions()
object KeyExportFailed : Actions()
}
sealed class ViewEvents : VectorViewEvents {
data class ExportKeys(val exporter: KeysExporter, val passphrase: String, val uri: Uri) : ViewEvents()
}
@AssistedFactory
@ -128,22 +128,32 @@ class SignoutCheckViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: Actions) {
when (action) {
is Actions.ExportKeys -> {
setState {
copy(hasBeenExportedToFile = Loading())
}
_viewEvents.post(ViewEvents.ExportKeys(KeysExporter(session), action.passphrase, action.uri))
}
is Actions.ExportKeys -> handleExportKeys(action)
Actions.KeySuccessfullyManuallyExported -> {
setState {
copy(hasBeenExportedToFile = Success(true))
}
}
Actions.KeyExportFailed -> {
setState {
copy(hasBeenExportedToFile = Uninitialized)
}
}
}.exhaustive
}
private fun handleExportKeys(action: Actions.ExportKeys) {
setState {
copy(hasBeenExportedToFile = Loading())
}
viewModelScope.launch {
val newState = try {
keysExporter.export(action.passphrase, action.uri)
Success(true)
} catch (failure: Throwable) {
Timber.e("## Failed to export manually keys ${failure.localizedMessage}")
Uninitialized
}
setState {
copy(hasBeenExportedToFile = newState)
}
}
}
}