mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Add support for cancelling an in-progress archive upload.
Co-authored-by: Jeffrey Starke <jeffrey@signal.org>
This commit is contained in:
@@ -16,6 +16,10 @@ import org.signal.core.util.throttleLatest
|
|||||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
|
import org.thoughtcrime.securesms.jobs.ArchiveThumbnailUploadJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.BackfillDigestJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.UploadAttachmentToArchiveJob
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
|
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -85,13 +89,27 @@ object ArchiveUploadProgress {
|
|||||||
get() = uploadProgress.state != ArchiveUploadProgressState.State.None
|
get() = uploadProgress.state != ArchiveUploadProgressState.State.None
|
||||||
|
|
||||||
fun begin() {
|
fun begin() {
|
||||||
updateState {
|
updateState(overrideCancel = true) {
|
||||||
ArchiveUploadProgressState(
|
ArchiveUploadProgressState(
|
||||||
state = ArchiveUploadProgressState.State.Export
|
state = ArchiveUploadProgressState.State.Export
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
updateState {
|
||||||
|
ArchiveUploadProgressState(
|
||||||
|
state = ArchiveUploadProgressState.State.UserCanceled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDependencies.jobManager.cancelAllInQueue(BackfillDigestJob.QUEUE)
|
||||||
|
UploadAttachmentToArchiveJob.getAllQueueKeys().forEach {
|
||||||
|
AppDependencies.jobManager.cancelAllInQueue(it)
|
||||||
|
}
|
||||||
|
AppDependencies.jobManager.cancelAllInQueue(ArchiveThumbnailUploadJob.KEY)
|
||||||
|
}
|
||||||
|
|
||||||
fun onMessageBackupCreated(backupFileSize: Long) {
|
fun onMessageBackupCreated(backupFileSize: Long) {
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -144,8 +162,20 @@ object ArchiveUploadProgress {
|
|||||||
updateState { PROGRESS_NONE }
|
updateState { PROGRESS_NONE }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(notify: Boolean = true, transform: (ArchiveUploadProgressState) -> ArchiveUploadProgressState) {
|
private fun updateState(
|
||||||
val newState = transform(uploadProgress)
|
notify: Boolean = true,
|
||||||
|
overrideCancel: Boolean = false,
|
||||||
|
transform: (ArchiveUploadProgressState) -> ArchiveUploadProgressState
|
||||||
|
) {
|
||||||
|
val newState = transform(uploadProgress).let { state ->
|
||||||
|
val oldArchiveState = uploadProgress.state
|
||||||
|
if (oldArchiveState == ArchiveUploadProgressState.State.UserCanceled && !overrideCancel) {
|
||||||
|
state.copy(state = ArchiveUploadProgressState.State.UserCanceled)
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (uploadProgress == newState) {
|
if (uploadProgress == newState) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ private fun LazyListScope.appendBackupDetailsItems(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backupProgress == null || backupProgress.state == ArchiveUploadProgressState.State.None) {
|
if (backupProgress == null || backupProgress.state == ArchiveUploadProgressState.State.None || backupProgress.state == ArchiveUploadProgressState.State.UserCanceled) {
|
||||||
item {
|
item {
|
||||||
LastBackupRow(
|
LastBackupRow(
|
||||||
lastBackupTimestamp = lastBackupTimestamp,
|
lastBackupTimestamp = lastBackupTimestamp,
|
||||||
@@ -1074,16 +1074,19 @@ private fun InProgressBackupRow(
|
|||||||
.padding(top = 16.dp, bottom = 14.dp)
|
.padding(top = 16.dp, bottom = 14.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f)
|
||||||
verticalArrangement = spacedBy(12.dp)
|
|
||||||
) {
|
) {
|
||||||
when (archiveUploadProgressState.state) {
|
when (archiveUploadProgressState.state) {
|
||||||
ArchiveUploadProgressState.State.None -> {
|
ArchiveUploadProgressState.State.None, ArchiveUploadProgressState.State.UserCanceled -> {
|
||||||
ArchiveProgressIndicator()
|
ArchiveProgressIndicator()
|
||||||
}
|
}
|
||||||
ArchiveUploadProgressState.State.Export -> {
|
ArchiveUploadProgressState.State.Export -> {
|
||||||
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.frameExportProgress(), animationSpec = tween(durationMillis = 250))
|
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.frameExportProgress(), animationSpec = tween(durationMillis = 250))
|
||||||
ArchiveProgressIndicator(progress = { progressValue })
|
ArchiveProgressIndicator(
|
||||||
|
progress = { progressValue },
|
||||||
|
isCancelable = true,
|
||||||
|
cancel = cancelArchiveUpload
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> {
|
ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> {
|
||||||
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.uploadProgress(), animationSpec = tween(durationMillis = 250))
|
val progressValue by animateFloatAsState(targetValue = archiveUploadProgressState.uploadProgress(), animationSpec = tween(durationMillis = 250))
|
||||||
@@ -1110,12 +1113,14 @@ private fun ArchiveProgressIndicator(
|
|||||||
isCancelable: Boolean = false,
|
isCancelable: Boolean = false,
|
||||||
cancel: () -> Unit = {}
|
cancel: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Row {
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
trackColor = MaterialTheme.colorScheme.secondaryContainer,
|
trackColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
progress = progress,
|
progress = progress,
|
||||||
drawStopIndicator = {},
|
drawStopIndicator = {},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.weight(1f).padding(vertical = 12.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isCancelable) {
|
if (isCancelable) {
|
||||||
@@ -1132,7 +1137,7 @@ private fun ArchiveProgressIndicator(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState): String {
|
private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState): String {
|
||||||
return when (archiveUploadProgressState.state) {
|
return when (archiveUploadProgressState.state) {
|
||||||
ArchiveUploadProgressState.State.None -> stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup)
|
ArchiveUploadProgressState.State.None, ArchiveUploadProgressState.State.UserCanceled -> stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup)
|
||||||
ArchiveUploadProgressState.State.Export -> getBackupExportPhaseProgressString(archiveUploadProgressState)
|
ArchiveUploadProgressState.State.Export -> getBackupExportPhaseProgressString(archiveUploadProgressState)
|
||||||
ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> getBackupUploadPhaseProgressString(archiveUploadProgressState)
|
ArchiveUploadProgressState.State.UploadBackupFile, ArchiveUploadProgressState.State.UploadMedia -> getBackupUploadPhaseProgressString(archiveUploadProgressState)
|
||||||
}
|
}
|
||||||
@@ -1628,30 +1633,6 @@ private fun InProgressRowPreview() {
|
|||||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Account
|
backupPhase = ArchiveUploadProgressState.BackupPhase.Account
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
InProgressBackupRow(
|
|
||||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
|
||||||
state = ArchiveUploadProgressState.State.Export,
|
|
||||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Call
|
|
||||||
)
|
|
||||||
)
|
|
||||||
InProgressBackupRow(
|
|
||||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
|
||||||
state = ArchiveUploadProgressState.State.Export,
|
|
||||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Sticker
|
|
||||||
)
|
|
||||||
)
|
|
||||||
InProgressBackupRow(
|
|
||||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
|
||||||
state = ArchiveUploadProgressState.State.Export,
|
|
||||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Recipient
|
|
||||||
)
|
|
||||||
)
|
|
||||||
InProgressBackupRow(
|
|
||||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
|
||||||
state = ArchiveUploadProgressState.State.Export,
|
|
||||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Thread
|
|
||||||
)
|
|
||||||
)
|
|
||||||
InProgressBackupRow(
|
InProgressBackupRow(
|
||||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||||
state = ArchiveUploadProgressState.State.Export,
|
state = ArchiveUploadProgressState.State.Export,
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cancelUpload() {
|
fun cancelUpload() {
|
||||||
// TODO [message-backups] -- Perform cancel of media upload.
|
ArchiveUploadProgress.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshState(lastPurchase: InAppPaymentTable.InAppPayment?) {
|
private suspend fun refreshState(lastPurchase: InAppPaymentTable.InAppPayment?) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.signal.core.util.stream.LimitedInputStream
|
|||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||||
|
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||||
import org.thoughtcrime.securesms.backup.v2.ArchiveValidator
|
import org.thoughtcrime.securesms.backup.v2.ArchiveValidator
|
||||||
import org.thoughtcrime.securesms.backup.v2.BackupMetadata
|
import org.thoughtcrime.securesms.backup.v2.BackupMetadata
|
||||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||||
@@ -46,7 +47,6 @@ import org.thoughtcrime.securesms.database.MessageType
|
|||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
||||||
import org.thoughtcrime.securesms.jobs.BackfillDigestJob
|
|
||||||
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
|
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
|
||||||
import org.thoughtcrime.securesms.jobs.BackupRestoreJob
|
import org.thoughtcrime.securesms.jobs.BackupRestoreJob
|
||||||
import org.thoughtcrime.securesms.jobs.BackupRestoreMediaJob
|
import org.thoughtcrime.securesms.jobs.BackupRestoreMediaJob
|
||||||
@@ -218,10 +218,8 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun haltAllJobs() {
|
fun haltAllJobs() {
|
||||||
AppDependencies.jobManager.cancelAllInQueue(BackfillDigestJob.QUEUE)
|
ArchiveUploadProgress.cancel()
|
||||||
AppDependencies.jobManager.cancelAllInQueue("ArchiveAttachmentJobs_0")
|
|
||||||
AppDependencies.jobManager.cancelAllInQueue("ArchiveAttachmentJobs_1")
|
|
||||||
AppDependencies.jobManager.cancelAllInQueue("ArchiveThumbnailUploadJob")
|
|
||||||
AppDependencies.jobManager.cancelAllInQueue("BackupRestoreJob")
|
AppDependencies.jobManager.cancelAllInQueue("BackupRestoreJob")
|
||||||
AppDependencies.jobManager.cancelAllInQueue("__LOCAL_BACKUP__")
|
AppDependencies.jobManager.cancelAllInQueue("__LOCAL_BACKUP__")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ class ArchiveAttachmentBackfillJob private constructor(parameters: Parameters) :
|
|||||||
|
|
||||||
ArchiveUploadProgress.onAttachmentsStarted(SignalDatabase.attachments.getPendingArchiveUploadBytes())
|
ArchiveUploadProgress.onAttachmentsStarted(SignalDatabase.attachments.getPendingArchiveUploadBytes())
|
||||||
|
|
||||||
Log.i(TAG, "Adding ${jobs.size} jobs to backfill attachments.")
|
if (!isCanceled) {
|
||||||
AppDependencies.jobManager.addAll(jobs)
|
Log.i(TAG, "Adding ${jobs.size} jobs to backfill attachments.")
|
||||||
|
AppDependencies.jobManager.addAll(jobs)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Job was canceled. Not enqueuing backfill.")
|
||||||
|
}
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class BackupMessagesJob private constructor(
|
|||||||
stopwatch.split("used-space")
|
stopwatch.split("used-space")
|
||||||
stopwatch.stop(TAG)
|
stopwatch.stop(TAG)
|
||||||
|
|
||||||
if (SignalStore.backup.backsUpMedia && SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload()) {
|
if (SignalStore.backup.backsUpMedia && SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload() && !isCanceled) {
|
||||||
Log.i(TAG, "Enqueuing attachment backfill job.")
|
Log.i(TAG, "Enqueuing attachment backfill job.")
|
||||||
AppDependencies.jobManager.add(ArchiveAttachmentBackfillJob())
|
AppDependencies.jobManager.add(ArchiveAttachmentBackfillJob())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
|
|||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCanceled) {
|
||||||
|
Log.w(TAG, "[$attachmentId] Canceled. Refusing to proceed.")
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
if (attachment.archiveTransferState == AttachmentTable.ArchiveTransferState.NONE) {
|
if (attachment.archiveTransferState == AttachmentTable.ArchiveTransferState.NONE) {
|
||||||
Log.i(TAG, "[$attachmentId] Not marked as pending copy. Enqueueing an upload job instead.")
|
Log.i(TAG, "[$attachmentId] Not marked as pending copy. Enqueueing an upload job instead.")
|
||||||
AppDependencies.jobManager.add(UploadAttachmentToArchiveJob(attachmentId))
|
AppDependencies.jobManager.add(UploadAttachmentToArchiveJob(attachmentId))
|
||||||
@@ -138,7 +143,12 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
|
|||||||
Log.d(TAG, "[$attachmentId] Updating archive transfer state to ${AttachmentTable.ArchiveTransferState.FINISHED}")
|
Log.d(TAG, "[$attachmentId] Updating archive transfer state to ${AttachmentTable.ArchiveTransferState.FINISHED}")
|
||||||
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.FINISHED)
|
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.FINISHED)
|
||||||
|
|
||||||
ArchiveThumbnailUploadJob.enqueueIfNecessary(attachmentId)
|
if (!isCanceled) {
|
||||||
|
ArchiveThumbnailUploadJob.enqueueIfNecessary(attachmentId)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "[$attachmentId] Refusing to enqueue thumb for canceled upload.")
|
||||||
|
}
|
||||||
|
|
||||||
SignalStore.backup.usedBackupMediaSpace += AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(attachment.size))
|
SignalStore.backup.usedBackupMediaSpace += AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(attachment.size))
|
||||||
|
|
||||||
ArchiveUploadProgress.onAttachmentFinished(attachmentId)
|
ArchiveUploadProgress.onAttachmentFinished(attachmentId)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.io.FileNotFoundException
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.ProtocolException
|
import java.net.ProtocolException
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,12 +46,17 @@ class UploadAttachmentToArchiveJob private constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(UploadAttachmentToArchiveJob::class)
|
private val TAG = Log.tag(UploadAttachmentToArchiveJob::class)
|
||||||
const val KEY = "UploadAttachmentToArchiveJob"
|
const val KEY = "UploadAttachmentToArchiveJob"
|
||||||
|
const val MAX_JOB_QUEUES = 2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This randomly selects between one of two queues. It's a fun way of limiting the concurrency of the upload jobs to
|
* This randomly selects between one of [MAX_JOB_QUEUES] queues. It's a fun way of limiting the concurrency of the upload jobs to
|
||||||
* take up at most two job runners.
|
* take up at most two job runners.
|
||||||
*/
|
*/
|
||||||
fun buildQueueKey() = "ArchiveAttachmentJobs_${Random.nextInt(0, 2)}"
|
fun buildQueueKey(
|
||||||
|
queue: Int = Random.nextInt(0, MAX_JOB_QUEUES)
|
||||||
|
) = "ArchiveAttachmentJobs_$queue"
|
||||||
|
|
||||||
|
fun getAllQueueKeys() = (0 until MAX_JOB_QUEUES).map { buildQueueKey(queue = it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(attachmentId: AttachmentId) : this(
|
constructor(attachmentId: AttachmentId) : this(
|
||||||
@@ -182,7 +188,11 @@ class UploadAttachmentToArchiveJob private constructor(
|
|||||||
|
|
||||||
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachment.attachmentId, uploadResult)
|
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachment.attachmentId, uploadResult)
|
||||||
|
|
||||||
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachment.attachmentId))
|
if (!isCanceled) {
|
||||||
|
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachment.attachmentId))
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "[$attachmentId] Job was canceled. Skipping copy job.")
|
||||||
|
}
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ message ArchiveUploadProgressState {
|
|||||||
Export = 1;
|
Export = 1;
|
||||||
UploadBackupFile = 2;
|
UploadBackupFile = 2;
|
||||||
UploadMedia = 3;
|
UploadMedia = 3;
|
||||||
|
UserCanceled = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user