Improved progress indicator for backup file upload.

This commit is contained in:
Alex Hart 2024-12-09 09:56:41 -04:00 committed by Greyson Parrelli
parent a188eb64ab
commit 574d6c51ab
7 changed files with 55 additions and 8 deletions

View file

@ -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)

View file

@ -947,10 +947,11 @@ object BackupRepository {
fun uploadBackupFile(
resumableSpec: ResumableMessagesBackupUploadSpec,
backupStream: InputStream,
backupStreamLength: Long
backupStreamLength: Long,
progressListener: ProgressListener? = null
): NetworkResult<Unit> {
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") }
}

View file

@ -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
)
)
}
}
}

View file

@ -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

View file

@ -7799,8 +7799,8 @@
<string name="RemoteBackupsSettingsFragment__couldnt_turn_off_and_delete_backups">Couldn\'t turn off and delete backups</string>
<!-- Dialog body for network error while trying to disable backups -->
<string name="RemoteBackupsSettingsFragment__a_network_error_occurred">A network error occurred. Please check your internet connection and try again.</string>
<!-- Progress message when backup file is being uploaded -->
<string name="RemoteBackupsSettingsFragment__uploading_messages">Uploading messages…</string>
<!-- Progress message when backup file is being uploaded. First placeholder and second placeholder are formatted byte sizes (2 MB) and third is percent completion. -->
<string name="RemoteBackupsSettingsFragment__uploading_s_of_s_d">Uploading: %1$s of %2$s (%3$d%%)</string>
<!-- SubscriptionNotFoundBottomSheet -->
<!-- Displayed as a bottom sheet title -->

View file

@ -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<Unit> {
fun uploadBackupFile(uploadForm: AttachmentUploadForm, resumableUploadUrl: String, data: InputStream, dataLength: Long, progressListener: SignalServiceAttachment.ProgressListener? = null): NetworkResult<Unit> {
return NetworkResult.fromFetch {
pushServiceSocket.uploadBackupFile(uploadForm, resumableUploadUrl, data, dataLength)
pushServiceSocket.uploadBackupFile(uploadForm, resumableUploadUrl, data, dataLength, progressListener)
}
}

View file

@ -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 {