From 574d6c51ab21854ace7ae41574df7274ca5ab448 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 9 Dec 2024 09:56:41 -0400 Subject: [PATCH] Improved progress indicator for backup file upload. --- .../securesms/backup/ArchiveUploadProgress.kt | 17 ++++++++++++ .../securesms/backup/v2/BackupRepository.kt | 5 ++-- .../remote/RemoteBackupsSettingsFragment.kt | 26 ++++++++++++++++++- .../securesms/jobs/BackupMessagesJob.kt | 2 +- app/src/main/res/values/strings.xml | 4 +-- .../signalservice/api/archive/ArchiveApi.kt | 5 ++-- .../internal/push/PushServiceSocket.java | 4 +++ 7 files changed, 55 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/ArchiveUploadProgress.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/ArchiveUploadProgress.kt index 017ca5a81c..4a13daf424 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/ArchiveUploadProgress.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/ArchiveUploadProgress.kt @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.backup.v2.BackupRepository import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import kotlin.math.max import kotlin.time.Duration.Companion.milliseconds @@ -118,6 +119,22 @@ object ArchiveUploadProgress { } } + class ArchiveUploadProgressListener( + private val shouldCancel: () -> Boolean = { false } + ) : SignalServiceAttachment.ProgressListener { + override fun onAttachmentProgress(total: Long, progress: Long) { + updateState( + state = ArchiveUploadProgressState( + state = ArchiveUploadProgressState.State.UploadingMessages, + totalAttachments = total, + completedAttachments = progress + ) + ) + } + + override fun shouldCancel(): Boolean = shouldCancel() + } + object ArchiveBackupProgressListener : BackupRepository.ExportProgressListener { override fun onAccount() { updatePhase(ArchiveUploadProgressState.BackupPhase.Account) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index d7fc780643..197d660c9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -947,10 +947,11 @@ object BackupRepository { fun uploadBackupFile( resumableSpec: ResumableMessagesBackupUploadSpec, backupStream: InputStream, - backupStreamLength: Long + backupStreamLength: Long, + progressListener: ProgressListener? = null ): NetworkResult { val (form, resumableUploadUrl) = resumableSpec - return SignalNetwork.archive.uploadBackupFile(form, resumableUploadUrl, backupStream, backupStreamLength) + return SignalNetwork.archive.uploadBackupFile(form, resumableUploadUrl, backupStream, backupStreamLength, progressListener) .also { Log.i(TAG, "UploadBackupFileResult: $it") } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt index 0179edee43..e684a415b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt @@ -76,7 +76,10 @@ import org.signal.core.ui.Snackbars import org.signal.core.ui.Texts import org.signal.core.ui.horizontalGutters import org.signal.core.ui.theme.SignalTheme +import org.signal.core.util.bytes +import org.signal.core.util.gibiBytes import org.signal.core.util.logging.Log +import org.signal.core.util.mebiBytes import org.signal.core.util.money.FiatMoney import org.thoughtcrime.securesms.BiometricDeviceAuthentication import org.thoughtcrime.securesms.R @@ -966,7 +969,7 @@ private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadPro return when (archiveUploadProgressState.state) { ArchiveUploadProgressState.State.None -> stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup) ArchiveUploadProgressState.State.BackingUpMessages -> getBackupPhaseMessage(archiveUploadProgressState) - ArchiveUploadProgressState.State.UploadingMessages -> stringResource(R.string.RemoteBackupsSettingsFragment__uploading_messages) + ArchiveUploadProgressState.State.UploadingMessages -> getUploadingMessages(archiveUploadProgressState) ArchiveUploadProgressState.State.UploadingAttachments -> getUploadingAttachmentsMessage(archiveUploadProgressState) } } @@ -989,6 +992,19 @@ private fun getBackupPhaseMessage(state: ArchiveUploadProgressState): String { } } +@Composable +private fun getUploadingMessages(state: ArchiveUploadProgressState): String { + val formattedCompleted = state.completedAttachments.bytes.toUnitString() + val formattedTotal = state.totalAttachments.bytes.toUnitString() + val percent = if (state.totalAttachments == 0L) { + 0 + } else { + ((state.completedAttachments / state.totalAttachments.toFloat()) * 100).toInt() + } + + return stringResource(R.string.RemoteBackupsSettingsFragment__uploading_s_of_s_d, formattedCompleted, formattedTotal, percent) +} + @Composable private fun getUploadingAttachmentsMessage(state: ArchiveUploadProgressState): String { return if (state.totalAttachments == 0L) { @@ -1454,6 +1470,14 @@ private fun InProgressRowPreview() { totalAttachments = 100_000 ) ) + InProgressBackupRow( + archiveUploadProgressState = ArchiveUploadProgressState( + state = ArchiveUploadProgressState.State.UploadingMessages, + backupPhase = ArchiveUploadProgressState.BackupPhase.BackupPhaseNone, + completedAttachments = 1.gibiBytes.inWholeBytes + 100.mebiBytes.inWholeBytes, + totalAttachments = 12.gibiBytes.inWholeBytes + ) + ) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt index dcc38570da..f3de98cf7c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -134,7 +134,7 @@ class BackupMessagesJob private constructor( } FileInputStream(tempBackupFile).use { - when (val result = BackupRepository.uploadBackupFile(backupSpec, it, tempBackupFile.length())) { + when (val result = BackupRepository.uploadBackupFile(backupSpec, it, tempBackupFile.length(), ArchiveUploadProgress.ArchiveUploadProgressListener { isCanceled })) { is NetworkResult.Success -> { Log.i(TAG, "Successfully uploaded backup file.") SignalStore.backup.hasBackupBeenUploaded = true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86f695cb5e..230697d6bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7799,8 +7799,8 @@ Couldn\'t turn off and delete backups A network error occurred. Please check your internet connection and try again. - - Uploading messages… + + Uploading: %1$s of %2$s (%3$d%%) diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveApi.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveApi.kt index dc55026c89..78b0f2fb16 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveApi.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveApi.kt @@ -16,6 +16,7 @@ import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse import org.whispersystems.signalservice.api.backup.BackupKey import org.whispersystems.signalservice.api.backup.MediaRootBackupKey import org.whispersystems.signalservice.api.backup.MessageBackupKey +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.internal.push.AttachmentUploadForm import org.whispersystems.signalservice.internal.push.PushServiceSocket @@ -215,9 +216,9 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) { /** * Uploads your main backup file to cloud storage. */ - fun uploadBackupFile(uploadForm: AttachmentUploadForm, resumableUploadUrl: String, data: InputStream, dataLength: Long): NetworkResult { + fun uploadBackupFile(uploadForm: AttachmentUploadForm, resumableUploadUrl: String, data: InputStream, dataLength: Long, progressListener: SignalServiceAttachment.ProgressListener? = null): NetworkResult { return NetworkResult.fromFetch { - pushServiceSocket.uploadBackupFile(uploadForm, resumableUploadUrl, data, dataLength) + pushServiceSocket.uploadBackupFile(uploadForm, resumableUploadUrl, data, dataLength, progressListener) } } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 4bcddc21c8..aeb37fb43e 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -2004,6 +2004,10 @@ public class PushServiceSocket { } public void uploadBackupFile(AttachmentUploadForm uploadForm, String resumableUploadUrl, InputStream data, long dataLength) throws IOException { + uploadBackupFile(uploadForm, resumableUploadUrl, data, dataLength, null); + } + + public void uploadBackupFile(AttachmentUploadForm uploadForm, String resumableUploadUrl, InputStream data, long dataLength, ProgressListener progressListener) throws IOException { if (uploadForm.cdn == 2) { uploadToCdn2(resumableUploadUrl, data, "application/octet-stream", dataLength, false, new NoCipherOutputStreamFactory(), null, null); } else {