From 8abf720439421ac0cb7dd7901d6f039090aa07fe Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Thu, 6 Mar 2025 13:45:12 +0100 Subject: [PATCH] fix: Restore ability to cancel an in-progress upload --- .../newtransfer/upload/UploadViewModel.kt | 11 +++--- .../upload/UploadForegroundService.kt | 36 +++++++++++++++---- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/UploadViewModel.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/UploadViewModel.kt index 8bbee443c..bb3c36616 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/UploadViewModel.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/UploadViewModel.kt @@ -45,6 +45,7 @@ class UploadViewModel @Inject constructor() : ViewModel() { val abandonUploadRequest = CallableState() init { + viewModelScope.launch { handleCancellationRequests() } viewModelScope.launch { UploadForegroundService.uploadStateFlow.collectLatest { uploadState -> if (uploadState is UploadState.Retry) handleRetryRequests() @@ -52,19 +53,19 @@ class UploadViewModel @Inject constructor() : ViewModel() { } } + private suspend fun handleCancellationRequests(): Nothing = repeatWhileActive { + abandonUploadRequest.awaitOneCall() + UploadForegroundService.cancelUpload() + } + private suspend fun handleRetryRequests(): Nothing = repeatWhileActive { val shouldRetry = raceOf( { retryRequest.awaitOneCall(); true }, { editRequest.awaitOneCall(); false }, - { abandonUploadRequest.awaitOneCall(); null }, ) when (shouldRetry) { true -> UploadForegroundService.retry() false -> UploadForegroundService.giveUp() - null -> { - UploadForegroundService.removeAllFiles() - UploadForegroundService.giveUp() - } } } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/upload/UploadForegroundService.kt b/app/src/main/java/com/infomaniak/swisstransfer/upload/UploadForegroundService.kt index bb2e12759..6915220f4 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/upload/UploadForegroundService.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/upload/UploadForegroundService.kt @@ -40,6 +40,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* +import splitties.coroutines.raceOf import splitties.coroutines.repeatWhileActive import splitties.experimental.ExperimentalSplittiesApi import splitties.init.appCtx @@ -60,6 +61,7 @@ class UploadForegroundService : ForegroundService(Companion, redeliverIntentIfKi private val startSignal = Channel() + private val cancelTransferSignals = Channel() private val shouldRetrySignals = Channel() private val pickedFilesExtractor: PickedFilesExtractor = PickedFilesExtractorImpl().also { @@ -123,6 +125,10 @@ class UploadForegroundService : ForegroundService(Companion, redeliverIntentIfKi shouldRetrySignals.send(false) } + suspend fun cancelUpload() { + cancelTransferSignals.send(Unit) + } + private fun keepServiceRunningWhileNeeded() { val needsToKeepFileUris = isHandlingPickedFilesFlow.transformLatest { isHandlingFiles -> if (isHandlingFiles) emit(true) @@ -202,21 +208,24 @@ class UploadForegroundService : ForegroundService(Companion, redeliverIntentIfKi ) //TODO[UL-retry]: Once we support resuming the upload: - // 1. Remove this big `isInternetConnectedFlow` thing and move it to just the uploader part. + // 1. Remove `isInternetConnectedFlow.mapLatest` from `tryCompletingWithInternetUnlessCancelled`, + // and move it to just the uploader part. // 2. Remove the loop (repeatWhileActive) below. // 3. In the uploader, use a function named `uploadAllRemainingWithRetries` or something. - val transferUuid = isInternetConnectedFlow.mapLatest { isInternetConnected -> - if (isInternetConnected.not()) { + val transferUuid = tryCompletingWithInternetUnlessCancelled( + valueIfCancelled = null, + withoutInternet = { currentState = uploadStateFlow.filterIsInstance().first().copy( status = Status.WaitingForInternet ) awaitCancellation() } + ) { repeatWhileActive retryLoop@{ val result = startUploadSession( startRequest = startRequest, updateState = { currentState = it } - ) ?: return@mapLatest null + ) ?: return@tryCompletingWithInternetUnlessCancelled null val uploader = TransferUploader( uploadManager = uploadManager, fileChunkSizeManager = fileChunkSizeManager, @@ -242,11 +251,11 @@ class UploadForegroundService : ForegroundService(Companion, redeliverIntentIfKi if (shouldRetry()) { return@retryLoop } else { - return@mapLatest null + return@tryCompletingWithInternetUnlessCancelled null } - }.onSuccess { uuid -> return@mapLatest uuid } + }.onSuccess { uuid -> return@tryCompletingWithInternetUnlessCancelled uuid } } - }.first() + } currentState = if (transferUuid != null) { val url = sharedApiUrlCreator.shareTransferUrl(transferUuid) val transferType = startRequest.info.type @@ -268,6 +277,19 @@ class UploadForegroundService : ForegroundService(Companion, redeliverIntentIfKi } } + private suspend fun tryCompletingWithInternetUnlessCancelled( + valueIfCancelled: R, + withoutInternet: suspend () -> R, + block: suspend () -> R + ): R? = raceOf({ + isInternetConnectedFlow.mapLatest { isInternetConnected -> + if (isInternetConnected) block() else withoutInternet() + }.first() + }, { + cancelTransferSignals.receive() + valueIfCancelled + }) + private suspend fun shouldRetry(): Boolean = shouldRetrySignals.receive() private suspend fun startUploadSession(