mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add improved archive upload progress.
This commit is contained in:
committed by
Greyson Parrelli
parent
981808d074
commit
6d5be0b445
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import org.signal.core.util.throttleLatest
|
||||
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
|
||||
@@ -116,4 +117,49 @@ object ArchiveUploadProgress {
|
||||
_progress.tryEmit(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
object ArchiveBackupProgressListener : BackupRepository.ExportProgressListener {
|
||||
override fun onAccount() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Account)
|
||||
}
|
||||
|
||||
override fun onRecipient() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Recipient)
|
||||
}
|
||||
|
||||
override fun onThread() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Thread)
|
||||
}
|
||||
|
||||
override fun onCall() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Call)
|
||||
}
|
||||
|
||||
override fun onSticker() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Sticker)
|
||||
}
|
||||
|
||||
override fun onMessage(currentProgress: Long, approximateCount: Long) {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Message, currentProgress, approximateCount)
|
||||
}
|
||||
|
||||
override fun onAttachment(currentProgress: Long, totalCount: Long) {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.BackupPhaseNone)
|
||||
}
|
||||
|
||||
private fun updatePhase(
|
||||
phase: ArchiveUploadProgressState.BackupPhase,
|
||||
completedObjects: Long = 0L,
|
||||
totalObjects: Long = 0L
|
||||
) {
|
||||
updateState(
|
||||
state = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = phase,
|
||||
completedAttachments = completedObjects,
|
||||
totalAttachments = totalObjects
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,6 +450,7 @@ object BackupRepository {
|
||||
plaintext: Boolean = false,
|
||||
currentTime: Long = System.currentTimeMillis(),
|
||||
mediaBackupEnabled: Boolean = SignalStore.backup.backsUpMedia,
|
||||
progressEmitter: ExportProgressListener? = null,
|
||||
cancellationSignal: () -> Boolean = { false },
|
||||
exportExtras: ((SignalDatabase) -> Unit)? = null
|
||||
) {
|
||||
@@ -464,7 +465,15 @@ object BackupRepository {
|
||||
)
|
||||
}
|
||||
|
||||
export(currentTime = currentTime, isLocal = false, writer = writer, mediaBackupEnabled = mediaBackupEnabled, cancellationSignal = cancellationSignal, exportExtras = exportExtras)
|
||||
export(
|
||||
currentTime = currentTime,
|
||||
isLocal = false,
|
||||
writer = writer,
|
||||
progressEmitter = progressEmitter,
|
||||
mediaBackupEnabled = mediaBackupEnabled,
|
||||
cancellationSignal = cancellationSignal,
|
||||
exportExtras = exportExtras
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,7 +576,9 @@ object BackupRepository {
|
||||
return@export
|
||||
}
|
||||
|
||||
progressEmitter?.onMessage()
|
||||
val approximateMessageCount = dbSnapshot.messageTable.getApproximateExportableMessageCount(exportState.threadIds)
|
||||
val frameCountStart = frameCount
|
||||
progressEmitter?.onMessage(0, approximateMessageCount)
|
||||
ChatItemArchiveProcessor.export(dbSnapshot, exportState, selfRecipientId, cancellationSignal) { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("message")
|
||||
@@ -575,6 +586,7 @@ object BackupRepository {
|
||||
|
||||
if (frameCount % 1000 == 0L) {
|
||||
Log.d(TAG, "[export] Exported $frameCount frames so far.")
|
||||
progressEmitter?.onMessage(frameCount - frameCountStart, approximateMessageCount)
|
||||
if (cancellationSignal()) {
|
||||
Log.w(TAG, "[export] Cancelled! Stopping")
|
||||
return@export
|
||||
@@ -1436,7 +1448,7 @@ object BackupRepository {
|
||||
fun onThread()
|
||||
fun onCall()
|
||||
fun onSticker()
|
||||
fun onMessage()
|
||||
fun onMessage(currentProgress: Long, approximateCount: Long)
|
||||
fun onAttachment(currentProgress: Long, totalCount: Long)
|
||||
}
|
||||
|
||||
|
||||
@@ -169,8 +169,10 @@ object LocalArchiver {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_STICKER))
|
||||
}
|
||||
|
||||
override fun onMessage() {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_MESSAGE))
|
||||
override fun onMessage(currentProgress: Long, approximateCount: Long) {
|
||||
if (currentProgress == 0L) {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_MESSAGE))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachment(currentProgress: Long, totalCount: Long) {
|
||||
|
||||
@@ -105,6 +105,7 @@ import org.thoughtcrime.securesms.util.viewModel
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -928,9 +929,6 @@ private fun SubscriptionMismatchMissingGooglePlayCard(
|
||||
private fun InProgressBackupRow(
|
||||
archiveUploadProgressState: ArchiveUploadProgressState
|
||||
) {
|
||||
val progress = archiveUploadProgressState.completedAttachments
|
||||
val totalProgress = archiveUploadProgressState.totalAttachments
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = dimensionResource(id = CoreUiR.dimen.gutter))
|
||||
@@ -939,23 +937,18 @@ private fun InProgressBackupRow(
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
if (totalProgress == 0L) {
|
||||
val backupProgress = getBackupProgress(archiveUploadProgressState)
|
||||
if (backupProgress.total == 0L) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = { ((progress ?: 0) / totalProgress).toFloat() }
|
||||
progress = { backupProgress.progress }
|
||||
)
|
||||
}
|
||||
|
||||
val inProgressText = if (totalProgress == 0L) {
|
||||
getProgressStateMessage(archiveUploadProgressState.state)
|
||||
} else {
|
||||
stringResource(R.string.RemoteBackupsSettingsFragment__d_slash_d, progress ?: 0, totalProgress)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = inProgressText,
|
||||
text = getProgressStateMessage(archiveUploadProgressState),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@@ -963,15 +956,46 @@ private fun InProgressBackupRow(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getProgressStateMessage(state: ArchiveUploadProgressState.State): String {
|
||||
val stringId = when (state) {
|
||||
ArchiveUploadProgressState.State.None, ArchiveUploadProgressState.State.BackingUpMessages -> R.string.RemoteBackupsSettingsFragment__processing_backup
|
||||
ArchiveUploadProgressState.State.UploadingMessages -> R.string.RemoteBackupsSettingsFragment__uploading_messages
|
||||
ArchiveUploadProgressState.State.UploadingAttachments -> R.string.RemoteBackupsSettingsFragment__processing_backup
|
||||
}
|
||||
private fun getBackupProgress(state: ArchiveUploadProgressState): BackupProgress {
|
||||
val approximateMessageCount = max(state.completedAttachments, state.totalAttachments)
|
||||
return BackupProgress(state.completedAttachments, approximateMessageCount)
|
||||
}
|
||||
|
||||
return stringResource(stringId)
|
||||
@Composable
|
||||
private fun getProgressStateMessage(archiveUploadProgressState: ArchiveUploadProgressState): String {
|
||||
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.UploadingAttachments -> getUploadingAttachmentsMessage(archiveUploadProgressState)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getBackupPhaseMessage(state: ArchiveUploadProgressState): String {
|
||||
return when (state.backupPhase) {
|
||||
ArchiveUploadProgressState.BackupPhase.BackupPhaseNone -> stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup)
|
||||
ArchiveUploadProgressState.BackupPhase.Message -> {
|
||||
val progress = getBackupProgress(state)
|
||||
pluralStringResource(
|
||||
R.plurals.RemoteBackupsSettingsFragment__processing_d_of_d_d_messages,
|
||||
progress.total.toInt(),
|
||||
progress.completed,
|
||||
progress.total,
|
||||
(progress.progress * 100).toInt()
|
||||
)
|
||||
}
|
||||
else -> stringResource(R.string.RemoteBackupsSettingsFragment__preparing_backup)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getUploadingAttachmentsMessage(state: ArchiveUploadProgressState): String {
|
||||
return if (state.totalAttachments == 0L) {
|
||||
stringResource(R.string.RemoteBackupsSettingsFragment__processing_backup)
|
||||
} else {
|
||||
stringResource(R.string.RemoteBackupsSettingsFragment__d_slash_d, state.completedAttachments, state.totalAttachments)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -1368,7 +1392,69 @@ private fun LastBackupRowPreview() {
|
||||
@Composable
|
||||
private fun InProgressRowPreview() {
|
||||
Previews.Preview {
|
||||
InProgressBackupRow(archiveUploadProgressState = ArchiveUploadProgressState())
|
||||
Column {
|
||||
InProgressBackupRow(archiveUploadProgressState = ArchiveUploadProgressState())
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.BackupPhaseNone
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Account
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Call
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Sticker
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Recipient
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Thread
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Message,
|
||||
completedAttachments = 1,
|
||||
totalAttachments = 1
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Message,
|
||||
completedAttachments = 1000,
|
||||
totalAttachments = 100_000
|
||||
)
|
||||
)
|
||||
InProgressBackupRow(
|
||||
archiveUploadProgressState = ArchiveUploadProgressState(
|
||||
state = ArchiveUploadProgressState.State.BackingUpMessages,
|
||||
backupPhase = ArchiveUploadProgressState.BackupPhase.Message,
|
||||
completedAttachments = 1_000_000,
|
||||
totalAttachments = 100_000
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1434,3 +1520,10 @@ private fun BackupFrequencyDialogPreview() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class BackupProgress(
|
||||
val completed: Long,
|
||||
val total: Long
|
||||
) {
|
||||
val progress: Float = if (total > 0) completed / total.toFloat() else 0f
|
||||
}
|
||||
|
||||
@@ -1808,6 +1808,21 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
.readToSingleInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of thread ids, return the count of all messages in the table that match that thread id. This will include *all* messages, and is
|
||||
* explicitly for use as a "fuzzy total"
|
||||
*/
|
||||
fun getApproximateExportableMessageCount(threadIds: Set<Long>): Long {
|
||||
val queries = SqlUtil.buildCollectionQuery(THREAD_ID, threadIds)
|
||||
return queries.sumOf {
|
||||
readableDatabase.count()
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_COUNT")
|
||||
.where(it.where, it.whereArgs)
|
||||
.run()
|
||||
.readToSingleLong(0L)
|
||||
}
|
||||
}
|
||||
|
||||
fun canSetUniversalTimer(threadId: Long): Boolean {
|
||||
if (threadId == -1L) {
|
||||
return true
|
||||
|
||||
@@ -209,7 +209,7 @@ class BackupMessagesJob private constructor(
|
||||
val outputStream = FileOutputStream(tempBackupFile)
|
||||
val backupKey = SignalStore.backup.messageBackupKey
|
||||
val currentTime = System.currentTimeMillis()
|
||||
BackupRepository.export(outputStream = outputStream, messageBackupKey = backupKey, append = { tempBackupFile.appendBytes(it) }, plaintext = false, cancellationSignal = { this.isCanceled }, currentTime = currentTime) {
|
||||
BackupRepository.export(outputStream = outputStream, messageBackupKey = backupKey, progressEmitter = ArchiveUploadProgress.ArchiveBackupProgressListener, append = { tempBackupFile.appendBytes(it) }, plaintext = false, cancellationSignal = { this.isCanceled }, currentTime = currentTime) {
|
||||
writeMediaCursorToTemporaryTable(it, currentTime = currentTime, mediaBackupEnabled = SignalStore.backup.backsUpMedia)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,22 @@ message ArchiveUploadProgressState {
|
||||
UploadingAttachments = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the current phase the backup is in when we are exporting the database
|
||||
* to the temporary file.
|
||||
*/
|
||||
enum BackupPhase {
|
||||
BackupPhaseNone = 0;
|
||||
Account = 1;
|
||||
Recipient = 2;
|
||||
Thread = 3;
|
||||
Call = 4;
|
||||
Sticker = 5;
|
||||
Message = 6;
|
||||
}
|
||||
|
||||
State state = 1;
|
||||
uint64 completedAttachments = 2;
|
||||
uint64 totalAttachments = 3;
|
||||
BackupPhase backupPhase = 4;
|
||||
}
|
||||
@@ -7772,6 +7772,13 @@
|
||||
<string name="RemoteBackupsSettingsFragment__learn_more">Learn more</string>
|
||||
<!-- Linear progress dialog text shown when creating a backup -->
|
||||
<string name="RemoteBackupsSettingsFragment__processing_backup">Processing backup…</string>
|
||||
<!-- Linear progress dialog text shown when preparing a backup -->
|
||||
<string name="RemoteBackupsSettingsFragment__preparing_backup">Preparing backup…</string>
|
||||
<!-- Linear progress dialog text shown when processing messages for backup. First placeholder is completed count, second is approximate total count, third is percent completed. -->
|
||||
<plurals name="RemoteBackupsSettingsFragment__processing_d_of_d_d_messages">
|
||||
<item quantity="one">Processing %1$d of %2$d (%3$d%%) message</item>
|
||||
<item quantity="other">Processing %1$d of %2$d (%3$d%%) messages</item>
|
||||
</plurals>
|
||||
<!-- Displayed in row when backup is available for download and users subscription has expired. First placeholder is data size e.g. 12MB, second is days before expiration -->
|
||||
<plurals name="RemoteBackupsSettingsFragment__you_have_s_of_backup_data">
|
||||
<item quantity="one">You have %1$s of backup data that’s not on this device. Your backup will be deleted when your subscription ends in %2$d day.</item>
|
||||
|
||||
Reference in New Issue
Block a user